tomcat与spring-web

发布于:2025-04-09 ⋅ 阅读:(82) ⋅ 点赞:(0)

SpringServletContainerInitializer

介绍

SpringServletContainerInitializer是spring-web jar包下的类,并且在jar包中的META-INF/services的特定文件中有如下配置:

org.springframework.web.SpringServletContainerInitializer

这是利用了tomcat的3.0特性,因为tomcat会扫描类路径上的每个jar包中的META-INF/services中的这个指定名称的文件,并且读取这个文件中的类,比如这里的SpringServletContainerInitializer,这个类必须实现ServletContainerInitializer接口。然后这个类会被实例化,并且回调这个类的onStartup方法,并且还可以通过@HandleTypes注解传入感兴趣的类的子类作为第一个参数,ServletContxt作为第二个参数。利用这个特性可以给ServletContainer容器配置功能。

源码

/**
 * 使用Servlet3.0的ServletContainerInitializer特性,可以使用基于java代码的方式配置servlet容器,而取代以前的web.xml
 * 的配置方式。SpringServletContainerInitializer通过@HandleTypes,传入WebApplicationInitializer的实现类,来实现
 * 对ServletContext初始化。也就是说,我们只要定义一个类实现WebApplicationInitializer这个接口,那么我们的这个类就能够
 * 拿到ServletContext,并且往ServletContext中添加三大组件。
 
 * Servlet 3.0 {@link ServletContainerInitializer} designed to support code-based
 * configuration of the servlet container using Spring's {@link WebApplicationInitializer}
 * SPI as opposed to (or possibly in combination with) the traditional
 * {@code web.xml}-based approach.
 *
 * 只要把spring-web 这个jar包放在类路径上,那么就会使用到tomcat的这个servlet 3.0的特性,不需要这个功能,
 * 那就移除这个jar包就行了。
 * 因为spring-web 包有指定的这个文件META-INF/services/javax.servlet.ServletContainerInitializer,
 * 
 *
 * <h2>Mechanism of Operation</h2>
 * This class will be loaded and instantiated and have its {@link #onStartup}
 * method invoked by any Servlet 3.0-compliant container during container startup assuming
 * that the {@code spring-web} module JAR is present on the classpath. This occurs through
 * the JAR Services API {@link ServiceLoader#load(Class)} method detecting the
 * {@code spring-web} module's {@code META-INF/services/javax.servlet.ServletContainerInitializer}
 * service provider configuration file. See the
 * <a href="http://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider">
 * JAR Services API documentation</a> as well as section <em>8.2.4</em> of the Servlet 3.0
 * Final Draft specification for complete details.
 *
 * 
 * <h3>In combination with {@code web.xml}</h3>
 * A web application can choose to limit the amount of classpath scanning the Servlet
 * container does at startup either through the {@code metadata-complete} attribute in
 * {@code web.xml}, which controls scanning for Servlet annotations or through an
 * {@code <absolute-ordering>} element also in {@code web.xml}, which controls which
 * web fragments (i.e. jars) are allowed to perform a {@code ServletContainerInitializer}
 * scan. When using this feature, the {@link SpringServletContainerInitializer}
 * can be enabled by adding "spring_web" to the list of named web fragments in
 * {@code web.xml} as follows:
 *
 * <pre class="code">
 * {@code
 * <absolute-ordering>
 *   <name>some_web_fragment</name>
 *   <name>spring_web</name>
 * </absolute-ordering>
 * }</pre>
 *
 * SpringServletContainerInitializer负责把用户定义的实现了WebApplicationInitializer接口的类实例化,并且把
 * ServletContext传给用户自定义实现了WebApplicationInitializer接口的类对象的void onStartup(ServletContext)
 * 方法,这样在tomcat初始化过程中,就可以配置servlet容器了。
 *
 * <h2>Relationship to Spring's {@code WebApplicationInitializer}</h2>
 * Spring's {@code WebApplicationInitializer} SPI consists of just one method:
 * {@link WebApplicationInitializer#onStartup(ServletContext)}. The signature is intentionally
 * quite similar to {@link ServletContainerInitializer#onStartup(Set, ServletContext)}:
 * simply put, {@code SpringServletContainerInitializer} is responsible for instantiating
 * and delegating the {@code ServletContext} to any user-defined
 * {@code WebApplicationInitializer} implementations. It is then the responsibility of
 * each {@code WebApplicationInitializer} to do the actual work of initializing the
 * {@code ServletContext}. The exact process of delegation is described in detail in the
 * {@link #onStartup onStartup} documentation below.
 *
 * 一般性建议: 这个类用于支持框架, 用户可以自定义实现了WebApplicationInitializer接口的类,放在类路径上
 *           来做定制化修改。
 * 
 * <h2>General Notes</h2>
 * In general, this class should be viewed as <em>supporting infrastructure</em> for
 * the more important and user-facing {@code WebApplicationInitializer} SPI. Taking
 * advantage of this container initializer is also completely <em>optional</em>: while
 * it is true that this initializer will be loaded and invoked under all Servlet 3.0+
 * runtimes, it remains the user's choice whether to make any
 * {@code WebApplicationInitializer} implementations available on the classpath. If no
 * {@code WebApplicationInitializer} types are detected, this container initializer will
 * have no effect.
 *
 * 这个容器初始化器,并没有要求一定要使用springmvc, 它允许你只要实现了 WebApplicationInitializer这个接口,那么
 * 你从这个接口的方法里拿到ServletContext后,可以做你任何想要添加的servlet, listener, or filter这些组件,并不一定
 * 是spring的组件。
 *
 * <p>Note that use of this container initializer and of {@code WebApplicationInitializer}
 * is not in any way "tied" to Spring MVC other than the fact that the types are shipped
 * in the {@code spring-web} module JAR. Rather, they can be considered general-purpose
 * in their ability to facilitate convenient code-based configuration of the
 * {@code ServletContext}. In other words, any servlet, listener, or filter may be
 * registered within a {@code WebApplicationInitializer}, not just Spring MVC-specific
 * components.
 *
 * <p>This class is neither designed for extension nor intended to be extended.
 * It should be considered an internal type, with {@code WebApplicationInitializer}
 * being the public-facing SPI.
 *
 * <h2>See Also</h2>
 * See {@link WebApplicationInitializer} Javadoc for examples and detailed usage
 * recommendations.<p>
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @author Rossen Stoyanchev
 * @since 3.1
 * @see #onStartup(Set, ServletContext)
 * @see WebApplicationInitializer
 */
