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.xml和spring-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());
}
}