概述

在开始之前,我们还是回过头看一眼 web.xml 的配置。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>

Servlet WebApplicationContext 容器的初始化,是在 DispatcherServlet 初始化的过程中执行。

类图如下:
image.png

  • HttpServletBean ,负责将 ServletConfig 设置到当前 Servlet 对象中
  • FrameworkServlet ,负责初始化 Spring Servlet WebApplicationContext 容器
  • DispatcherServlet ,负责初始化 Spring MVC的各个组件,以及处理客户端的请求。

如何调试

执行 DispatcherServletTests#configuredDispatcherServlets()

HttpServletBean

实现 EnvironmentCapable、EnvironmentAware 接口,继承 HttpServlet 抽象类,负责将 ServletConfig 集成到 Spring 中。当然,HttpServletBean 自身也是一个抽象类。

构造方法

1
2
3
4
5
6
7
8
9
10
11
// HttpServletBean.java

@Nullable
private ConfigurableEnvironment environment;

/**
* 必须配置的属性的集合
*
* 在 {@link ServletConfigPropertyValues} 中,会校验是否有对应的属性
*/
private final Set<String> requiredProperties = new HashSet<>(4);

environment 属性,相关的方法,代码如下:

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

// setting 方法
@Override // 实现自 EnvironmentAware 接口,自动注入
public void setEnvironment(Environment environment) {
Assert.isInstanceOf(ConfigurableEnvironment.class, environment, "ConfigurableEnvironment required");
this.environment = (ConfigurableEnvironment) environment;
}

// getting 方法
@Override // 实现自 EnvironmentCapable 接口
public ConfigurableEnvironment getEnvironment() {
// 如果 environment 为空,主动创建
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}

protected ConfigurableEnvironment createEnvironment() {
return new StandardServletEnvironment();
}

Aware接口,实现了对environment的感知

requiredProperties 属性,必须配置的属性的集合

1
2
3
protected final void addRequiredProperty(String property) {
this.requiredProperties.add(property);
}

init

负责将 ServletConfig 设置到当前 Servlet 对象中

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
public final void init() throws ServletException {
// Set bean properties from init parameters.
// <1> 解析 <init-param /> 标签,封装到 PropertyValues pvs 中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// <2.1> 将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
// <2.2> 注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
// <2.3> 空实现,留给子类覆盖
initBeanWrapper(bw);
// <2.4> 以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中
bw.setPropertyValues(pvs, true);
} catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}

// Let subclasses do whatever initialization they like.
// <3> 子类来实现,实现自定义的初始化逻辑。目前,有具体的代码实现。
initServletBean();
}
  • 解析 Servlet 配置的  标签,封装到 PropertyValues pvs 中。其中,ServletConfigPropertyValues 是 HttpServletBean 的私有静态类,继承 MutablePropertyValues 类,ServletConfig 的 PropertyValues 封装实现类。
  • 将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中。简单来说,BeanWrapper 是 Spring 提供的一个用来操作 Java Bean 属性的工具,使用它可以直接修改一个对象的属性。
  • 注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析
  • 以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中,技设置到当前 Servlet 对象中。
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
private static class ServletConfigPropertyValues extends MutablePropertyValues {

/**
* Create new ServletConfigPropertyValues.
* @param config the ServletConfig we'll use to take PropertyValues from
* @param requiredProperties set of property names we need, where
* we can't accept default values
* @throws ServletException if any required properties are missing
*/
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
// 获得缺失的属性的集合
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);

// <1> 遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues 中,并从 missingProps 移除
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
// 添加到 ServletConfigPropertyValues 中
addPropertyValue(new PropertyValue(property, value));
// 从 missingProps 中移除
if (missingProps != null) {
missingProps.remove(property);
}
}

// Fail if we are still missing properties.
// <2> 如果存在缺失的属性,抛出 ServletException 异常
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}

}
  • 遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues
  • 判断要求的属性是否齐全。如果不齐全,则抛出 ServletException 异常

举个例子,验证psv注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<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>


// 经过2.4处的逻辑处理,会反射设置到 FrameworkServlet.contextConfigLocation 属性,没看懂。
@Nullable
private String contextConfigLocation;

public void setContextConfigLocation(@Nullable String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}

FrameworkServlet

