SpringMVC源码之:根容器的创建

118 阅读5分钟

1. 从web.xml说起

我们在进行SSM整合的时候,肯定都在web.xml文件中写过如下配置:

<web-app>
    
    <!-- 给ServletContext设置的参数,用于指定Spring配置文件的位置 -->
    <!-- 如果没有指定,则默认配置文件为/WEB-INF/applicationContext.xml -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>
    
    <!-- 配置ContextLoaderListener监听器;在tomcat启动时,该监听器会读取上面指定的Spring配置文件,然后创建IOC容器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 配置DispatcherServlet;当该Servlet初始化时,会读取spring-mvc.xml文件并创建另一个IOC容器 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        
        <!-- DispatcherServlet的初始化参数,用于指定SpringMVC配置文件的位置 -->
        <!-- 如果没有指定,则默认配置文件为/WEB-INF/${servlet-name}-servlet.xml,即/WEB-INF/springmvc-servlet.xml -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        
        <!-- 让该DispatcherServlet在tomcat启动完成后立刻创建(而不是等到第一次接收到请求时才创建) -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <!-- Servlet映射;这个没什么好说的 -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

可以看到,这里我们是配置了两个IOC容器的,它们分别是通过spring.xmlspring-mvc.xml文件创建出来的
为了方便描述,我们将这两个IOC容器分别称为根容器Mvc容器
根容器在创建完成后会被放到ServletContext中,而Mvc容器会从ServletContext中获取根容器,并将其设置为父容器
我们知道,子容器(即这里的Mvc容器)在获取Bean时,如果获取不到,则会从父容器(即这里的根容器)中获取
这也是为什么在spring-mvc.xml文件中只配置了Controller,而Mvc容器却能为这些Controller注入Service的原因

2. XmlWebApplicationContext

根容器和Mvc容器在默认情况下都是XmlWebApplicationContext类型;该类的祖先类如下所示:

AbstractApplicationContext
    |-- AbstractRefreshableApplicationContext
            |-- AbstractRefreshableConfigApplicationContext
                    |-- AbstractRefreshableWebApplicationContext
                            |-- XmlWebApplicationContext

其中,AbstractApplicationContext已经完成了ApplicationContext的大部分功能,子类只需维护底层的BeanFactory即可

2.1. AbstractRefreshableApplicationContext

/**
 * 抽象的可重复刷新的ApplicationContext,允许多次调用refresh()方法
 * 本类使用DefaultListableBeanFactory作为底层的Bean工厂;每次刷新都会创建一个新的DefaultListableBeanFactory
 * 子类需要实现loadBeanDefinitions()方法,以便于在刷新时将BeanDefinition加载到DefaultListableBeanFactory中
 */
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {

    /**
     * 是否允许BeanDefinition覆盖;这里省略了该字段的set方法
     * 在刷新底层BeanFactory时,如果该值不为null,则该值会被设置给BeanFactory(默认为true)
     * 该配置主要是在有多个配置文件时起作用;如果原先的某个Bean不符合要求,那么可以通过新增配置文件的方式来重写该Bean的定义信息
     */
    @Nullable
    private Boolean allowBeanDefinitionOverriding;

    /**
     * 是否允许循环依赖;这里省略了该字段的set方法
     * 在刷新底层BeanFactory时,如果该值不为null,则该值会被设置给BeanFactory(默认为true)
     */
    @Nullable
    private Boolean allowCircularReferences;

    /**
     * 底层的BeanFactory
     */
    @Nullable
    private volatile DefaultListableBeanFactory beanFactory;

    /**
     * 重写父类的抽象方法:刷新底层的BeanFactory
     */
    @Override
    protected final void refreshBeanFactory() throws BeansException {
        
        // 如果this.beanFactory不为null,则先关闭它
        if (hasBeanFactory()) {
            destroyBeans();     // 调用BeanFactory的destroySingletons()方法
            closeBeanFactory(); // 主要是将this.beanFactory置为null
        }
        
        // 创建一个新的DefaultListableBeanFactory,并将其赋给this.beanFactory
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory); // 定制BeanFactory
            loadBeanDefinitions(beanFactory);  // 将Bean的定义信息加载到BeanFactory中
            this.beanFactory = beanFactory;
        } catch (IOException ex) {
            throw new ApplicationContextException("IO error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

    /**
     * 定制BeanFactory:如果allowBeanDefinitionOverriding/allowCircularReferences字段不为null,则将其设置给BeanFactory
     */
    protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
        if (this.allowBeanDefinitionOverriding != null) {
            beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.allowCircularReferences != null) {
            beanFactory.setAllowCircularReferences(this.allowCircularReferences);
        }
    }

    /**
     * 将Bean的定义信息加载到BeanFactory中;该功能一般是委派给若干个BeanDefinitionReader来实现的
     */
    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 
            throws BeansException, IOException;
}