@HandlesTypes(WebApplicationInitializer.class) // 感兴趣的类是 WebApplicationInitializer接口的子类
public class SpringServletContainerInitializer implements ServletContainerInitializer {

   @Override // classpath路径上的所有类只要是@HandlesTypes注解标注的类,都会传过来,即WebApplicationInitializer
   public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
         throws ServletException {

      List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

       // 把传过来的类实例化(不能被实例化的就不管了),添加到initializers集合中
      if (webAppInitializerClasses != null) {
         for (Class<?> waiClass : webAppInitializerClasses) {
            // Be defensive: Some servlet containers provide us with invalid classes,
            // no matter what @HandlesTypes says...
            if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                  WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
               try {
                  initializers.add((WebApplicationInitializer) waiClass.newInstance());
               }
               catch (Throwable ex) {
                  throw new ServletException("Failed to instantiate WebApplicationInitializer class", 
                                             ex);
               }
            }
         }
      }

      if (initializers.isEmpty()) {
         servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
         return;
      }

      servletContext.log(initializers.size() + 
                         " Spring WebApplicationInitializers detected on classpath");
       // 对这些initializers进行一个排序,排序可以通过实现Ordered接口,或者使用@Order注解
      AnnotationAwareOrderComparator.sort(initializers);
      for (WebApplicationInitializer initializer : initializers) {
         initializer.onStartup(servletContext);
      }
   }

}

WebApplicationInitializer

介绍

通过SpringServletContainerInitializer的介绍,我们知道了spring-web模块能够把类路径上实现了WebApplicationInitializer这个接口的类给例化,并且把ServletContext给传过来给onStartup方法,那么我们就可以自己往ServletContext中添加web的三大组建了。

我们先看下常规的web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
		  http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">


	<!-- 使用spring 监听器 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!-- Spring核心配置文件 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>
    
	<!-- 配置SpringMVC -->
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

</web-app>

在这里插入图片描述

源码

通过看上面的继承关系图,我们来看下它们的源码

WebApplicationInitializer接口
public interface WebApplicationInitializer {