该文件,实现 ApplicationContextAware 接口,继承 HttpServletBean 抽象类,负责初始化 Spring Servlet WebApplicationContext 容器。同时,FrameworkServlet 自身也是一个抽象类。

部分的关键属性

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

/**
* WebApplicationContext implementation class to create.
*
* 创建的 WebApplicationContext 类型
*/
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

/**
* Explicit context config location.
*
* 配置文件的地址
*/
@Nullable
private String contextConfigLocation;

/**
* WebApplicationContext for this servlet.
*
* WebApplicationContext 对象
*/
@Nullable
private WebApplicationContext webApplicationContext;
  • contextClass 属性,创建的 WebApplicationContext 类型,默认为 DEFAULT_CONTEXT_CLASS
1
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
  • contextConfigLocation 属性,配置文件的地址。例如:/WEB-INF/spring-servlet.xml
  • webApplicationContext 属性,WebApplicationContext 对象,即本文的关键,Servlet WebApplicationContext 容器。

四种方式进行“创建”webApplicationContext

  • 1 通过构造方法,代码如下
1
2
3
4
5
// FrameworkServlet.java

public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
  • 2 因为实现 ApplicationContextAware 接口,也可以 Spring 注入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* If the WebApplicationContext was injected via {@link #setApplicationContext}.
*
* 标记 {@link #webApplicationContext} 属性,是否通过 {@link #setApplicationContext(ApplicationContext)} 方法进行注入
*/
private boolean webApplicationContextInjected = false;

@Override
public void setApplicationContext(ApplicationContext applicationContext) {
if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
this.webApplicationContext = (WebApplicationContext) applicationContext;
this.webApplicationContextInjected = true;
}
}
  • 3 findWebApplicationContext()
  • 4 createWebApplicationContext(WebApplicationContext parent)

initServletBean

进一步初始化当前 Servlet 对象。实际上,重心在初始化 Servlet 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
@Override
protected final void initServletBean() throws ServletException {
// 打日志
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}

// 记录开始时间
long startTime = System.currentTimeMillis();

try {
// 初始化 WebApplicationContext 对象
this.webApplicationContext = initWebApplicationContext();
// 空实现。子类有需要,可以实现该方法,实现自定义逻辑
initFrameworkServlet();
} catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}

// 打日志
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}

// 打日志
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}

initWebApplicationContext

初始化 Servlet 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
protected WebApplicationContext initWebApplicationContext() {
// <1> 获得根 WebApplicationContext 对象
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

// <2> 获得 WebApplicationContext wac 变量
WebApplicationContext wac = null;
// 第一种情况,如果构造方法已经传入 webApplicationContext 属性,则直接使用
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
// 赋值给 wac 变量
wac = this.webApplicationContext;
// 如果是 ConfigurableWebApplicationContext 类型,并且未激活,则进行初始化
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) { // 未激活
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
// 设置 wac 的父 context 为 rootContext 对象
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
// 配置和初始化 wac
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 第二种情况,从 ServletContext 获取对应的 WebApplicationContext 对象
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
// 第三种,创建一个 WebApplicationContext 对象
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}

// <3> 如果未触发刷新事件,则主动触发刷新事件
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}

// <4> 将 context 设置到 ServletContext 中
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}

return wac;
}
  • 获得 WebApplicationContext wac 变量
    • 情况一
    • 如果构造方法已经传入 webApplicationContext 属性,则直接使用
      • 实际上,就是Servlet WebApplicationContext 容器的第一、二种方式。
    • 实际上,这块代码和ContextLoader#initWebApplicationContext(ServletContext servletContext)的中间段是一样的。除了#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 的具体实现代码不同。
    • 情况二
      • Servlet WebApplicationContext 容器的第三种方式
      • 如果此处 wac 还是为空,则调用 #findWebApplicationContext() 方法,从 ServletContext 获取对应的 WebApplicationContext 对象。
    • 情况三
      • 如果此处 wac 还是为空,则调用 #createWebApplicationContext(WebApplicationContext parent) 方法,创建一个 WebApplicationContext 对象。
  • 如果未触发刷新事件,则调用 #onRefresh(ApplicationContext context) 主动触发刷新事件
  • 如果 publishContext 为 true 时,则将 context 设置到 ServletContext 中

