Spring WebApplicationContext 侵立删

转自:http://www.spring4all.com/article/1267


基于Spring4.0.5

ContextLoaderListener继承了ContextLoader同时实现了ServletContextListener接口,Spring通过ContextLoaderListener在ServletContext的两个生命周期函数contextInitialized()contextDestroyed()WebApplicationContext进行创建和销毁.

/**
 * Initialize the root web application context.
 */
@Override
public void contextInitialized(ServletContextEvent event) {
   initWebApplicationContext(event.getServletContext());
}


/**
 * Close the root web application context.
 */
@Override
public void contextDestroyed(ServletContextEvent event) {
   closeWebApplicationContext(event.getServletContext());
   ContextCleanupListener.cleanupAttributes(event.getServletContext());
}

initWebApplicationContext()是创建WebApplicationContext的核心函数.首先在ServletContext里通过WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE这个key尝试在ServletContext里获取WebApplicationContext,若成功获取则代表已经进行过初始化,所以会抛出一个IllegalStateException.

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!");
}

接下来是获得WebApplicationContext的实例并保存本地引用和放入ServletContext中.由于WebApplicationContext只是一个接口,实际上我们获取的是此接口的实现类的实例,而具体是获取什么实现类的逻辑则放在了createWebApplicationContext()中.先继续分析本方法的逻辑,在获得context实例后从第7行到第21行都是根据相关判断对context进行一些初始化工作.这里提一下,从第12行到17行的代码,对于单纯的Web应用来讲是不需要关心的,这个是针对EAR的设置,如果不是EJB开发,这段可以忽略掉,在loadParentContext()的注释里有说明这一点.然后重点在第18行的configureAndRefreshWebApplicationContext(),这里是对WebApplicationContext进行初始化的核心函数,我们稍后分析.这段逻辑结束后,把已经初始化结束的WebApplicationContext放入了servletContext中,key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE.

然后在第23行到第29行,这里是为了解决一个Spring类库的位置问题.在Tomcat中,假设你的Web应用都是使用了Spring,而且又都是同一个版本,那你可以把jar包放在Tomcat目录的lib文件夹里,Tomcat的Common ClassLoader在默认配置下会读取并加载此文件夹里的jar包,从而使所有的Web应用都能使用这些类库,详细的Tomcat加载类库的内容可以去搜索相关资料查阅,这里不再多提.回到原题,如果是这种情况,Spring的类是由Common ClassLoader加载的,但是具体的Web应用的类却是由Tomcat实例为每个Web应用创建的ClassLoader加载的.根据JVM ClassLoader的双亲委派模型,由Common ClassLoader载入的Spring类是无法获得由WebApp ClassLoader载入的相关应用类的.所以这里Spring使用了一个线程上下文ClassLoader,确保不管Spring类库放在Web应用下还是放在Tomcat的lib下,都能成功获取相关Web应用的业务类.在Web应用里,线程上下文ClassLoader在默认情况下就是Web应用的ClassLoader,自然能够获得Web应用下的各种类.然后到此WebApplicationContext的创建过程结束.

try {
   // Store context in local instance variable, to guarantee that
   // it is available on ServletContext shutdown.
   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);
   }

   if (logger.isDebugEnabled()) {
      logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
   }
   if (logger.isInfoEnabled()) {
      long elapsedTime = System.currentTimeMillis() - startTime;
      logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
   }

   return this.context;
}

接下来我们先分析createWebApplicationContext()函数,这里的逻辑很简答,通过determineContextClass()获得contextClass并且判断是否为ConfigurableWebApplicationContext的实现类,是则返回,否则抛出异常.而在determineContextClass()里,先判断开发者是否有设置contextClass参数,有的话根据参数使用自定义的上下文类,否则就使用默认的上下文类.目前Spring是通过在ContextLoader.properties里设置默认的上下文类,此文件包含在org.springframework.web.context包下,设置了XmlWebApplicationContext为默认的上下文类.而ContextLoader.properties的载入代码则是被写在ContextLoader的静态代码块中.

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
   Class<?> contextClass = determineContextClass(sc);
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
            "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
   }
   return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        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);
            }
        }
    }

接下来我们分析初始化WebApplicationContext的核心函数configureAndRefreshWebApplicationContext(),从第2行到第14行是对上下文的id作一个设置,比较简单.然后第16行到20行,设置应用上下文类的ServletContext并根据contextConfigLocation参数设置应用上下文的ConfigLocation,如果没设置的话,根据默认配置,XmlWebApplicationContextConfigLocation/WEB-INF/applicationContext.xml,所以为什么大家默认都把Spring的配置文件这样命名并放在WEB-INF下就能解释了.在设置ConfigLocation的同时还设置好了Environment,例如Java自带的systemPropertiessystemEnvironment.然后第24行到28行对EnvironmentPropertiesSources进行初始化,这里是设置了ServletContextEnvironmentpropertiesSourcesList里.然后customizeContext()里面主要是执行配置了的ApplicationContextInitializerinitialize(),对ApplicationContext进行一些自定义的操作,在Web应用下自然是WebApplicationContext.(这里提一下,单纯的SpringMVC貌似这里没有任何作用,如果开发者自己没有编写ApplicationContextInitializer的话,但是在SpringBoot的类库里则有用到这个特性.)

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
   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()));
      }
   }

   wac.setServletContext(sc);
   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);
   wac.refresh();
}

接下来重头戏来了,refresh()里面才是真正通过配置进行Spring IOC容器的初始化和加载代码,包括了BeanFactory的创建,BeanFactoryPostProcesserBeanPostProcesser的注册和执行(这两个我的理解是一个hook,对BeanFactoryBean的创建执行进行自定义操作),然后还有下面各种MessageSource,EventMuticaster,Listener的初始化,最后是单例Bean的初始化和refresh()的一些收尾工作,这里面初始化了LifecycleProcessor,这个Spring有提供一个默认的实现类,用于管理有生命周期的Bean,单例的则在前面已经被生成了并且不需要做声明周期的管理,所以这里如果开发者有需要还可以自定义LifecycleProcessor来管理Bean的声明周期.然后发布ContextRefreshedEvent,最后还有个LiveBeansView.registerApplicationContext(this)貌似是提供查看当前应用上下文里存活的Bean的.当前版本这个功能还处于beta状态,是设计给Spring Tool Suite 3.1及更高版本使用的.

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         onRefresh();

         // Check for listener beans and register them.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }
   }
}

到这里,WebApplicationContext的创建和初始化就结束了,至于销毁部分就不详细解释了,代码比较少(其实是我懒= =).源码阅读完后收获还是挺大的,了解了Spring IOC容器是怎么切入到Web应用中,并且有提供了哪些地方可以hook进行自定义操作的,还有Spring IOC容器的初始化流程等等.

阅读源码过程中查阅了不少网上的资料,包括各种个人博客以及官方文档,因为自己没有做记录也就没罗列出来.本文如有不妥之处,希望大家能提出宝贵的意见.


猜你喜欢

转载自blog.csdn.net/qq_22167989/article/details/80904069