   /**
    * 实现了这个接口的类可以往传过来的servletContext中注入servlets, filters, listeners三大组件
    * 这个接口方法的回调是由SpringServletContainerInitializer负责的
    *
    * Configure the given {@link ServletContext} with any servlets, filters, listeners
    * context-params and attributes necessary for initializing this web application. See
    * examples {@linkplain WebApplicationInitializer above}.
    * @param servletContext the {@code ServletContext} to initialize
    * @throws ServletException if any call against the given {@code ServletContext}
    * throws a {@code ServletException}
    */
   void onStartup(ServletContext servletContext) throws ServletException;

}
AbstractContextLoaderInitializer抽象类
/**
 * 这个类实现了WebApplicationInitializer,目的是往ServlvetContext中添加ContextLoaderListener,
 * 这跟我们在web.xml 中配置ContextLoaderListener监听器是一样的
 *
 * Convenient base class for {@link WebApplicationInitializer} implementations
 * that register a {@link ContextLoaderListener} in the servlet context.
 *
 * <p>The only method required to be implemented by subclasses is
 * {@link #createRootApplicationContext()}, which gets invoked from
 * {@link #registerContextLoaderListener(ServletContext)}.
 *
 * @author Arjen Poutsma
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.2
 */
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {

   /** Logger available to subclasses */
   protected final Log logger = LogFactory.getLog(getClass());


   /*
   	* 这个重写接口的方法负责使用servletContext注册ContextLoaderListener监听器
    */
   @Override
   public void onStartup(ServletContext servletContext) throws ServletException {
      registerContextLoaderListener(servletContext);
   }

   /**
    *
    * 1.创建spring容器,准确的说应该是spring的根容器,并且把这个spring的根容器设置给了ContextLoaderListener监听器,
    * 2.getRootApplicationContextInitializers()方法获取到ApplicationContextInitializer实例,这个在此处是可以
    *   在当前这个类的这个模板方法里自定义返回ApplicationContextInitializer实例的。这个对应到web.xml中做的如下配置
    *     <context-param>
    *        <param-name>contextInitializerClasses</param-name>
    *        <param-value>com.zzhua.initializer.MyApplicationContextInitializer</param-value>
    *     </context-param>
    *   这里返回的 ApplicationContextInitializer 实例用于对spring的根容器的修改,并且可以返回不止1个,
    *   可以使用@Order注解或者实现Order接口来做一个排序,按顺序对spring根容器修改。
    *   这个ApplicationContextInitializer的回调是发生在ContextLoader的
    *   configureAndRefreshWebApplicationContext()方法中调用customizeContext(sc, wac)方法时。
    *   
    * 
    * Register a {@link ContextLoaderListener} against the given servlet context. The
    * {@code ContextLoaderListener} is initialized with the application context returned
    * from the {@link #createRootApplicationContext()} template method.
    * @param servletContext the servlet context to register the listener against
    */
   protected void registerContextLoaderListener(ServletContext servletContext) {
      WebApplicationContext rootAppContext = createRootApplicationContext();
      if (rootAppContext != null) {
          // spring容器被设置给了ContextLoaderListener的父类ContextLoader的WebApplicationContext属性
          // 说明ContextLoaderListener是持有spring根容器的
         ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
         listener.setContextInitializers(getRootApplicationContextInitializers());
         servletContext.addListener(listener);
      }
      else {
         logger.debug("No ContextLoaderListener registered, as " +
               "createRootApplicationContext() did not return an application context");
      }
   }

   /**
    * Create the "<strong>root</strong>" application context to be provided to the
    * {@code ContextLoaderListener}.
    * <p>The returned context is delegated to
    * {@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)} and will
    * be established as the parent context for any {@code DispatcherServlet} application
    * contexts. As such, it typically contains middle-tier services, data sources, etc.
    * @return the root application context, or {@code null} if a root context is not
    * desired
    * @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
    */
   protected abstract WebApplicationContext createRootApplicationContext();

   /**
    * Specify application context initializers to be applied to the root application
    * context that the {@code ContextLoaderListener} is being created with.
    * @since 4.2
    * @see #createRootApplicationContext()
    * @see ContextLoaderListener#setContextInitializers
    */
   protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
      return null;
   }

}
AbstractDispatcherServletInitializer抽象类
/**
 * 
 *  这个类同样使用WebApplicationInitializer接口, 用于往ServletContext中注册 DispatcherServlet,
 *  并且对DispatcherServlet做相关的配置
 *
 * Base class for {@link org.springframework.web.WebApplicationInitializer}
 * implementations that register a {@link DispatcherServlet} in the servlet context.
 *
 * <p>Concrete implementations are required to implement
 * {@link #createServletApplicationContext()}, as well as {@link #getServletMappings()},
 * both of which get invoked from {@link #registerDispatcherServlet(ServletContext)}.
 * Further customization can be achieved by overriding
 * {@link #customizeRegistration(ServletRegistration.Dynamic)}.
 *
 * <p>Because this class extends from {@link AbstractContextLoaderInitializer}, concrete
 * implementations are also required to implement {@link #createRootApplicationContext()}
 * to set up a parent "<strong>root</strong>" application context. If a root context is
 * not desired, implementations can simply return {@code null} in the
 * {@code createRootApplicationContext()} implementation.
 *
 * @author Arjen Poutsma
 * @author Chris Beams
 * @author Rossen Stoyanchev
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @since 3.2
 */
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

   /**
    * The default servlet name. Can be customized by overriding {@link #getServletName}.
    */
   public static final String DEFAULT_SERVLET_NAME = "dispatcher";


   @Override
   public void onStartup(ServletContext servletContext) throws ServletException {
      // 先调用父类的方法,即先创建spring的父容器
      super.onStartup(servletContext);
      // 注册DispatcherServlet到ServletContext中,在此过程中,会创建spring容器,即spring子容器
      registerDispatcherServlet(servletContext);
   }

   /**
    *
    * 创建spring的web子容器, 创建方法是模板方法,交给子类实现,
    * new了一个DispatcherServlet,并且把上面创建的spring子容器传给了DispatcherServlet
    * 获取到ApplicationContextInitializer的实例,获取方法是模板方法。获取的这些实例将应用于初始化spring的web子容器
    *     获取实例的过程,对应于web.xml中的servlet标签下的如下配置
    *              <init-param>
    *                   <param-name>contextInitializerClasses</param-name>
    *                   <param-value>com.zzhua.initializer.MyAppContextInitializer,</param-value>
    *              </init-param>
    *     加上 下面这个配置也会对spring的web子容器初始化
    *     <context-param>
    *        <param-name>contextInitializerClasses</param-name>
    *        <param-value>com.zzhua.initializer.MyApplicationContextInitializer</param-value>
    *     </context-param>
    *     这些初始化的调用是在DispatcherServlet的父类FrameworkServlet中的
    *         configureAndRefreshWebApplicationContext方法调用applyInitializers(wac);时被调用
    *
    * Register a {@link DispatcherServlet} against the given servlet context.
    * <p>This method will create a {@code DispatcherServlet} with the name returned by
    * {@link #getServletName()}, initializing it with the application context returned
    * from {@link #createServletApplicationContext()}, and mapping it to the patterns
    * returned from {@link #getServletMappings()}.
    * <p>Further customization can be achieved by overriding {@link
    * #customizeRegistration(ServletRegistration.Dynamic)} or
    * {@link #createDispatcherServlet(WebApplicationContext)}.
    * @param servletContext the context to register the servlet against
    */
   protected void registerDispatcherServlet(ServletContext servletContext) {
      String servletName = getServletName();
      Assert.hasLength(servletName, "getServletName() must not return empty or null");

      // 创建一个spring的web容器, 这个创建方法是个模板方法,实现交给子类
      WebApplicationContext servletAppContext = createServletApplicationContext();
      Assert.notNull(servletAppContext,
            "createServletApplicationContext() did not return an application " +
            "context for servlet [" + servletName + "]");
      
       // 将创建的spring的web子容器作为创建DispatcherServlet的构造参数,从而创建DispatcherServlet,
       // 这说明,DispatcherServlet持有WebApplicationContext的,并且是在父类FrameWorkServlet中属性持有的
      FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
       // 将Initializers设置给dispatcherServlet,用于初始化spring的web子容器
      dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

       // 将DispatcherServlet注册给ServletContext
      ServletRegistration.Dynamic registration = servletContext.addServlet(servletName,
                                                                           dispatcherServlet);
      Assert.notNull(registration,
            "Failed to register servlet with name '" + servletName + "'." +
            "Check if there is another servlet registered under the same name.");

       // 立即初始化
      registration.setLoadOnStartup(1);
       // 设置前端控制器DispatcherServlet的映射路径
      registration.addMapping(getServletMappings());
       // 异步支持
      registration.setAsyncSupported(isAsyncSupported());

       // 获取到filter,获取方法时模板方法,
       // 这个filter只拦截的servlet-name就是前端控制器DispatcherServlet
      Filter[] filters = getServletFilters();
      if (!ObjectUtils.isEmpty(filters)) {
         for (Filter filter : filters) {
            registerServletFilter(servletContext, filter);
         }
      }
	  // 自定义registration方法, 模板方法,让用户继续自定义registration
      customizeRegistration(registration);
   }

   /**
    *
    * 默认就是: servletName
    *
    * Return the name under which the {@link DispatcherServlet} will be registered.
    * Defaults to {@link #DEFAULT_SERVLET_NAME}.
    * @see #registerDispatcherServlet(ServletContext)
    */
   protected String getServletName() {
      return DEFAULT_SERVLET_NAME;
   }

   /**
    * 模板方法,创建spring的web子容器的逻辑,交给子类来做
    *
    * Create a servlet application context to be provided to the {@code DispatcherServlet}.
    * <p>The returned context is delegated to Spring's
    * {@link DispatcherServlet#DispatcherServlet(WebApplicationContext)}. As such,
    * it typically contains controllers, view resolvers, locale resolvers, and other
    * web-related beans.
    * @see #registerDispatcherServlet(ServletContext)
    */
   protected abstract WebApplicationContext createServletApplicationContext();

   /**
    * DispatcherServlet中传了spring的web子容器,说明dispatcherServlet是持有spring的web子容器的
    * 这个子容器将传给前端控制器的父类FrameWorkServlet的webApplicationContext
    * 
    * Create a {@link DispatcherServlet} (or other kind of {@link FrameworkServlet}-derived
    * dispatcher) with the specified {@link WebApplicationContext}.
    * <p>Note: This allows for any {@link FrameworkServlet} subclass as of 4.2.3.
    * Previously, it insisted on returning a {@link DispatcherServlet} or subclass thereof.
    */
   protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
      return new DispatcherServlet(servletAppContext);
   }

   /**
    * 这里返回的初始化器,将应用于前端控制器创建时传入的spring的web容器的初始化
    * Specify application context initializers to be applied to the servlet-specific
    * application context that the {@code DispatcherServlet} is being created with.
    * @since 4.2
    * @see #createServletApplicationContext()
    * @see DispatcherServlet#setContextInitializers
    * @see #getRootApplicationContextInitializers()
    */
   protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
      return null;
   }

   /**
    * 设置前端控制器DispatcherServlet的处理请求的url-pattern
    *
    * Specify the servlet mapping(s) for the {@code DispatcherServlet} &mdash;
    * for example {@code "/"}, {@code "/app"}, etc.
    * @see #registerDispatcherServlet(ServletContext)
    */
   protected abstract String[] getServletMappings();

   /**
    * 指定拦截前端控制器DispatcherServlet的filter
    *
    * Specify filters to add and map to the {@code DispatcherServlet}.
    * @return an array of filters or {@code null}
    * @see #registerServletFilter(ServletContext, Filter)
    */
   protected Filter[] getServletFilters() {
      return null;
   }

   /**
    * Add the given filter to the ServletContext and map it to the
    * {@code DispatcherServlet} as follows:
    * <ul>
    * <li>a default filter name is chosen based on its concrete type
    * <li>the {@code asyncSupported} flag is set depending on the
    * return value of {@link #isAsyncSupported() asyncSupported}
    * <li>a filter mapping is created with dispatcher types {@code REQUEST},
    * {@code FORWARD}, {@code INCLUDE}, and conditionally {@code ASYNC} depending
    * on the return value of {@link #isAsyncSupported() asyncSupported}
    * </ul>
    * <p>If the above defaults are not suitable or insufficient, override this
    * method and register filters directly with the {@code ServletContext}.
    * @param servletContext the servlet context to register filters with
    * @param filter the filter to be registered
    * @return the filter registration
    */
   protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, 
                                                              Filter filter) {
       // 这里就是在往ServletContext注册Filter用于拦截前端控制器DispatcherServlet
      String filterName = Conventions.getVariableName(filter);
      Dynamic registration = servletContext.addFilter(filterName, filter);
      if (registration == null) {
         int counter = -1;
         while (counter == -1 || registration == null) {
            counter++;
            registration = servletContext.addFilter(filterName + "#" + counter, filter);
            Assert.isTrue(counter < 100,
                  "Failed to register filter '" + filter + "'." +
                  "Could the same Filter instance have been registered already?");
         }
      }
      registration.setAsyncSupported(isAsyncSupported());
      registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
      return registration;
   }

   private EnumSet<DispatcherType> getDispatcherTypes() {
      return (isAsyncSupported() ?
            EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, 
                       DispatcherType.INCLUDE, DispatcherType.ASYNC) :
            EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
   }

   /**
    * 异步支持
    * A single place to control the {@code asyncSupported} flag for the
    * {@code DispatcherServlet} and all filters added via {@link #getServletFilters()}.
    * <p>The default value is "true".
    */
   protected boolean isAsyncSupported() {
      return true;
   }

   /**
    * 留给用户自定义前端控制器的方法,
    * Optionally perform further registration customization once
    * {@link #registerDispatcherServlet(ServletContext)} has completed.
    * @param registration the {@code DispatcherServlet} registration to be customized
    * @see #registerDispatcherServlet(ServletContext)
    */
   protected void customizeRegistration(ServletRegistration.Dynamic registration) {
   }

}
AbstractAnnotationConfigDispatcherServletInitializer抽象类
/**
 * 
 * spring借助tomcat的3.0新特性SCI机制,提供的自动扫描 WebApplicationInitializer 接口实现类,
 * 得到ServletContext,从而注册Servlet、listener、filter等web组件。
 *
 * 使用AnnotationConfigWebApplicationContext支持使用@Configuration注解类作为spring容器的配置信息,创建spring容器
 *       servlet的spring的web子容器
 *       ServletContextListener的spring的根容器
 *
 * 作为这个类的具体实现,需要重写getRootConfigClasses()和getServletConfigClasses()方法,指定spring的配置类
 *
 * Base class for {@link org.springframework.web.WebApplicationInitializer}
 * implementations that register a
 * {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
 * configured with annotated classes, e.g. Spring's
 * {@link org.springframework.context.annotation.Configuration @Configuration} classes.
 *
 * <p>Concrete implementations are required to implement {@link #getRootConfigClasses()}
 * and {@link #getServletConfigClasses()} as well as {@link #getServletMappings()}.
 * Further template and customization methods are provided by
 * {@link AbstractDispatcherServletInitializer}.
 *
 * <p>This is the preferred approach for applications that use Java-based
 * Spring configuration.
 *
 * @author Arjen Poutsma
 * @author Chris Beams
 * @since 3.2
 */
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
      extends AbstractDispatcherServletInitializer {

   /**
    * 获取配置类创建spring的web子容器, 这里用的是 AnnotationConfigWebApplicationContext
    * {@inheritDoc}
    * <p>This implementation creates an {@link AnnotationConfigWebApplicationContext},
    * providing it the annotated classes returned by {@link #getRootConfigClasses()}.
    * Returns {@code null} if {@link #getRootConfigClasses()} returns {@code null}.
    */
   @Override
   protected WebApplicationContext createRootApplicationContext() {
       // 获取配置类
      Class<?>[] configClasses = getRootConfigClasses();
      if (!ObjectUtils.isEmpty(configClasses)) {
         AnnotationConfigWebApplicationContext 
                           rootAppContext = new AnnotationConfigWebApplicationContext();
          // 注册给spring的配置文件,但并未刷新
         rootAppContext.register(configClasses);
         return rootAppContext;
      }
      else {
         return null;
      }
   }

   /**
    * 获取配置类创建spring根容器,这里用的是 AnnotationConfigWebApplicationContext
    *
    * {@inheritDoc}
    * <p>This implementation creates an {@link AnnotationConfigWebApplicationContext},
    * providing it the annotated classes returned by {@link #getServletConfigClasses()}.
    */
   @Override
   protected WebApplicationContext createServletApplicationContext() {
      AnnotationConfigWebApplicationContext servletAppContext = new
                                                            AnnotationConfigWebApplicationContext();
       // 获取配置类
      Class<?>[] configClasses = getServletConfigClasses();
      if (!ObjectUtils.isEmpty(configClasses)) {
         // 把配置类注册给了spring容器,但并未刷新
         servletAppContext.register(configClasses);
      }
      return servletAppContext;
   }

   /**
    * 指定spring根容器使用的配置类,这个配置类需要使用@Configuration或者是@Component注解
    * Specify {@link org.springframework.context.annotation.Configuration @Configuration}
    * and/or {@link org.springframework.stereotype.Component @Component} classes to be
    * provided to the {@linkplain #createRootApplicationContext() root application context}.
    * @return the configuration classes for the root application context, or {@code null}
    * if creation and registration of a root context is not desired
    */
   protected abstract Class<?>[] getRootConfigClasses();

   /** 
    * 指定spring的web子容器使用的配置类,这个配置类需要使用@Configuration或者是@Component注解
    * Specify {@link org.springframework.context.annotation.Configuration @Configuration}
    * and/or {@link org.springframework.stereotype.Component @Component} classes to be
    * provided to the {@linkplain #createServletApplicationContext() dispatcher servlet
    * application context}.
    * @return the configuration classes for the dispatcher servlet application context or
    * {@code null} if all configuration is specified through root config classes.
    */
   protected abstract Class<?>[] getServletConfigClasses();

}