2.2. AbstractRefreshableConfigApplicationContext

/**
 * 本类继承自AbstractRefreshableApplicationContext
 * 由于父类已经为本类解决了底层BeanFactory的维护问题,因此本类只需关心BeanDefinition的加载
 * 但本类并没有实现BeanDefinition的加载逻辑,而是把注意力放在了xml配置文件的来源上
 * 子类只需通过本类的方法来获取配置文件的路径,就可以读取配置文件,从而加载BeanDefinition了
 * 
 * 值得注意的是,本类还实现了BeanNameAware和InitializingBean接口
 * 一般来说,ApplicationContext应该不会被配置成Bean,因此实现这两个接口可能意义不大
 * 个人猜测,这么做是为了:允许我们将Mvc容器配置成根容器的一个Bean,由根容器(而不是DispatcherServlet)来维护Mvc容器的生命周期
 */
public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
        implements BeanNameAware, InitializingBean {

    /**
     * 用户自己指定的配置文件路径;如果为null,则采用默认配置文件路径
     */
    @Nullable
    private String[] configLocations;

    /**
     * 用户是否手动调用了本类的setId()方法
     */
    private boolean setIdCalled = false;

    /**
     * 设置id,同时将setIdCalled置为true
     */
    @Override
    public void setId(String id) {
        super.setId(id);
        this.setIdCalled = true;
    }

    /**
     * 实现BeanNameAware接口的setBeanName()方法
     * 默认会将Bean名称作为ApplicationContext的id设置进来(优先级低于手动设置的id)
     * 当我们把该ApplicationContext配置成Bean、且没有为其指定id时,本方法就能为该Bean设置一个默认的id
     */
    @Override
    public void setBeanName(String name) {
        if (!this.setIdCalled) {
            super.setId(name);
            setDisplayName("ApplicationContext '" + name + "'");
        }
    }

    /**
     * 实现InitializingBean接口的afterPropertiesSet()方法
     * 如果在构造方法中没有刷新本容器,那么在本容器的Bean初始化完成后,将会自动刷新本容器
     */
    @Override
    public void afterPropertiesSet() {
        if (!isActive()) {
            refresh();
        }
    }

    /**
     * 设置配置文件的路径;可以设置多个,每个路径用逗号、分号、空格、制表符或换行符隔开
     * 本方法适用于无法配置字符串数组的情况,比如init-param标签就只允许给目标字段设置字符串类型的值
     * 本方法间接地实现了ConfigurableWebApplicationContext的setConfigLocation()方法
     */
    public void setConfigLocation(String location) {
        setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
    }

    /**
     * 设置配置文件的路径;路径中可以包含占位符,这里会对占位符进行解析
     * 本方法间接地实现了ConfigurableWebApplicationContext的setConfigLocations()方法
     */
    public void setConfigLocations(@Nullable String... locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        } else {
            this.configLocations = null;
        }
    }

    /**
     * 获取配置文件的路径;如果没有手动设置过,则返回默认配置文件路径
     * 这里返回的路径允许有通配符;子类会通过ResourcePatternResolver组件来解析通配符
     */
    @Nullable
    protected String[] getConfigLocations() {
        return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
    }

    /**
     * 获取默认的配置文件路径
     * 默认返回null,这意味着用户必须手动指定配置文件的位置,或者重写本方法
     */
    @Nullable
    protected String[] getDefaultConfigLocations() {
        return null;
    }
}

