Spring中ContextLoaderListener监听详解
作者:it_lihongmin
前言
web.xml配置文件的部分,方便下面的理解:
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:Resource/SpringConf.xml</param-value> </context-param> <servlet> <servlet-name>springweb</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/springweb.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springweb</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
一、SpringMVC总览
SpringMVC启动时会启动WebApplicationContext类型的容器,并且会调用之前分析的refresh方法。
还是从原始的SpringMVC开始,启动方式有三种,配置文件中配置ContextLoadServlet,ContextLoaderListener、ContextLoaderPlugin。
不论哪种方式启动都大致相同。主要包括两部分:
1、ContextLoaderListener监听
初始化Spring容器,WebApplicationContext类型,并且执行refresh方法
2、DispatcherServlet(一个特殊的Servlet,如上面的配置web.xml中进行配置了)
1)、DispatcherServlet像普通Servlet一样,init中启动九大件为服务请求做准备
HttpServletBean#init => DispatcherServlet#onRefresh => DispatcherServlet#initStrategies(初始化九大件)
2)、当上面初始化完成后,则请求进入后拦截转发到DispatcherServlet
DispatcherServlet#doService=> DispatcherServlet#doDispatch(执行MVC流程)
二、ContextLoaderListener的contextInitialized监听事件
当Web容器启动时,触发ContextLoaderListener的contextInitialized方法:
@Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { // 省略部分日志和try catch代码 long startTime = System.currentTimeMillis(); if (this.context == null) { this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> // determine parent for root web application context, if any. ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } return this.context; }
这里需要考虑容器是否其他地方已经创建了,但是不论是否创建。整个Spring MVC启动过程需要经历几个步骤:
1、createWebApplicationContext(创建Web类型的ApplicationContext)
2、configureAndRefreshWebApplicationContext(主要是refresh方法执行)
3、将初始化的WebApplicationContext容器,用key为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE加载到ServletContext容器中
创建Web类型的ApplicationContext
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("省略"); } return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }
先判断启动的WebApplicationContext的类型Class,再使用反射初始化。主要是看启动的Class是怎么确定的。
protected Class<?> determineContextClass(ServletContext servletContext) { String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { // 省略try catch代码 return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } else { contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); // 省略try catch代码 return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } }
先看启动Web时是否有进行配置Class,否则就在默认的配置策略中进行获取。详细可以参见:Spring中的策略模式简单实现与使用分析
从配置文件ContextLoader.properties中获取到的类型为(所以如果没有配置默认启动XmlWebApplicationContext,Spring Boot不会启动该类型,但是SpringMVC没有太大差异):
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
执行refresh方法
不管怎么样都回执行configureAndRefreshWebApplicationContext方法,以启动Spring 容器,也就是之前一直分析refresh方法。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { // 设置ConfigurableWebApplicationContext的容器Id if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } // 设置Web容器上下文,设置到Spring容器中 wac.setServletContext(sc); String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); // 配置location,如果配置的话,则refresh方法时会调用Resource加载,解析,初始化 if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } // 设置Environment,refresh时也分析过,如果没有则会从java.lang.System中 // 获取数据初始化StandardEnvironment或其子类 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } // 初始化自定义的容器,如果自己配置的话(我们可以自定义实现ServletContextListener) customizeContext(sc, wac); // 调用refresh方法 wac.refresh(); }
1、设置WebApplicationContext的Id
2、设置Web容器上下文,设置到Spring容器中
3、配置location(比如上面配置中的contextConfigLocation,则refresh方法时会调用Resource加载,解析,初始化)
4、初始化自定义的监听
5、最重要的一步,启动Spring容器(可以从这里开始看:SpringIoc源码(五)- ApplicationContext(一)- 结构梳理)
到此这篇关于Spring中ContextLoaderListener监听详解的文章就介绍到这了,更多相关ContextLoaderListener监听内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!