应用

从WebApplicationIntializer接口的继承体系和下面子类的实现来看,它主要是做好了准备工作,

  • 创建Spring的根容器(注意此时并没有刷新spring容器),并且设置到了ContextLoaderListener当中,并且把ContextLoaderListener注册到了ServletContext中,等待tomcat回调ServletContextListener的ContextInitialized方法时,再刷新spring容器
  • 创建Spring的web子容器(注意此时也未刷新该spring容器),并且设置到了DispatcherServlet当中,并且把DispatcherServlet注册到了ServletContext中,等待tomcat初始化Servlet时,回调servlet的init方法时,再刷新spring容器

那么 其实接下来就是要看tomcat回调ContextLoaderLisetener和DispatcherServlet的初始化方法,那么在初始化方法过程中,肯定就会被spring容器进行一个自定义,并且完成spring根容器和spring的web子容器的刷新。

示例:

RootConfig

@ComponentScan(basePackages = "com.zzhua",
               excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION
                                                        ,value = Controller.class)}
              )
public class RootConfig { // 配置根容器中的bean


}

ServletConfig

@ComponentScan(basePackages = "com.zzhua",
        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, 
                                                value = Controller.class)
                         },
        useDefaultFilters = false)
public class ServletConfig extends WebMvcConfigurerAdapter { // 配置spring的web子容器中的bean
    
}