2.3. AbstractRefreshableWebApplicationContext

/**
 * 本类在AbstractRefreshableConfigApplicationContext的基础上,添加了与web相关的支持
 * 本类默认会将configLocations中的路径解释为Servlet上下文中的资源,也就是说,相对路径是web应用的根目录
 * 如果configLocations中的路径是绝对路径,则需要以"file:"开头;DefaultResourceLoader会对这些前缀进行处理
 * 
 * 和AbstractApplicationContext类似,本类也会自动在Bean工厂中获取指定的组件
 * 本类通过"themeSource"名称来从Bean工厂中获取ThemeSource组件
 */
public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext
        implements ConfigurableWebApplicationContext, ThemeSource {

    /**
     * Servlet上下文
     */
    @Nullable
    private ServletContext servletContext;

    /**
     * Servlet配置
     */
    @Nullable
    private ServletConfig servletConfig;

    /**
     * 命名空间;如果为null,说明是根容器;命名空间决定了默认配置文件的路径
     * 假如根容器有两个子容器,那么这两个子容器就代表两个不同的命名空间,它们的默认配置文件也不相同
     */
    @Nullable
    private String namespace;

    /**
     * ApplicationContext容器中的ThemeSource组件;这个组件可以先不管
     */
    @Nullable
    private ThemeSource themeSource;
    
    // 省略以上字段的set/get方法,以及其它的简单方法

    /**
     * 重写AbstractApplicationContext中的方法,返回StandardServletEnvironment实例而不是StandardEnvironment实例
     * 
     * StandardServletEnvironment的构造方法会调用customizePropertySources()方法,给底层的属性源集合新增两个占位属性源:
     * 1. new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)
     * 2. new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)
     * 记住这两个占位属性源,后文中会提到它们
     */
    @Override
    protected ConfigurableEnvironment createEnvironment() {
        return new StandardServletEnvironment();
    }

    /**
     * 重写AbstractApplicationContext中的方法,对BeanFactory进行后置处理
     */
    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        
        // 注册ServletContextAwareProcessor后置处理器,负责调用ServletContextAware/ServletConfigAware接口相应的set方法
        beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
        
        // 忽略ServletContextAware/ServletConfigAware接口依赖
        beanFactory.ignoreDependencyInterface(ServletContextAware.class);
        beanFactory.ignoreDependencyInterface(ServletConfigAware.class);

        // 该方法主要做两件事:
        // 1. 注册Scope,包括request/session/application这3个作用域
        // 2. 将RequestObjectFactory/ResponseObjectFactory/SessionObjectFactory等实例注册为可解析的依赖
        WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
        
        // 往Bean工厂中注册servletContext/servletConfig/contextParameters/contextAttributes组件
        WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
    }

    /**
     * 根据路径加载资源
     */
    @Override
    protected Resource getResourceByPath(String path) {
        Assert.state(this.servletContext != null, "No ServletContext available");
        return new ServletContextResource(this.servletContext, path);
    }

    /**
     * 获取通配符解析器
     */
    @Override
    protected ResourcePatternResolver getResourcePatternResolver() {
        return new ServletContextResourcePatternResolver(this);
    }

    /**
     * 底层Bean工厂刷新完成时,初始化this.themeSource
     */
    @Override
    protected void onRefresh() {
        this.themeSource = UiApplicationContextUtils.initThemeSource(this);
    }

    /**
     * 重写AbstractApplicationContext中的方法,在底层BeanFactory刷新前,将占位属性源替换成真正的属性源
     */
    @Override
    protected void initPropertySources() {
        ConfigurableEnvironment env = getEnvironment();
        
        // 如果底层环境是ConfigurableWebEnvironment类型,则替换属性源
        // 这里主要是将上述说的两个占位属性源替换成ServletContextPropertySource/ServletConfigPropertySource
        // 这两个属性源主要用于封装ServletContext/ServletConfig中的初始化参数(initParameters)
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, this.servletConfig);
        }
    }
}

