熟悉的web.xml
ContextLoaderListener
为了使用spring我们常见在web.xml中做这样的配置. 配置一个ContextLoaderListener监听器。这个监听器是如何把Tomcat与spring关联的呢?
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--启用spring--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--2、部署applicationContext的xml文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param> <!--3、启用springmvc--> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup><!--是启动顺序,让这个Servlet随Servletp容器一起启动。--> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> <!--会拦截URL中带“/”的请求。--> </servlet-mapping> <welcome-file-list><!--指定欢迎页面--> <welcome-file>login.html</welcome-file> </welcome-file-list> <error-page> <!--当系统出现404错误,跳转到页面nopage.html--> <error-code>404</error-code> <location>/nopage.html</location> </error-page> <error-page> <!--当系统出现java.lang.NullPointerException,跳转到页面error.html--> <exception-type>java.lang.NullPointerException</exception-type> <location>/error.html</location> </error-page> <session-config><!--会话超时配置,单位分钟--> <session-timeout>360</session-timeout> </session-config> </web-app>
Tomcat的初始化StandardContext.startInternal()
1.Tomcat对web.xml的加载.
要解开Tomcat与spring的关系,我们首先应该先搞懂,Tomcat是在什么位置,如何加载的web.xml文件。
说到这个问题,我们不得不提一下Tomcat启动的三大主线中的start()主线。
Tomcat 层级调用组件的start()方法,执行到StandardContext.startInternal() 时, 在startInternal()方法中调用fireLifecycleEvent()发布一个"configure_start" 事件.
public static final String CONFIGURE_START_EVENT = "configure_start"; // Notify our interested LifecycleListeners fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); //响应configure_start事件 protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(this, type, data); for (LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); } }
web.xml之旅就此开始。
在众多监听器中,有一个ContextConfig监听器,在监听到"configure_start" 事件后, 会执行configureStart()方法. 在configureStart()方法中执行webConfig()开始web.xml解析.
lifecycleEvent()==》 public void lifecycleEvent(LifecycleEvent event) { //如果事件类型是configure_start,执行 configureStart() if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart();// } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart(); } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) { .... } } configureStart()==》 protected synchronized void configureStart() { webConfig(); } //解析web.xml protected void webConfig() { .... }
webConfig:就是Tomcat加载解析web.xml的地方 方法中注释标注了1,2...步骤,详细讲解web.xml加载,解析的过程.讲的很详细,我这里不一一讲了,强烈建议大家去看看源码。 这里主要提一个两个重要的点
parseWebXml(contextWebXml, webXml, false)方法: 这个方法中有一个Digester工具,在Tomcat加载server.xml配置文件的时候就是使用了此工具,解析原理异曲同工。 此处使用WebRuleSet规则,将web.xml文件中的配置读取出来设置到webXml对象中去.configureContext(StandardContext context)方法: 将web.xml文件解析出来的各个组件设置到标准servlet上下文StandardContext中去。 其中就包括我们的filter ,servlet, listener
parseWebXml()===> public boolean parseWebXml(InputSource source, WebXml dest, boolean fragment) { Digester digester; WebRuleSet ruleSet; if (fragment) { digester = webFragmentDigester; ruleSet = webFragmentRuleSet; } else { digester = webDigester; ruleSet = webRuleSet; } .... } configure()===> private void configureContext(WebXml webxml) { //把配置文件中filter设置到context中 for (FilterDef filter : webxml.getFilters().values()) { if (filter.getAsyncSupported() == null) { filter.setAsyncSupported("false"); } context.addFilterDef(filter); } //把配置文件中filterMap设置到context中 for (FilterMap filterMap : webxml.getFilterMappings()) { context.addFilterMap(filterMap); } .... //把配置文件中sevrlet设置到context中 for (ServletDef servlet : webxml.getServlets().values()) { Wrapper wrapper = context.createWrapper(); } ... //把配置文件中listener设置到context中 for (String listener : webxml.getListeners()) { context.addApplicationListener(listener); } }
至此StandardContext已经有了我们配置的listener,fitler,servlet
2.Tomcat 执行了listener.
在加载并将filter,servlet,listener设置到contxt中后,接下来就是执行了。
还是StandardContext.startInternal()方法, 在方法的下半部分按顺序有如下操作:listenerStart() 启动所有的listener.filterStart() 启动所有的filterloadOnStartup(findChildren()) 启动所持有的servletlistenerStart() 启动众多listener,其中就包括我们配置的ContextLoaderListener监听器。
// Configure and call application event listeners if (ok) { if (!listenerStart()) { log.error(sm.getString("standardContext.listenerFail")); ok = false; } } ... // Configure and call application filters if (ok) { if (!filterStart()) { log.error(sm.getString("standardContext.filterFail")); ok = false; } } ... // Load and initialize all "load on startup" servlets if (ok) { if (!loadOnStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail")); ok = false; } }
3.ContextLoaderListener.contextInitialized(event) spring的初始化.
listenerStart() 方法其实就是调用Listener的contextInitialized()方法
public boolean listenerStart() { ... for (int i = 0; i < instances.length; i++) { if (!(instances[i] instanceof ServletContextListener)) continue; ServletContextListener listener = (ServletContextListener) instances[i]; try { fireContainerEvent("beforeContextInitialized", listener); if (noPluggabilityListeners.contains(listener)) { listener.contextInitialized(tldEvent); } else { //执行listener的初始。传递ServletContextEvent参数 listener.contextInitialized(event);// } fireContainerEvent("afterContextInitialized", listener); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); fireContainerEvent("afterContextInitialized", listener); getLogger().error (sm.getString("standardContext.listenerStart", instances[i].getClass().getName()), t); ok = false; } } ... }
来到ContextLoaderListener.contextInitialized(ServletContextEvent event)方法中,开始初始化web应用下的IO容器。
public void contextInitialized(ServletContextEvent event) { this.initWebApplicationContext(event.getServletContext()); }
initWebApplicationContext方法中,调用了createWebApplicationContext方法来构建一个上下文类,createWebApplicationContext方法中首先调用determineContextClass()来判断上下文类型决定创建哪种上下文, 通常会使用默认策略,根据ContextLoader.properties文件中配置的WebApplicationContext值创建上下文XmlWebApplicationContext对象 至此web容器实例就创建出来了
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
紧接着调用configureAndRefreshWebApplicationContext()方法来初始化bean.就开始了spring的著名的初始化方法refresh()
public void refresh() throws BeansException, IllegalStateException { synchronized(this.startupShutdownMonitor) { this.prepareRefresh(); ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); this.prepareBeanFactory(beanFactory); try { this.postProcessBeanFactory(beanFactory); this.invokeBeanFactoryPostProcessors(beanFactory); this.registerBeanPostProcessors(beanFactory); this.initMessageSource(); this.initApplicationEventMulticaster(); this.onRefresh(); this.registerListeners(); this.finishBeanFactoryInitialization(beanFactory); this.finishRefresh(); } catch (BeansException var9) { if (this.logger.isWarnEnabled()) { this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9); } this.destroyBeans(); this.cancelRefresh(var9); throw var9; } finally { this.resetCommonCaches(); } } }
总结:ContextLoaderListener通过实现ServletContextListener接口,继承ContextLoader加载器。 把Tomcat与spring连接到了一起。
看懂了Tomcat与spring的关系, 想想配置的DispatcherServlet。其中原理是否有门道了
Spring分别提供了用于启动WebApplicationContext的Servlet和Web容器监听器: org.springframework.web.context.ContextLoaderServlet; org.springframework.web.context.ContextLoaderListener. ContextLoaderListener,ContextLoaderServlet 其实区别不大。流程是一样的