情况二,代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Nullable
private String contextAttribute;
@Nullable
public String getContextAttribute() {
return this.contextAttribute;
}

@Nullable
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
// 需要配置了 contextAttribute 属性下,才会去查找
if (attrName == null) {
return null;
}
// 从 ServletContext 中,获得属性名对应的 WebApplicationContext 对象
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
// 如果不存在,则抛出 IllegalStateException 异常
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}

情况三,代码

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
/**
* WebApplicationContext implementation class to create.
*
* 创建的 WebApplicationContext 类型
*/
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
public Class<?> getContextClass() {
return this.contextClass;
}

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// <a> 获得 context 的类
Class<?> contextClass = getContextClass();
// 如果非 ConfigurableWebApplicationContext 类型,抛出 ApplicationContextException 异常
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// <b> 创建 context 类的对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

// <c> 设置 environment、parent、configLocation 属性
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}

// <d> 配置和初始化 wac
configureAndRefreshWebApplicationContext(wac);

return wac;
}

configureAndRefreshWebApplicationContext

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
// FrameworkServlet.java

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// <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 属性
if (this.contextId != null) {
wac.setId(this.contextId);
// 情况二,自动生成
} else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}

// <2> 设置 wac 的 servletContext、servletConfig、namespace 属性
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());

// <3> 添加监听器 SourceFilteringListener 到 wac 中
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

// <4> TODO 芋艿,暂时忽略
// 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
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}

// <5> 执行处理完 WebApplicationContext 后的逻辑。目前是个空方法,暂无任何实现
postProcessWebApplicationContext(wac);

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

// <7> 刷新 wac ,从而初始化 wac
wac.refresh();
}

onRefresh

当 Servlet WebApplicationContext 刷新完成后,触发 Spring MVC 组件的初始化

1
2
3
protected void onRefresh(ApplicationContext context) {
// For subclasses: do nothing by default.
}

具体的实现,在子类 DispatcherServlet 中

onRefresh() 方法,有两种方式被触发

  • 方式一,在initWebApplicationContext 中,有两种情形,会触发
    • 情形一:情况一 + wac 已激活
    • 情形一:情况二
    • 这两种情形,此时 refreshEventReceived 为 false ,所以会顺着 #initWebApplicationContext() 方法的 <3> 的逻辑,调用 #onRefresh() 方法
  • 方式二,在initWebApplicationContext也有两种情况,会触发。不过相比方式一来说,过程会“曲折”一点。
    • 情形一:情况一 + wac 未激活
    • 情形二:情况三
    • 这两种情形,都会调用#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContextwac)方法,在wac执行刷新完成后,会回调在该方法中,注册的 SourceFilteringListener 监听器。

SourceFilteringListener

实现 GenericApplicationListener、SmartApplicationListener 监听器,实现将原始对象触发的事件,转发给指定监听器。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void onApplicationEvent(ApplicationEvent event) {
if (event.getSource() == this.source) { // 判断来源
onApplicationEventInternal(event);
}
}

protected void onApplicationEventInternal(ApplicationEvent event) {
if (this.delegate == null) {
throw new IllegalStateException(
"Must specify a delegate object or override the onApplicationEventInternal method");
}
this.delegate.onApplicationEvent(event);
}

判断事件的来源,就是原始类 source

  • 如果是,则调用 #onApplicationEventInternal(ApplicationEvent event) 方法,将事件转发给 delegate 监听器

创建 SourceFilteringListener 对象时,传入的两个参数:

  • source 属性,就是 wac 对象
  • delegate 属性,就是 ContextRefreshListener 对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}

}

public void onApplicationEvent(ContextRefreshedEvent event) {
// <1> 标记 refreshEventReceived 为 true
this.refreshEventReceived = true;
// <2> 处理事件中的 ApplicationContext 对象。这个方法,目前是空实现,由子类 DispatcherServlet 来实现。
onRefresh(event.getApplicationContext());
}

ContextRefreshListener 是 FrameworkServlet 的内部类。

  • 标记 refreshEventReceived 为 true 。这样,在 #initWebApplicationContext() 方法的 <3> 的逻辑, 不会调用 #onRefresh() 方法
  • <2> 处,调用 #onRefresh(ApplicationContext context) 方法,也就回到了onRefresh的逻辑了。