MyWebApplicationInitializer

public class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    // 把这个配置类设置给了ContextLoaderListener的父类ContextLoader持有的webApplicationContxt属性的配置类。
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    // 把这个配置类设置给了DispacherServlet的父类FrameWorkServlet持有的webApplicationContxt属性的配置类。
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{ServletConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        
        // 我们可以顺便注册一下其它的一些需要的组件      
        //注册组件  ServletRegistration
        ServletRegistration.Dynamic servlet = servletContext.addServlet("userServlet",
                                                                        new HelloServlet());
        //配置servlet的映射信息
        servlet.addMapping("/hello1");

    }
}

WebApplicationContext

// Spring-web的WebApplicationContext继承了Spring的ApplicationContext, 
// 也就是说WebApplicationContext具有了spring容器的功能

public interface WebApplicationContext extends ApplicationContext {
    // 此处注意这个名字, 这个名字是创建了spring容器后, 将这个spring容器存到ServletContext的键,
    //                                           通过这个键能拿到spring根容器
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    String SCOPE_REQUEST = "request";
    String SCOPE_SESSION = "session";
    String SCOPE_APPLICATION = "application";
    String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
    String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
    String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

    @Nullable  // 从这个定义的方法, 可以看出WebApplicationContext(Spring容器)也能拿到SerlvetContext
    ServletContext getServletContext(); 
}