2.4. XmlWebApplicationContext

/**
 * 本类不是抽象类,实现了上面3个父类中的抽象方法
 * 本类和GenericXmlApplicationContext类似,只不过底层环境用的是web环境
 * 默认地,根容器的配置文件是/WEB-INF/applicationContext.xml,非根容器的配置文件是/WEB-INF/${namespace}.xml
 */
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {

    /**
     * 根容器的默认配置文件
     */
    public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

    /**
     * 有namespace的容器的默认配置文件的前缀
     */
    public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";

    /**
     * 有namespace的容器的默认配置文件的后缀
     */
    public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

    /**
     * 加载Bean的定义信息;这里是通过XmlBeanDefinitionReader组件来实现的
     */
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // 配置XmlBeanDefinitionReader
        beanDefinitionReader.setEnvironment(getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // 钩子方法,默认为空实现
        // 该方法允许子类对XmlBeanDefinitionReader进行进一步的配置
        // 比如:关闭XML校验,或者底层使用其它的XmlBeanDefinitionParser实现类
        initBeanDefinitionReader(beanDefinitionReader);
        
        // 真正开始加载Bean定义信息
        loadBeanDefinitions(beanDefinitionReader);
    }

    /**
     * 加载BeanDefinition
     */
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
        
        // 获取所有的配置文件;如果不为null,则依次解析这些配置文件
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            for (String configLocation : configLocations) {
                reader.loadBeanDefinitions(configLocation);
            }
        }
    }

    /**
     * 获取默认的配置文件;如果没有手动指定配置文件的位置,则会调用该方法获取默认配置文件
     * 这里会判断是否有namespace,如果是,则返回/WEB-INF/${namespace}.xml,否则返回/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};
        }
    }
}

3. ContextLoader

ContextLoaderListener继承自ContextLoader,因此先来看ContextLoader组件

3.1. 概述

/**
 * 负责初始化根容器,主要被ContextLoaderListener调用
 * 
 * 会从web.xml中读取"contextClass"上下文参数来获取根容器的类型(必须是ConfigurableWebApplicationContext类型)
 * 如果没有指定容器类型,则默认采用XmlWebApplicationContext
 * 
 * 还会读取"contextConfigLocation"上下文参数来获取配置文件路径,并将配置文件路径设置给根容器
 * 在将配置文件路径传给容器之前,会先用逗号、空格等分隔符将该参数的值切分成多个配置文件路径
 * 同时,配置文件路径中可以包含通配符,如:"/WEB-INF/*Context.xml,/WEB-INF/spring*.xml"
 * 如果没有指定配置文件路径,则默认采用"/WEB-INF/applicationContext.xml"
 * 
 * 此外,本类还提供了钩子方法loadParentContext(),允许我们在根容器初始化完成后,给该容器指定父容器
 * 
 * 从Spring3.1开始,本类支持通过构造方法直接注入WebApplicationContext,而无需自己通过读取web.xml来创建根容器
 * 这是为了支持Servlet3.0规范;Servlet3.0中,可以通过编程的方式来进行web配置,而无需web.xml文件
 */
public class ContextLoader {

    /**
     * 根容器的id对应的参数名称
     */
    public static final String CONTEXT_ID_PARAM = "contextId";

    /**
     * 根容器的配置文件路径对应的参数名称
     */
    public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

    /**
     * 根容器的类型对应的参数名称
     */
    public static final String CONTEXT_CLASS_PARAM = "contextClass";

    /**
     * 根容器的初始化器对应的参数名称
     * 可以通过该参数来指定ApplicationContextInitializer的实现类的全限定类名,可以有多个,每个类名用分隔符隔开
     * 本类在创建根容器后,会实例化这些ApplicationContextInitializer组件,并用这些组件对根容器进行初始化
     */
    public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";

