说明:本文所用的SpringMVC版本为4.3.4.RELEASE,应用服务器为TomCat8.0.33。下面我们先回顾一下我们在用SpringMVC进行开发时在web.xml中进行的一些配置:
<!-- 配置Spring上下文配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- 配置SpringMVC Servlet --> <servlet> <servlet-name>spring-miscellaneous</servlet-name> <!-- SpringMVC 分发器 --> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 更改SpringMVC配置文件的名字 默认的是spring-mvc-servlet.xml --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-miscellaneous-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-miscellaneous</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 配置监听器 用来载入上下文配置文件 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>上面的代码,基本上是我们用哪个SpringMVC开发时进行的一些配置。那么这些配置都用什么用呢?下面我们来一点一点进行分析:
ContextLoaderListener
ContextLoaderListener上下文监听器。ContextLoaderListener的作用是Web容器启动的时候,自动装配ApplicationContext的配置信息,即初始化Spring IOC容器。我们看一下这个类的继承关系:
。从图中我们可以看出ContextLoaderListener实现了ServletContextListener(Servlet上下文监听器)接口。我们简单说一下ServletContextListener的作用:ServletContextListener中有两个方法,一个是contextInitialized一个是contextDestroyed。每一个应用都有ServletContext与之相关联,ServletContext中存放了一些web应用全局性的配置。当Servlet容器启动的时候,会实例化和ServletContext相关的一些类。如ApplicationContext(注意不是Spring中的那个ApplicationContext)、ApplicationContexFacade、ServletConfig、ServletContextEvent以及ServletContextListener的实现类等等,并调用contextInitialized方法进行一些上下文初始化相关的动作。当Servlet容器关闭的时候,会调用contextDestroyed销毁上下文相关的内容。即ServletContextListener是和ServletContext的生命周期相关联的。ContextLoaderListener实现了ServletContextListener接口,所以当容器启动的时候,会调用ContextLoaderListener中的contextInitialized方法,进行一系列的初始化的动作。
而<context-param>中的内容即是ServletContext中的一些配置信息。
下面我们看一下从容器启动到调用到contextInitialized方法的调用链:
从上图中我们可以看到在org.apache.catalina.core.StandardContext#listenerStart的方法中调用了contextInitialized的方法,进行上下文的初始化。PS:StandardContex是TomCat体系中很重要的一个类,可以找时间说一下TomCat的大致体系结构,网上也有很多资料。下面我们看一下在ContextLoaderListener中的contextInitialized中发生了什么。
@Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }ContextLoaderListener中的内容很简单,就是调用了initWebApplicationContext这个方法,并把ServletContext的实现类( ApplicationContextFacade)传进去。initWebApplicationContext这个方法在ContextLoader这个类中,ContextLoaderListener继承了ContextLoader这个类,其实主要的调用逻辑都在ContextLoader这个类中。我们就进入到 initWebApplicationContext这个方法中,看看代码中是怎么写的(去掉了无关的代码):
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { //如果已经初始化过了,则抛出异常 在ServletContext的生命周期中,只能初始化一次 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!"); } Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring root WebApplicationContext"); long startTime = System.currentTimeMillis(); try { //如果context为空的话,则创建Spring上下文 因为这里有一个带WebApplicationContext参数的构造函数 if (this.context == null) { this.context = createWebApplicationContext(servletContext); } //如果Spring web上下文为可配置的web上下文 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 //在Spring上下文还未刷新完之前 ApplicationContext是通过调用refresh方法来进行bean的加载初始化装配等一系列动作 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); } } //把Spring中的web上下文放到ServletContext中, 在程序中可以通过ServletContext获取到Spring中的web上下文 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); //获取线程上下文加载器 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); //如果线程上下文加载器和当前类加载器是同一个类加载器,则当前上下文和Context是一个东西 if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } //如果不是同一个类加载器加载的,则把刚实例化的上下文放到currentContextPerThread中 key是线程上下文加载器 value是当前实例化的context else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } return this.context; } }假设我们的this.context为null(这里我们只考虑正常的一般场景),我们继续到 createWebApplicationContext方法中,看一下是怎么创建Spring web上下文的:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { //查找WebApplicationContext的实现类 Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {//获取到的Class必须是ConfigurableWebApplicationContext的子类 throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } //实例化WebApplicationContext的实现类 注意的是这个实现类一定是实现了ConfigurableWebApplicationContext的接口的 return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }上面的代码很简单:查找WebApplicationContext的实现类,然后通过反射的方式实例化。实例化的过程不用多说,我们看一下 determineContextClass这个方法。
protected Class<?> determineContextClass(ServletContext servletContext) { //从ServletContext中获取key为contextClass的配置信息 String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); //如果在web.xml中配置了WebApplicationContext的实现类,则获取配置的实现类的类信息 if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load custom context class [" + contextClassName + "]", ex); } } else { //取默认的配置信息 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( "Failed to load default context class [" + contextClassName + "]", ex); } } }从上面的代码中我们可以看出,我们可以配置在web.xml中配置WebApplicationContext的实现类,如果没有在web.xml中进行配置的话,则取默认的实现类。下面我们看一下是怎么去默认的实现类的:
在ContextLoader中有这样的一段代码:
static { //从类路径下获取ContextLoader.properties //Spring的Resource相关的类非常好用 try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); //加载ContextLoader.properties defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } }静态代码块,我们知道在类初始化的时候会执行静态代码块。所以在ContextLoader实例化之前已经加载过了ContextLoader.properties配置文件。我们看一下ContextLoader.properties中的内容:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext我们发现这里就一个配置项,默认配置了一个WebApplicationContext的实现类:XmlWebApplicationContext。所以如果我们没有在web.xml中进行配置的话,这里就会实例化XmlWebApplicationContext这个类。
下面我们回到initWebApplicationContext这个方法中继续分析。因为我们的Context是刚实例化的,而active的默认值是false,所以会进入到if中,同样cwac.getParent()==null成立,所以会执行loadParentContext()这个方法。我们看看这个方法中做了什么操作:
protected ApplicationContext loadParentContext(ServletContext servletContext) { ApplicationContext parentContext = null; //从ServletContext中加载配置项为locatorFactorySelector的信息 String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM); //从ServletContext中加载配置项为parentContextKey的新 String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM); //如果设置了parentContextKey的信息 if (parentContextKey != null) { // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml" //实例化一个单例的BeanFactoryLocator BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector); Log logger = LogFactory.getLog(ContextLoader.class); if (logger.isDebugEnabled()) { logger.debug("Getting parent context definition: using parent context key of '" + parentContextKey + "' with BeanFactoryLocator"); } //获取父上下文 this.parentContextRef = locator.useBeanFactory(parentContextKey); parentContext = (ApplicationContext) this.parentContextRef.getFactory(); } return parentContext; }loadParentContext主要做的就是从web.xml中加载父应用上下文。
下面就到了我们最重要的一个方法了configureAndRefreshWebApplicationContext了。接下来我们的分析也就是围绕这个类了:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { //这里判断wac有没有被初始化 设置过 if (ObjectUtils.identityToString(wac).equals(wac.getId())) { //如果ServletContext中配置了contextId,则获取配置的值,并设置为id的值 String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... //生成一个默认的ID值 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } } //把ServletContext设置到Spring的Web上下文中 wac.setServletContext(sc); //获取contextConfigLocation的值, 这个值就是我们的Spring的配置文件 String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); } // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(sc, null); } //定制化的上下文 customizeContext(sc, wac); //调用上下文的刷新的方法,这里就开始bean的加载初始化实例化装配等一系列的动作 wac.refresh(); }在上面的代码中我们分析一下 customizeContext这个方法,对于 wac.refresh();这个方法我们这里就先不展开了。因为这个方法是SpringIOC容器初始化的入口。内容太多了,我们以后在分析Spring的源码时再详细的说明。
//获取配置的初始化的类 List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(sc); for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) { Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) { throw new ApplicationContextException(String.format( "Could not apply context initializer [%s] since its generic parameter [%s] " + "is not assignable from the type of application context used by this " + "context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(), wac.getClass().getName())); } this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass)); } //进行排序操作 即上面得到的进行初始化的类 需要实现Ordered的接口或者配置@Order注解 AnnotationAwareOrderComparator.sort(this.contextInitializers); for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { //调用初始化方法 进行初始化 initializer.initialize(wac); } }下面我们看一下 determineContextInitializerClasses这个查找初始化类的方法
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> determineContextInitializerClasses(ServletContext servletContext) { List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes = new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>(); //从ServletContext中获取初始化参数为globalInitializerClasses的值 String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM); if (globalClassNames != null) { //进行字符串的分割 for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) { classes.add(loadInitializerClass(className)); } } //从ServletContext中获取初始化参数为contextInitializerClasses的值 String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM); if (localClassNames != null) { //进行字符串的分割, ; 空格 for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) { classes.add(loadInitializerClass(className)); } } return classes; }OK,到这里我们关于Spring Web上下文的分析就先到这里了。这里初始化了一个父的IOC容器。下篇文章我们接着分析DispatcherServlet的初始化过程。
其时序图如下:
资料:
线程上下文:
http://blog.csdn.net/zhoudaxia/article/details/35897057