WebApplicationContextUtils

public abstract class WebApplicationContextUtils {
    // ...
	public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
        return getWebApplicationContext(
            		sc,                  // 从ServletContext中取出spring容器
            		WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE // 引用了上面这个键
        );
    }
    // ...
}
// 可以通过这种静态方法拿到spring容器, 所以只要能拿到ServletContext就能拿到Spring容器
// 源码可以参看ContextLoaderListener

ContextLoaderListener

ContextLoaderLisetener继承了ContextLoader,实现了ServletContextListener接口。ContextLoaderListener的大部分功能实现都在它的父类ContextLoader中实现,实现ServletContextListener接口,只是成为父类方法被tomcat容器调用的入口。

我们先看下ContextLoaderListener类

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    // 无参构造
	public ContextLoaderListener() {
	}

	// 传入web版的spring的容器
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}


    // 这个方法很重要,这个方法将会被tomcat在生命周期中回调,并且将ServletContext传给了父类处理
    // 我们不要忘了,我们把未刷新的spring容器放到了ContextLoaderListener中,并且保存在父类中,
    // 下面就要看父类是如何处理的
	/** 
	 * 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());
	}

}
ContextLoader
package org.springframework.web.context;

public class ContextLoader {

	/** 作为内部维护的web版spring容器在ServletContext中的contextId
	 * Config param for the root WebApplicationContext id,
	 * to be used as serialization id for the underlying BeanFactory: {@value}
	 */
	public static final String CONTEXT_ID_PARAM = "contextId";

	// spring容器刷新时用到的配置文件,我们应该还记得使用web.xml配置的时候,
    // 会给该监听器配置一个ContextConfigLocation,来让spring容器根据这个配置文件刷新
    /*
        <!-- 使用spring 监听器 -->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <!-- Spring核心配置文件 -->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
    */ // 这个context-param将会被tomcat读取到servletContext,而获取到servletContext后
    //    ,可以使用servletContext.getInitParameter("contextConfigLocation")获取到对应的值
    // 
	public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
    
    // 获取web版的spring容器的实现类
	/**
	 * Config param for the root WebApplicationContext implementation class to use: {@value}
	 * @see #determineContextClass(ServletContext)
	 */
	public static final String CONTEXT_CLASS_PARAM = "contextClass";

    // 用来定义初始化web版spring容器的initializer类(需要实现ApplicationContextInitializer接口),
    // 这些类将会被实例化, 并且调用initializer.initialize(wac)来初始化spring容器
	/**
	 * Config param for {@link ApplicationContextInitializer} classes to use
	 * for initializing the root web application context: {@value}
	 * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)
	 */
	public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";

    // 定义全局的用来初始化spring容器的ApplicationContextInitializer接口实现类
	/**
	 * Config param for global {@link ApplicationContextInitializer} classes to use
	 * for initializing all web application contexts in the current application: {@value}
	 * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)
	 */
	public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";

	/**
	 * Optional servlet context parameter (i.e., "{@code locatorFactorySelector}")
	 * used only when obtaining a parent context using the default implementation
	 * of {@link #loadParentContext(ServletContext servletContext)}.
	 * Specifies the 'selector' used in the
	 * {@link ContextSingletonBeanFactoryLocator#getInstance(String selector)}
	 * method call, which is used to obtain the BeanFactoryLocator instance from
	 * which the parent context is obtained.
	 * <p>The default is {@code classpath*:beanRefContext.xml},
	 * matching the default applied for the
	 * {@link ContextSingletonBeanFactoryLocator#getInstance()} method.
	 * Supplying the "parentContextKey" parameter is sufficient in this case.
	 */
	public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";

	/**
	 * Optional servlet context parameter (i.e., "{@code parentContextKey}")
	 * used only when obtaining a parent context using the default implementation
	 * of {@link #loadParentContext(ServletContext servletContext)}.
	 * Specifies the 'factoryKey' used in the
	 * {@link BeanFactoryLocator#useBeanFactory(String factoryKey)} method call,
	 * obtaining the parent application context from the BeanFactoryLocator instance.
	 * <p>Supplying this "parentContextKey" parameter is sufficient when relying
	 * on the default {@code classpath*:beanRefContext.xml} selector for
	 * candidate factory references.
	 */
	public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";

    // 定义可以使用的初始化参数的分隔符
	/**
	 * Any number of these characters are considered delimiters between
	 * multiple values in a single init-param String value.
	 */
	private static final String INIT_PARAM_DELIMITERS = ",; \t\n";

    // 在ContextLoader的同包下有ContextLoader.properties文件,里面定义了
    // org.springframework.web.context.WebApplicationContext\
    //  			=org.springframework.web.context.support.XmlWebApplicationContext
	/**
	 * Name of the class path resource (relative to the ContextLoader class)
	 * that defines ContextLoader's default strategy names.
	 */
	private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

	// 默认策略的配置从默认的策略路径文件中读取
	private static final Properties defaultStrategies;

	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {
            // 使用的是ClassPathResource来读取与ContextLoader同包下的ContextLoader.properties文件
			ClassPathResource resource = new ClassPathResource(
                DEFAULT_STRATEGIES_PATH, ContextLoader.class);
            // 使用PropertiesLoaderUtils的静态方法来读取resource
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': "
                                            + ex.getMessage());
		}
	}


	/**
	 * Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext.
	 */
	private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =
			new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);

	/**
	 * The 'current' WebApplicationContext, if the ContextLoader class is
	 * deployed in the web app ClassLoader itself.
	 */
	private static volatile WebApplicationContext currentContext;


    // 从这里我们可以看到ContextLoader维护的spring容器这个属性
    // ,我们可以理解为,把spring容器存储到了tomcat的监听器中
	/**
	 * The root WebApplicationContext instance that this loader manages.
	 */
	private WebApplicationContext context;

	/**
	 * Holds BeanFactoryReference when loading parent factory via
	 * ContextSingletonBeanFactoryLocator.
	 */
	private BeanFactoryReference parentContextRef;

    // contextInitializer将会被维护起来,它们将会用来初始化spring容器
	/** Actual ApplicationContextInitializer instances to apply to the context */
	private final 
        List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
			new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();


	// 无参构造方法
	public ContextLoader() {
	}

	// 有参构造方法,
	public ContextLoader(WebApplicationContext context) {
		this.context = context;
	}
	
    // 添加到ContextLoader维护的List<ApplicationContextInitializer>集合中
	@SuppressWarnings("unchecked")
	public void setContextInitializers(ApplicationContextInitializer<?>... initializers) {
		if (initializers != null) {
			for (ApplicationContextInitializer<?> initializer : initializers) {
				this.contextInitializers.add
                    ((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
			}
		}
	}

    
	// 这是个很重要的方法, 
	// 如果在构造阶段传入了spring容器,那么就初始化它;
    // 如果没有,那么就使用配置的contextClass以及contextConfigLocation来创建并刷新spring容器;
    
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        // spring的根容器都会放到ServletContext中并且以下面这个东西为键,并且只能放1个
		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");
		}
        // 记录开始时间
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
                // 如果刚开始构造当前监听器的时候,并没有传入容器,那么就根据默认的策略创建spring容器
				this.context = createWebApplicationContext(servletContext);
			}
            
            // 到这里,this.context就一定有值了
            
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext 
                    			cwac = (ConfigurableWebApplicationContext) this.context;
                // isActive表示spring容器被刷新了,并且还没有关闭
				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) {
                        // 如果context没有父容器,那么就从servletContext中的loadParentContext方法中弄出来一个
						// 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);
					}
                    // 给this.context找完父容器之后,开始配置并且刷新spring容器:this.context
                    // 这是个非常关键的方法,spring容器刷新是个非常重要的阶段
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
            
            // spring容器刷新之后,将spring容器存到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;
		}
		catch (RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
		catch (Error err) {
			logger.error("Context initialization failed", err);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
			throw err;
		}
	}

	// 创建容器的方法,先从servletContext中配置的contextClass参数反射创建spring容器
	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);
	}

	// 确定contextClass
	protected Class<?> determineContextClass(ServletContext servletContext) {
        
        // 先从ServletContext中查找配置,配置找不到,那就使用默认策略的(ContextLoader.properteis配置的)
		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);
			}
		}
	}

	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, 
                                                            ServletContext sc) {
        
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // spring容器的默认id就是 ObjectUtils.identityToString(this),
            // 如果它没有被改变,那就根据已有的信息,做个修改
            
            // 首先看servletContext有没有配置contextId, 不然的话就设置一个默认的
			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容器中保存了servletContext,
        //               而servletContext也通过(域)一个键保存了spring容器
		wac.setServletContext(sc);
        
        // spring容器刷新前需要设置配置文件,首先看servletContext中有没有配置contextConfigLocation
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}

		// spring容器持有一个environment对象,environment对象有两个功能,一个是解析属性,二是对bean逻辑分组(profile)
        // 这里获取environment的逻辑也简单,第一次获取,就创建并设置到spring容器的属性里
        // ,这里创建的是StandardServletEnvironment,
        //  environment主要持有了一个propertySource的集合以及使用包装了propertySource的集合
        //                                                  的PropertySourcesPropertyResolver对象来作解析
		ConfigurableEnvironment env = wac.getEnvironment();
        
		if (env instanceof ConfigurableWebEnvironment) {
            // 将servletContext包装成ServletContextPropertySource设置到env的propertySource集合中
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		
        // 刷新spring容器之前的最后一步
        // 在这一步,我们可以将initializer配置给servletContext,
        // 那么在这里就会获取到所有配置的initializer(ApplicationInitializer)对象,(servletContext+属性上的)
        // 逐个对sprin根容器进行初始化,注意这些initializer对象是可以设置顺序的(@Order注解或者实现Order接口)
        // 比如: 我们这个时候就可以往spring容器中注入我们的组件了,这是一个很好的时机
		customizeContext(sc, wac);
        
        // 准备工作都做好了,开始刷新spring容器
		wac.refresh();
	}

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

		AnnotationAwareOrderComparator.sort(this.contextInitializers);
		for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : 
                                                                 this.contextInitializers) {
			initializer.initialize(wac);
		}
	}

	protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
			determineContextInitializerClasses(ServletContext servletContext) {

		List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
				new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();

		String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
		if (globalClassNames != null) {
			for (String className : StringUtils.tokenizeToStringArray(globalClassNames, 
                                                                      INIT_PARAM_DELIMITERS)) {
				classes.add(loadInitializerClass(className));
			}
		}

		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;
	}

	@SuppressWarnings("unchecked")
	private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {
		try {
			Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
			if (!ApplicationContextInitializer.class.isAssignableFrom(clazz)) {
				throw new ApplicationContextException(
						"Initializer class does not implement ApplicationContextInitializer interface: " + clazz);
			}
			return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;
		}
		catch (ClassNotFoundException ex) {
			throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
		}
	}

	/**
	 * Template method with default implementation (which may be overridden by a
	 * subclass), to load or obtain an ApplicationContext instance which will be
	 * used as the parent context of the root WebApplicationContext. If the
	 * return value from the method is null, no parent context is set.
	 * <p>The main reason to load a parent context here is to allow multiple root
	 * web application contexts to all be children of a shared EAR context, or
	 * alternately to also share the same parent context that is visible to
	 * EJBs. For pure web applications, there is usually no need to worry about
	 * having a parent context to the root web application context.
	 * <p>The default implementation uses
	 * {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator},
	 * configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and
	 * {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context
	 * which will be shared by all other users of ContextsingletonBeanFactoryLocator
	 * which also use the same configuration parameters.
	 * @param servletContext current servlet context
	 * @return the parent application context, or {@code null} if none
	 * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator
	 */
	protected ApplicationContext loadParentContext(ServletContext servletContext) {
		ApplicationContext parentContext = null;
		String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
		String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);

		if (parentContextKey != null) {
			// locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
			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;
	}

	/**
	 * Close Spring's web application context for the given servlet context. If
	 * the default {@link #loadParentContext(ServletContext)} implementation,
	 * which uses ContextSingletonBeanFactoryLocator, has loaded any shared
	 * parent context, release one reference to that shared parent context.
	 * <p>If overriding {@link #loadParentContext(ServletContext)}, you may have
	 * to override this method as well.
	 * @param servletContext the ServletContext that the WebApplicationContext runs in
	 */
	public void closeWebApplicationContext(ServletContext servletContext) {
		servletContext.log("Closing Spring root WebApplicationContext");
		try {
			if (this.context instanceof ConfigurableWebApplicationContext) {
				((ConfigurableWebApplicationContext) this.context).close();
			}
		}
		finally {
			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = null;
			}
			else if (ccl != null) {
				currentContextPerThread.remove(ccl);
			}
			servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
			if (this.parentContextRef != null) {
				this.parentContextRef.release();
			}
		}
	}


	/**
	 * Obtain the Spring root web application context for the current thread
	 * (i.e. for the current thread's context ClassLoader, which needs to be
	 * the web application's ClassLoader).
	 * @return the current root web application context, or {@code null}
	 * if none found
	 * @see org.springframework.web.context.support.SpringBeanAutowiringSupport
	 */
	public static WebApplicationContext getCurrentWebApplicationContext() {
		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		if (ccl != null) {
			WebApplicationContext ccpt = currentContextPerThread.get(ccl);
			if (ccpt != null) {
				return ccpt;
			}
		}
		return currentContext;
	}

}

DispatcherServlet

框架源码学习收藏汇总


网站公告

今日签到

点亮在社区的每一天
去签到