    /**
     * 全局的容器初始化器对应的参数名称
     * 全局的容器初始化器可以用于初始化所有的IOC容器,而不仅仅是根容器
     */
    public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";

    /**
     * 配置多个参数值时,可选的分割符
     */
    private static final String INIT_PARAM_DELIMITERS = ",; \t\n";

    /**
     * 本类的配置文件的路径(相对于本类所在路径)
     * 该文件只有一行配置,它指定了默认的IOC容器为XmlWebApplicationContext类型
     */
    private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

    /**
     * 加载本类时,读取本类的配置文件,从而获取默认的IOC容器类型
     */
    private static final Properties defaultStrategies;
    static {
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
            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<>(1);

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

3.2. 简单方法

public class ContextLoader {

    /**
     * 本ContextLoader管理的根容器;可以是本类创建的,也可以是外界提供的
     */
    @Nullable
    private WebApplicationContext context;

    /**
     * 用户手动指定的ApplicationContextInitializer组件,会和web.xml配置文件中的ApplicationContextInitializer一同生效
     */
    private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers = 
            new ArrayList<>();

    /**
     * 无参构造器
     * 如果是通过web.xml文件来配置ContextLoaderListener的,则会通过该构造器来实例化ContextLoader
     * 如果ContextLoader是通过该构造器创建的,则会自动创建根容器
     * 创建好的根容器会作为一个属性注册到ServletContext中,属性名称为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
     */
    public ContextLoader() {
    }

    /**
     * 通过构造器直接注入根容器
     * 该构造器一般在Servlet3.0中使用,此时可以调用ServletContext的addListener()方法来注册ContextLoaderListener
     * 这个根容器也会作为一个属性注册到ServletContext中,属性名称为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
     * 
     * 如果传入的根容器是ConfigurableWebApplicationContext类型且还未刷新(自动创建的根容器也同理),那么会执行如下操作:
     * 1. 如果该容器没有设置id,则设置id
     * 2. 将ServletContext和ServletConfig设置给该容器
     * 3. 调用customizeContext()方法
     * 4. 执行ApplicationContextInitializer并刷新容器
     * 如果根容器不是ConfigurableWebApplicationContext类型或已刷新,则不执行上面的操作(默认此时的根容器已经初始化好了)
     */
    public ContextLoader(WebApplicationContext context) {
        this.context = context;
    }

    /**
     * 手动添加ApplicationContextInitializer组件
     */
    public void setContextInitializers(@Nullable ApplicationContextInitializer<?>... initializers) {
        if (initializers != null) {
            for (ApplicationContextInitializer<?> initializer : initializers) {
                this.contextInitializers
                        .add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
            }
        }
    }
}

3.3. initWebApplicationContext()

public class ContextLoader {
    
    /**
     * 根据ServletContext来初始化根容器;一般是在tomcat启动时调用;这里省略了打印日志和记录时间的代码
     */
    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!");
        }

        try {
            // 如果this.context为null,说明是this是通过无参构造器创建的,此时先实例化根容器
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            
            // 判断根容器是否为ConfigurableWebApplicationContext类型且未刷新,如果是,则初始化根容器并刷新
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) {
                    
                    // 如果根容器没有父容器,则加载父容器(可以为null)并将该父容器设置给根容器
                    if (cwac.getParent() == null) {
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    
                    // 初始化并刷新根容器
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            
            // 将根容器注册到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);
            }
            return this.context;
        
        } catch (RuntimeException | Error ex) {
            
            // 初始化失败则将异常对象放到ServletContext中,注意这里和根容器用的是同一个属性名称
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
    }

    /**
     * 获取根容器的父容器,目前没有任何子类重写该方法,因此可以将该方法看作固定返回null
     * 
     * 本方法的主要目的是:
     * 1. 允许多个web根容器成为某个共享的EAR上下文的子容器
     * 2. 或者让这些web根容器共享同一个(EJB可以访问到的)父容器
     * 对于纯web应用,一般不用担心web根容器有父容器
     */
    @Nullable
    protected ApplicationContext loadParentContext(ServletContext servletContext) {
        return null;
    }
}

3.4. createWebApplicationContext()

public class ContextLoader {
    
