Spring-MVC 源码分析(一):ContextLoaderListener 初始化

 

ContextLoaderListener这个监听器是启动根IoC容器并把它载入到Web容器的主要功能模块,也是整个Spring Web应用加载IoC的第一个地方。


为了方便在Web环境中使用IoC容器,Spring为Web应用提供了上下文的扩展接口WebApplicationContext来满足启动过程的需要,结构图如下:


  这个常量用于在ServletContext中存取根上下文
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

通过getServlectContext()方法可以得到当前Web容器的Servlet上下文环境,通过这个方法,相当于提供了一个Web容器级别的全局环境。
在启动过程中,Spring 会使用一个默认的 WebApplicationContext实现作为IoC容器,就是 XmlWebApplicationContext

XmlWebApplicationContext类的结构图


 
 /** 
      *   这里是设置默认BeanDefinition的地方,如果不特殊指定其他文件,IoC容器会从/WEB-INF/applicationContext.xml文件
      *   读取BeanDefinition来初始化IoC容器。
      * */
     public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

     /** Default prefix for building a config location for a namespace */
     public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";

     /** Default suffix for building a config location for a namespace */
     public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

     /**
      * 这里是取得Resource位置的地方,使用默认的配置位/WEB-INF/applicationContext.xml。
      */
     @Override
     protected String[] getDefaultConfigLocations() {
          if (getNamespace() != null) {
              return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
          }
          else {
              return new String[] {DEFAULT_CONFIG_LOCATION};
          }
     }

     /**
      * 这个加载过程在容器refresh()时启动
      */
     @Override
     protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
          // Create a new XmlBeanDefinitionReader for the given BeanFactory.
          // 对于XmlWebApplicationContext,当然是使用XmlBeanDefinitionReader来对BeanDefinition信息进行解析。
          XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
       
          // 因为XmlWebApplicationContext是DefaultResourceLoader的子类,
          // 所以这里同样使用DefaultResourceLoader来定位BeanDefinition。
          beanDefinitionReader.setResourceLoader(this);
          beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
       
          // 钩子,允许子类自定义XmlBeanDefinitionReader的属性配置
          initBeanDefinitionReader(beanDefinitionReader);
          //这里使用定义好的XmlBeanDefinitionReader来载入BeanDefinition
          loadBeanDefinitions(beanDefinitionReader);
     }
     
    
          /**
      * 如果有多个BeanDefinition文件的定义,需逐个载入,都是通过reader来完成的
      * 这个初始化过程是由refreshBeanFactory方法来完成的,这里只负责载入BeanDefinition。
      */
     protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
          String[] configLocations = getConfigLocations();
          if (configLocations != null) {
              for (String configLocation : configLocations) {
                   reader.loadBeanDefinitions(configLocation);
              }
          }
     }
 


 
public interface ServletContextListener extends EventListener {
    //服务器启动的时候初始化入口    
    public void contextInitialized ( ServletContextEvent sce );
     //服务器销毁的时候销毁入口
    public void contextDestroyed ( ServletContextEvent sce );
}
 
ContextLoader 类是用来建立WEB环境的根上下文环境

第一步:
ContextLoaderListener 类的contextInitialized方法实现

 /**
      * Initialize the root web application context.
      */
     public void contextInitialized(ServletContextEvent event) {
           //这个方法实现的本意是提供一个占位符方法createContextLoader()给子类机会创建客户化的环境加载,
                //但是,后来这个证明不是非常有用的,已经鼓励不再使用了,事实上,子类可以通过重写本方法达到同样的目的  
          this.contextLoader = createContextLoader();
           //实际上是为了使用超类的默认实现           
          if (this.contextLoader == null) {
              this.contextLoader = this;
          }
           //调用超类的加载根共享Web应用程序环境的默认实现  
          this.contextLoader.initWebApplicationContext(event.getServletContext());
     }
 
