美文网首页
Spring WebApplicationContext

Spring WebApplicationContext

作者: Yakecan_Lee | 来源:发表于2018-06-17 22:05 被阅读0次

    Spring WebApplicationContext

    基于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容器的初始化流程等等.

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

    相关文章

      网友评论

          本文标题:Spring WebApplicationContext

          本文链接:https://www.haomeiwen.com/subject/ilkgeftx.html