    /**
     * 实例化根容器;该方法固定创建ConfigurableWebApplicationContext类型的容器,子类可以重写该方法
     */
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        
        // 推断根容器的类型
        Class<?> contextClass = determineContextClass(sc);
        
        // 必须是ConfigurableWebApplicationContext类型
        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) {

        // 先读取web.xml中的配置,如果有,则直接返回相应的类型
        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);
            }
        
        // 否则,获取默认配置的根容器类型(即XmlWebApplicationContext)
        } 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);
            }
        }
    }
}

3.5. configureAndRefreshWebApplicationContext()

public class ContextLoader {

    /**
     * 初始化并刷新根容器
     */
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {

        // AbstractApplicationContext在构造时,会将id属性初始化为:ObjectUtils.identityToString(this)
        // 因此,这里先判断一下根容器的id是否为默认值,如果是,则将id设置成一个更有意义的值
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {

            // 如果在web.xml中配置了id,则使用该id,否则自动生成
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                wac.setId(idParam);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        // 将ServletContext设置到根容器中
        wac.setServletContext(sc);

        // 如果指定了配置文件的路径,则设置配置文件的路径;如果没有指定,则根容器将会读取默认配置文件
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

        // 注意,此时容器还没有刷新
        // 容器在刷新时,才会将占位属性源替换成真正的属性源
        // 而这里则提前执行了这一步操作,以确保ServletContext属性源已就绪
        // 这是因为在刷新前的某些后置处理或初始化操作可能会去读取ServletContext中的属性值
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
        }

        // 自定义配置根容器并刷新
        customizeContext(sc, wac);
        wac.refresh();
    }

    /**
     * 自定义配置根容器;在设置了配置文件路径之后、容器刷新之前调用
     */
    protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {

        // 读取web.xml,获取到用户指定的ApplicationContextInitializer组件的类型
        // determineContextInitializerClasses()方法会依次将全局初始化器和根容器初始化器放到List集合中
        List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
                determineContextInitializerClasses(sc);

        // 实例化ApplicationContextInitializer并将其放入this.contextInitializers集合
        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);
        }
    }
}

3.6. 其它方法

public class ContextLoader {

    /**
     * 关闭根容器,一般是在tomcat关闭时调用
     * 如果重写了loadParentContext()方法,那么一般也要重写本方法来关闭父容器
     */
    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中移除
            servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        }
    }

    /**
     * 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).
     */
    @Nullable
    public static WebApplicationContext getCurrentWebApplicationContext() {
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl != null) {
            WebApplicationContext ccpt = currentContextPerThread.get(ccl);
            if (ccpt != null) {
                return ccpt;
            }
        }
        return currentContext;
    }
}

4. ContextLoaderListener

/**
 * 监听tomcat的启动与关闭
 * 在tomcat启动时,启动根容器;在tomcat关闭时,关闭根容器
 * 本类继承自ContextLoader,通过调用父类中的方法来启动/关闭根容器
 */
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    /**
     * web.xml方式实例化ContextLoaderListener
     */
    public ContextLoaderListener() {
    }

    /**
     * Servlet3.0方式实例化ContextLoaderListener
     */
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    /**
     * tomcat启动时调用initWebApplicationContext()方法初始化根容器
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

    /**
     * tomcat关闭时调用closeWebApplicationContext()方法关闭根容器
     */
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        
        // 程序运行过程中,Spring可能会往ServletContext中存放一些Bean
        // 因此在tomcat关闭时,需要销毁掉Spring在ServletContext中存放的Bean
        // 这里会获取所有名称以"org.springframework."开头的属性,如果属性值是DisposableBean类型,则调用其destroy()方法
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}