概述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- [1] Spring配置 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 指定Spring Bean的配置文件所在目录。默认配置在WEB-INF目录下 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/applicationContext.xml</param-value>
</context-param>

<!-- [2] Spring MVC配置 -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 可以自定义servlet.xml配置文件���位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.xml,如spring-servlet.xml
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-servlet.xml</param-value> // 默认
</init-param>
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

[1] 和 [2] 分别会创建其对应的 Spring WebApplicationContext 容器,并且它们是父子容器的关系。

Root WebApplicationContext 容器

image.png

Root WebApplicationContext 容器的初始化,通过 ContextLoaderListener 来实现。在 Servlet 容器启动时,例如 Tomcat、Jetty 启动,则会被 ContextLoaderListener 监听到,从而调用 #contextInitialized(ServletContextEvent event) 方法,初始化 Root WebApplicationContext 容器。

ContextLoaderListener

org.springframework.web.context.ContextLoaderListener ,实现 ServletContextListener 接口,继承 ContextLoader 类,实现 Servlet 容器启动和关闭时,分别初始化和销毁 WebApplicationContext 容器。

构造方法

1
2
3
4
5
6
7
8
// ContextLoaderListener.java

public ContextLoaderListener() {
}

public ContextLoaderListener(WebApplicationContext context) {
super(context);
}

这两个构造方法,是因为父类 ContextLoader 有这两个构造方法,所以必须重新定义。

第二个构造方法,可以直接传递一个 WebApplicationContext 对象,那样,实际 ContextLoaderListener 就无需在创建一个新的 WebApplicationContext 对象。

contextInitialized

1
2
3
4
5
6
7
// ContextLoaderListener.java

@Override
public void contextInitialized(ServletContextEvent event) {
// 初始化 WebApplicationContext
initWebApplicationContext(event.getServletContext());
}

调用父类 ContextLoader 的 #initWebApplicationContext(ServletContext servletContext) 方法,初始化 WebApplicationContext 对象。

contextDestroyed

1
2
3
4
5
6
7
// ContextLoaderListener.java

@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}

销毁 WebApplicationContext 容器的逻辑。

ContextLoader

真正实现初始化和销毁 WebApplicationContext 容器的逻辑的类。

构造方法

因为 ContextLoader 的属性比较多,我们逐块来看。

第一块,defaultStrategies 静态属性,默认的配置 Properties 对象。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ContextLoader.java

/**
* 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";

/**
* 默认的配置 Properties 对象
*
* 从 {@link #DEFAULT_STRATEGIES_PATH} 中读取
*/
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 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());
}
}

从 ContextLoader.properties 中,读取默认的配置 Properties 对象。

1
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

这意味着什么呢?如果我们没有在  标签中,配置指定的 WebApplicationContext 类型,就使用 XmlWebApplicationContext 类。

第二块,context 属性,Root WebApplicationContext 对象。代码如下:

1
2
3
4
5
6
7
8
9
@Nullable
private WebApplicationContext context;

public ContextLoader() {
}

public ContextLoader(WebApplicationContext context) {
this.context = context;
}

在下文中,我们将会看到,如果 context 是直接传入,则不会进行初始化,重新创建

initWebApplicationContext

该初始化 WebApplicationContext 对象。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// <1> 若已经存在 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 对应的 WebApplicationContext 对象,则抛出 IllegalStateException 异常。
// 例如,在 web.xml 中存在多个 ContextLoader 。
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!");
}

// <2> 打印日志
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
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) {
// <3> 初始化 context ,即创建 context 对象
this.context = createWebApplicationContext(servletContext);
}
// <4> 如果是 ConfigurableWebApplicationContext 的子类,如果未刷新,则进行配置和刷新
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) { // <4.1> 未刷新( 激活 )
// 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) { // <4.2> 无父容器,则进行加载和设置。
// 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);
}
// <4.3> 配置 context 对象,并进行刷新
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// <5> 记录在 servletContext 中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

// <6> 记录到 currentContext 或 currentContextPerThread 中
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}

// <7> 打印日志
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}

// <8> 返回 context
return this.context;
} catch (RuntimeException | Error ex) {
// <9> 当发生异常,记录异常到 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 中,不再重新初始化。
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
  • 若已经存在 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 对应的 WebApplicationContext 对象,则抛出 IllegalStateException 异常
  • 打印日志
  • 调用 #createWebApplicationContext(ServletContext sc) 方法,初始化 context ,即创建 WebApplicationContext 对象
  • 如果 context 是 ConfigurableWebApplicationContext 的子类,如果未刷新,则进行配置和刷新。
    • 如果未未刷新( 激活 )。默认情况下,是符合这个条件的,所以会往下执行
    • 无父容器,则进行加载和设置。默认情况下,#loadParentContext(ServletContext servletContext) 方法,返回 null
  • 调用 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) 方法,配置 ConfigurableWebApplicationContext 对象,并进行刷新
  • 记录 context 在 ServletContext 中
  • 记录到 currentContext 或 currentContextPerThread 中,差异在于类加载器的不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ContextLoader.java

/**
* 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;
  • 打印日志
  • 返回 context
  • 当发生异常,记录异常到WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 中,不再重新初始化。对应第一处的逻辑

createWebApplicationContext

初始化 context ,即创建 WebApplicationContext 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
// ContextLoader.java

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// <1> 获得 context 的类
Class<?> contextClass = determineContextClass(sc);
// <2> 判断 context 的类,是否符合 ConfigurableWebApplicationContext 的类型
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// <3> 创建 context 的类的对象
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

determineContextClass(ServletContext servletContext)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static final String CONTEXT_CLASS_PARAM = "contextClass";

protected Class<?> determineContextClass(ServletContext servletContext) {
// 获得参数 contextClass 的值
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);
}
// 情况二,从 defaultStrategies 获得该类
} 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);
}
}
}
  • 前者,从 ServletContext 配置的 context 类
  • 后者,从 ContextLoader.properties 配置的 context 类。

configureAndRefreshWebApplicationContext

配置 ConfigurableWebApplicationContext 对象,并进行刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static final String CONTEXT_ID_PARAM = "contextId";

public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// <1> 如果 wac 使用了默认编号,则重新设置 id 属性
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
// 情况一,使用 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()));
}
}

// <2>设置 context 的 ServletContext 属性
wac.setServletContext(sc);
// <3> 设置 context 的配置文件地址
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}

// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
// <4> TODO 芋艿,暂时忽略
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}

// <5> 执行自定义初始化 context TODO 芋艿,暂时忽略
customizeContext(sc, wac);

// 刷新 context ,执行初始化
wac.refresh();
}
  • 如果 wac 使用了默认编号,则重新设置 id 属性。
    • 默认情况下,我们不会对 wac 设置编号,所以会执行进去
  • id 的生成规则,也分成使用contextId在标签中设置,和自动生成两种情况
    • 默认情况下,会走第二种情况
  • 设置 context 的配置文件地址,该文件地址就是在xml重配置的文件地址
1
2
3
4
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/applicationContext.xml</param-value>
</context-param>
  • 调用 #customizeContext(ServletContext sc,ConfigurableWebApplicationContext wac) 方法,执行自定义初始化 wac
  • 刷新 wac ,执行初始化

closeWebApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void closeWebApplicationContext(ServletContext servletContext) {
servletContext.log("Closing Spring root WebApplicationContext");
try {
// 关闭 context
if (this.context instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) this.context).close();
}
} finally {
// 移除 currentContext 或 currentContextPerThread
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);
}
}