第二步:
ContextLoader 类的 initWebApplicationContext(ServletContext servletContext)代码

 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
           //如果已经存在了根共享Web应用程序环境,则抛出异常提示客户  
          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");
          if (logger.isInfoEnabled()) {
              logger.info("Root WebApplicationContext: initialization started");
          }
          //记录创建根Web应用程序环境的开始时间  
          long startTime = System.currentTimeMillis();

          try {
               //决定根Web应用程序环境是否存在父应用程序环境,一般是返回null
              ApplicationContext parent = loadParentContext(servletContext);

               //创建根Web应用程序环境,如果父环境存在则引用父环境,通常情况下父环境是不存在的 即parent=null
                    // 这是一个比较重要的方法,在第三步说明
              this.context = createWebApplicationContext(servletContext, parent);
               //把创建的根Web应用程序环境保存到Servlet环境中,每个派遣器Servlet加载的子环境会应用这个环境作为父环境  
               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) {
                      //如果线程和本类拥有不同的类加载器,则使用线程的类加载器作为关键在保存在一个映射对象里,保证析构时能拿到Web应用程序环境进行关闭操作  
                   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;
          }
          catch (RuntimeException ex) {
              logger.error("Context initialization failed", ex);
               //如果产生任何异常,则保存异常对象到Servlet环境里  
               servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
              throw ex;
          }
          catch (Error err) {
              logger.error("Context initialization failed", err);
               //如果产生任何错误,则保存错误对象到Servlet环境里  
               servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
              throw err;
          }
     }
 

第三步:
ContextLoader 类的 createWebApplicationContext(ServletContext, ApplicationContext) 代码

 protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
          //检测是否有配置的Web应用程序环境类(即存放bean的容器类),如果没有配置,则使用缺省的类XmlWebApplicationContext 
              // 例如在web.xml中配置 
              //  <context-param>
            //       <param-name>contextClass</param-name>
            //       <param-value>org.springframework.web.context.support.GenericWebApplicationContext</param-value>
              //  </context-param> 
          Class<?> contextClass = determineContextClass(sc);
          
          //如果配置的Web应用程序环境类不是可配置的Web应用程序环境的子类,则抛出异常,停止初始化
              //若按上面的配置,这里会抛出异常,因为不是ConfigurableWebApplicationContext的子类 
          if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
              throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                        "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
          }
           //实例化Web应用程序环境类 
          ConfigurableWebApplicationContext wac =
                   (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

          //设置Web应用程序环境的ID 
          if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
              //如果 Servlet规范 <= 2.4,则使用web.xml里定义的应用程序名字定义Web应用程序名 
              String servletContextName = sc.getServletContextName();
               wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(servletContextName));
          }
          else {
               // 如果Servlet规范是 2.5, 则使用配置的ContextPath定义Web应用程序名  
              try {
                   String contextPath = (String) ServletContext.class.getMethod("getContextPath").invoke(sc);
                   wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                             ObjectUtils.getDisplayString(contextPath));
              }
              catch (Exception ex) {
                   throw new IllegalStateException("Failed to invoke Servlet 2.5 getContextPath method", ex);
              }
          }
          
           //如果父环境存在,则引用使用父环境  
          wac.setParent(parent);
          //保存Servlet环境  
          wac.setServletContext(sc);
          //设置根环境XML文件存放的位置
              //这里如果以下配置容器启动不会报错,但若不配置,则会去寻找/WEB-INF/applicationContext.xml,若该文件不存在就则抛出FileNotFound异常
              //<context-param>
              //     <param-name>contextConfigLocation</param-name>
              //     <param-value></param-value>
              //</context-param>  
          wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));
          //提供子类可互换Web应用程序环境的机会 占位符方法
          customizeContext(sc, wac);          //刷新Web应用程序环境以加载Bean定义,这里才是把我们XML里定义的bean放入容器的时候
          wac.refresh();
          return wac;
     }
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值