981 lines
38 KiB
Markdown
981 lines
38 KiB
Markdown
# 精尽 Spring MVC 源码分析 —— 容器的初始化(二)之 Servlet WebApplicationContext 容器
|
||
|
||
# 1. 概述
|
||
|
||
本文接 [《精尽 Spring MVC 源码分析 —— 容器的初始化(一)之 Root WebApplicationContext 容器》](http://svip.iocoder.cn/Spring-MVC/context-init-Root-WebApplicationContext) 一文,我们来分享下 Servlet WebApplicationContext 容器的初始化的过程。
|
||
|
||
在开始之前,我们还是回过头看一眼 `web.xml` 的配置。代码如下:
|
||
|
||
```
|
||
<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 初始化的过程中执行。
|
||
|
||
------
|
||
|
||
DispatcherServlet 的类图如下:
|
||
|
||
[之 Servlet WebApplicationContext 容器.assets/01.png)](http://static.iocoder.cn/images/Spring/2022-02-04/01.png)类图
|
||
|
||
- HttpServletBean ,负责将 ServletConfig 设置到当前 Servlet 对象中。类上的简单注释如下:
|
||
|
||
```
|
||
// HttpServletBean.java
|
||
|
||
/**
|
||
* Simple extension of {@link javax.servlet.http.HttpServlet} which treats
|
||
* its config parameters ({@code init-param} entries within the
|
||
* {@code servlet} tag in {@code web.xml}) as bean properties.
|
||
*/
|
||
```
|
||
|
||
- FrameworkServlet ,负责初始化 Spring Servlet WebApplicationContext 容器。类上的简单注释如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
/**
|
||
* Base servlet for Spring's web framework. Provides integration with
|
||
* a Spring application context, in a JavaBean-based overall solution.
|
||
*/
|
||
```
|
||
|
||
- DispatcherServlet ,负责初始化 Spring MVC 的各个组件,以及处理客户端的请求。类上的简单注释如下:
|
||
|
||
```
|
||
// DispatcherServlet.java
|
||
|
||
/**
|
||
* Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers
|
||
* or HTTP-based remote service exporters. Dispatches to registered handlers for processing
|
||
* a web request, providing convenient mapping and exception handling facilities.
|
||
*/
|
||
```
|
||
|
||
每一层的 Servlet 实现类,执行对应负责的逻辑。干净~下面,我们逐个类来进行解析。
|
||
|
||
# 2. 如何调试
|
||
|
||
执行 `DispatcherServletTests#configuredDispatcherServlets()` 单元测试方法,即可执行本文涉及的一些逻辑。
|
||
|
||
# 3. HttpServletBean
|
||
|
||
`org.springframework.web.servlet.HttpServletBean` ,实现 EnvironmentCapable、EnvironmentAware 接口,继承 HttpServlet 抽象类,负责将 ServletConfig 集成到 Spring 中。当然,HttpServletBean 自身也是一个抽象类。
|
||
|
||
## 3.1 构造方法
|
||
|
||
```
|
||
// HttpServletBean.java
|
||
|
||
@Nullable
|
||
private ConfigurableEnvironment environment;
|
||
|
||
/**
|
||
* 必须配置的属性的集合
|
||
*
|
||
* 在 {@link ServletConfigPropertyValues} 中,会校验是否有对应的属性
|
||
*/
|
||
private final Set<String> requiredProperties = new HashSet<>(4);
|
||
```
|
||
|
||
- `environment` 属性,相关的方法,代码如下:
|
||
|
||
```
|
||
// 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();
|
||
}
|
||
```
|
||
|
||
- 为什么 `environment` 属性,能够被自动注入呢?答案是 EnvironmentAware 接口。具体的源码解析,见 [《【死磕 Spring】—— IoC 之深入分析 Aware 接口》](http://svip.iocoder.cn/Spring/IoC-Aware-interface/?self) 。当然,也可以不看。
|
||
|
||
- `requiredProperties` 属性,必须配置的属性的集合。可通过 `#addRequiredProperty(String property)` 方法,添加到其中。代码如下:
|
||
|
||
```
|
||
// HttpServletBean.java
|
||
|
||
protected final void addRequiredProperty(String property) {
|
||
this.requiredProperties.add(property);
|
||
}
|
||
```
|
||
|
||
## 3.2 init
|
||
|
||
`#init()` 方法,负责将 ServletConfig 设置到当前 Servlet 对象中。代码如下:
|
||
|
||
```
|
||
// HttpServletBean.java
|
||
|
||
@Override
|
||
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();
|
||
}
|
||
```
|
||
|
||
- `<1>` 处,解析 Servlet 配置的 `<init-param />` 标签,封装到 PropertyValues `pvs` 中。其中,ServletConfigPropertyValues 是 HttpServletBean 的私有静态类,继承 MutablePropertyValues 类,**ServletConfig 的 PropertyValues 封装实现类**。代码如下:
|
||
|
||
```
|
||
// HttpServletBean.java
|
||
|
||
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, ", "));
|
||
}
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
- 代码简单,实现两方面的逻辑:`<1>` 处,遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues 中;`<2>` 处,判断要求的属性是否齐全。如果不齐全,则抛出 ServletException 异常。
|
||
|
||
- `<2.1>` 处,将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 `pvs` 注入到该 BeanWrapper 对象中。简单来说,BeanWrapper 是 Spring 提供的一个用来操作 Java Bean 属性的工具,**使用它可以直接修改一个对象的属性**。
|
||
|
||
- `<2.2>` 处,注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析。
|
||
|
||
- `<2.3>` 处,空实现,留给子类覆盖。代码如下:
|
||
|
||
```
|
||
// HttpServletBean.java
|
||
|
||
/**
|
||
* Initialize the BeanWrapper for this HttpServletBean,
|
||
* possibly with custom editors.
|
||
* <p>This default implementation is empty.
|
||
* @param bw the BeanWrapper to initialize
|
||
* @throws BeansException if thrown by BeanWrapper methods
|
||
* @see org.springframework.beans.BeanWrapper#registerCustomEditor
|
||
*/
|
||
protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
|
||
}
|
||
```
|
||
|
||
- 然而实际上,子类暂时木有任何实现。
|
||
|
||
- `<2.4>` 处,以 Spring 的方式来将 `pvs` 注入到该 BeanWrapper 对象中,技设置到当前 Servlet 对象中。可能比较费解,我们还是举个例子。假设如下:
|
||
|
||
```
|
||
// web.xml
|
||
|
||
<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>
|
||
```
|
||
|
||
- 此处有配置了 `contextConfigLocation` 属性,那么通过 `<2.4>` 处的逻辑,会反射设置到 `FrameworkServlet.contextConfigLocation` 属性。代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
/** Explicit context config location. */
|
||
@Nullable
|
||
private String contextConfigLocation;
|
||
|
||
public void setContextConfigLocation(@Nullable String contextConfigLocation) {
|
||
this.contextConfigLocation = contextConfigLocation;
|
||
}
|
||
```
|
||
|
||
- 😈 看懂了这波骚操作了么?
|
||
|
||
- `<3>` 处,调用 `#initServletBean()` 方法,子类来实现,实现自定义的初始化逻辑。目前,FrameworkServlet 实现类该方法。代码如下:
|
||
|
||
```
|
||
// HttpServletBean.java
|
||
|
||
protected void initServletBean() throws ServletException {
|
||
}
|
||
```
|
||
|
||
- 详细解析,见 [「4. FrameworkServlet」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 。
|
||
|
||
# 4. FrameworkServlet
|
||
|
||
`org.springframework.web.servlet.FrameworkServlet` ,实现 ApplicationContextAware 接口,继承 HttpServletBean 抽象类,负责初始化 Spring Servlet WebApplicationContext 容器。同时,FrameworkServlet 自身也是一个抽象类。
|
||
|
||
## 4.1 构造方法
|
||
|
||
FrameworkServlet 的属性还是非常多,我们还是只看部分的关键属性。代码如下:
|
||
|
||
```
|
||
// 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` 。代码如下:
|
||
|
||
```
|
||
/**
|
||
* Default context class for FrameworkServlet.
|
||
* @see org.springframework.web.context.support.XmlWebApplicationContext
|
||
*/
|
||
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
|
||
```
|
||
|
||
- 😈 又是我们熟悉的 XmlWebApplicationContext 类。在上一篇文章的 `ContextLoader.properties` 配置文件中,我们已经看到咯。
|
||
|
||
- `contextConfigLocation` 属性,配置文件的地址。例如:`/WEB-INF/spring-servlet.xml` 。
|
||
|
||
- `webApplicationContext` 属性,WebApplicationContext 对象,即本文的关键,**Servlet WebApplicationContext 容器**。它有四种方式进行“创建”。
|
||
|
||
- 方式一:通过构造方法,代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
public FrameworkServlet(WebApplicationContext webApplicationContext) {
|
||
this.webApplicationContext = webApplicationContext;
|
||
}
|
||
```
|
||
|
||
- 通过方法参数 `webApplicationContext` 。
|
||
|
||
- 方式二:因为实现 ApplicationContextAware 接口,也可以 Spring 注入。代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
/**
|
||
* 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;
|
||
}
|
||
}
|
||
```
|
||
|
||
- 和方式一,是有几分类似的。
|
||
|
||
- 方式三:见 `#findWebApplicationContext()` 方法。
|
||
|
||
- 方式四:见 `#createWebApplicationContext(WebApplicationContext parent)` 方法。
|
||
|
||
## 4.2 initServletBean
|
||
|
||
`#initServletBean()` 方法,进一步初始化当前 Servlet 对象。实际上,重心在初始化 Servlet WebApplicationContext 容器。代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
@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");
|
||
}
|
||
}
|
||
```
|
||
|
||
- `<1>` 处,调用 `#initWebApplicationContext()` 方法,初始化 Servlet WebApplicationContext 对象。详细解析,见 [「4.3 initWebApplicationContext」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 。
|
||
|
||
- `<2>` 处,调用 `#initFrameworkServlet()` 方法,空实现。子类有需要,可以实现该方法,实现自定义逻辑。代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
/**
|
||
* This method will be invoked after any bean properties have been set and
|
||
* the WebApplicationContext has been loaded. The default implementation is empty;
|
||
* subclasses may override this method to perform any initialization they require.
|
||
* @throws ServletException in case of an initialization exception
|
||
*/
|
||
protected void initFrameworkServlet() throws ServletException {
|
||
}
|
||
```
|
||
|
||
- 😈 然而实际上,并没有子类,对该方法重新实现。
|
||
|
||
## 4.3 initWebApplicationContext
|
||
|
||
`#initWebApplicationContext()` 方法,初始化 Servlet WebApplicationContext 对象。代码如下:
|
||
|
||
> 艿艿提示:这个方法的逻辑并不复杂,但是涉及调用的方法的逻辑比较多。同时,也是本文最最最核心的方法了。
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
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;
|
||
}
|
||
```
|
||
|
||
- `<1>` 处,调用 `WebApplicationContextUtils#getWebApplicationContext((ServletContext sc)` 方法,获得 Root WebApplicationContext 对象,这就是在 [《精尽 Spring MVC 源码分析 —— 容器的初始化(一)之 Root WebApplicationContext 容器》](http://svip.iocoder.cn/Spring-MVC/context-init-Root-WebApplicationContext) 中初始化的呀。代码如下:
|
||
|
||
```
|
||
// WebApplicationContextUtils.java
|
||
|
||
@Nullable
|
||
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
|
||
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
|
||
}
|
||
|
||
@Nullable
|
||
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
|
||
Assert.notNull(sc, "ServletContext must not be null");
|
||
Object attr = sc.getAttribute(attrName);
|
||
// ... 省略各种校验的代码
|
||
return (WebApplicationContext) attr;
|
||
}
|
||
```
|
||
|
||
- 熟不熟悉,惊喜不惊喜。
|
||
|
||
- `<2>` 处,获得 WebApplicationContext `wac` 变量。下面,会分成三种情况。
|
||
|
||
- ========== 第一种情况 ==========
|
||
|
||
- 如果构造方法已经传入 `webApplicationContext` 属性,则直接使用。实际上,就是我们在 [「4.1 构造方法」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 提到的 **Servlet WebApplicationContext 容器**的第一、二种方式。
|
||
|
||
- 实际上,这块代码和 `ContextLoader#initWebApplicationContext(ServletContext servletContext)` 的[中间段](https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java#L280-L293) 是一样的。除了 `#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)` 的具体实现代码不同。详细解析,见 [「4.4 configureAndRefreshWebApplicationContext」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 。
|
||
|
||
- ========== 第二种情况 ==========
|
||
|
||
- 这种情况,就是我们在 [「4.1 构造方法」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 提到的 **Servlet WebApplicationContext 容器**的第三种方式。
|
||
|
||
- 如果此处 `wac` 还是为空,则调用 `#findWebApplicationContext()` 方法,从 ServletContext 获取对应的 WebApplicationContext 对象。代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
/** ServletContext attribute to find the WebApplicationContext in. */
|
||
@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;
|
||
}
|
||
```
|
||
|
||
- 一般情况下,我们不会配置 `contextAttribute` 属性。所以,这段逻辑暂时无视。
|
||
|
||
- ========== 第三种情况 ==========
|
||
|
||
- 这种情况,就是我们在 [「4.1 构造方法」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 提到的 **Servlet WebApplicationContext 容器**的第四种方式。
|
||
|
||
- 如果此处 `wac` 还是为空,则调用 `#createWebApplicationContext(WebApplicationContext parent)` 方法,创建一个 WebApplicationContext 对象。代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
/**
|
||
* 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;
|
||
}
|
||
```
|
||
|
||
- `<a>` 处,获得 `context` 的类,即 `contextClass` 属性。并且,如果非 ConfigurableWebApplicationContext 类型,抛出 ApplicationContextException 异常。
|
||
- `<b>` 处,创建 `context` 类的对象。
|
||
- `<c>` 处,设置 `environment`、`parent`、`configLocation` 属性。其中,`configLocation` 是个**重要**属性。
|
||
- `<d>` 处,调用 `#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)` 方法,配置和初始化 `wac` 。详细解析,见 [「4.4 configureAndRefreshWebApplicationContext」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 。
|
||
|
||
- ========== END ==========
|
||
|
||
- `<3>` 处,如果未触发刷新事件,则调用 `#onRefresh(ApplicationContext context)` 主动触发刷新事件。详细解析,见 [「4.5 onRefresh」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 中。另外,`refreshEventReceived` 属性,定义如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
/**
|
||
* Flag used to detect whether onRefresh has already been called.
|
||
*
|
||
* 标记是否接收到 ContextRefreshedEvent 事件。即 {@link #onApplicationEvent(ContextRefreshedEvent)}
|
||
*/
|
||
private boolean refreshEventReceived = false;
|
||
```
|
||
|
||
- `<4>` 处,如果 `publishContext` 为 `true` 时,则将 `context` 设置到 ServletContext 中。涉及到的变量和方法如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
/**
|
||
* Should we publish the context as a ServletContext attribute?.
|
||
*
|
||
* 是否将 {@link #webApplicationContext} 设置到 {@link ServletContext} 的属性种
|
||
*/
|
||
private boolean publishContext = true;
|
||
|
||
/**
|
||
* Prefix for the ServletContext attribute for the WebApplicationContext.
|
||
* The completion is the servlet name.
|
||
*/
|
||
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
|
||
|
||
public String getServletContextAttributeName() {
|
||
return SERVLET_CONTEXT_PREFIX + getServletName();
|
||
}
|
||
|
||
// HttpServletBean.java
|
||
|
||
@Override
|
||
@Nullable
|
||
public String getServletName() {
|
||
return (getServletConfig() != null ? getServletConfig().getServletName() : null);
|
||
}
|
||
```
|
||
|
||
## 4.4 configureAndRefreshWebApplicationContext
|
||
|
||
`#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)` 方法,配置和初始化 `wac` 。代码如下:
|
||
|
||
```
|
||
// 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();
|
||
}
|
||
```
|
||
|
||
- 实际上,大体逻辑上,和 [《精尽 Spring MVC 源码分析 —— 容器的初始化(一)之 Root WebApplicationContext 容器》](http://svip.iocoder.cn/Spring-MVC/context-init-Root-WebApplicationContext) 的 [「4.3 configureAndRefreshWebApplicationContext」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 小节是一致的。
|
||
|
||
- 【相同】`<1>` 处,如果 `wac` 使用了默认编号,则重新设置 `id` 属性。
|
||
|
||
- 【类似】`<2>` 处,设置 `wac` 的 `servletContext`、`servletConfi`g、`namespace` 属性。
|
||
|
||
- 【独有】`<3>` 处,添加监听器 SourceFilteringListener 到 `wac` 中。这块的详细解析,见 [「4.5 onRefresh」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 中。
|
||
|
||
- 【相同】`<4>` 处,TODO 芋艿,暂时忽略。
|
||
|
||
- 【独有】`<5>` 处,执行处理完 WebApplicationContext 后的逻辑。目前是个空方法,暂无任何实现。代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
|
||
}
|
||
```
|
||
|
||
- 【相同】`<6>` 处,执行自定义初始化 context TODO 芋艿,暂时忽略。
|
||
- 【相同】`<7>` 处,刷新 `wac` ,从而初始化 `wac` 。
|
||
|
||
## 4.5 onRefresh
|
||
|
||
`#onRefresh(ApplicationContext context)` 方法,当 Servlet WebApplicationContext 刷新完成后,触发 Spring MVC 组件的初始化。代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
/**
|
||
* Template method which can be overridden to add servlet-specific refresh work.
|
||
* Called after successful context refresh.
|
||
* <p>This implementation is empty.
|
||
* @param context the current WebApplicationContext
|
||
* @see #refresh()
|
||
*/
|
||
protected void onRefresh(ApplicationContext context) {
|
||
// For subclasses: do nothing by default.
|
||
}
|
||
```
|
||
|
||
- 这是一个空方法,具体的实现,在子类 DispatcherServlet 中。代码如下:
|
||
|
||
```
|
||
// DispatcherServlet.java
|
||
|
||
/**
|
||
* This implementation calls {@link #initStrategies}.
|
||
*/
|
||
@Override
|
||
protected void onRefresh(ApplicationContext context) {
|
||
initStrategies(context);
|
||
}
|
||
|
||
/**
|
||
* Initialize the strategy objects that this servlet uses.
|
||
* <p>May be overridden in subclasses in order to initialize further strategy objects.
|
||
*/
|
||
protected void initStrategies(ApplicationContext context) {
|
||
// 初始化 MultipartResolver
|
||
initMultipartResolver(context);
|
||
// 初始化 LocaleResolver
|
||
initLocaleResolver(context);
|
||
// 初始化 ThemeResolver
|
||
initThemeResolver(context);
|
||
// 初始化 HandlerMappings
|
||
initHandlerMappings(context);
|
||
// 初始化 HandlerAdapters
|
||
initHandlerAdapters(context);
|
||
// 初始化 HandlerExceptionResolvers
|
||
initHandlerExceptionResolvers(context);
|
||
// 初始化 RequestToViewNameTranslator
|
||
initRequestToViewNameTranslator(context);
|
||
// 初始化 ViewResolvers
|
||
initViewResolvers(context);
|
||
// 初始化 FlashMapManager
|
||
initFlashMapManager(context);
|
||
}
|
||
```
|
||
|
||
- 这里,我们先不深究,在 DispatcherServlet 的初始化的文章中,详细解析。
|
||
|
||
------
|
||
|
||
`#onRefresh()` 方法,有两种方式被触发:
|
||
|
||
- 方式一,在
|
||
|
||
|
||
|
||
「4.3 initWebApplicationContext」
|
||
|
||
|
||
|
||
中,有两种情形,会触发。
|
||
|
||
- 情形一:情况一 + `wac` 已激活。
|
||
- 情形二:情况二。
|
||
- 这两种情形,此时 `refreshEventReceived` 为 `false` ,所以会顺着 `#initWebApplicationContext()` 方法的 `<3>` 的逻辑,调用 `#onRefresh()` 方法。😈 貌似说的有点绕,大家自己顺顺。
|
||
|
||
- 方式二,在
|
||
|
||
|
||
|
||
「4.3 initWebApplicationContext」
|
||
|
||
|
||
|
||
中,也有两种情况,会触发。不过相比方式一来说,过程会“曲折”一点。
|
||
|
||
- 情形一:情况一 + `wac` 未激活。
|
||
- 情形二:情况三。
|
||
- 这两种情形,都会调用 `#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)` 方法,在 `wac` 执行刷新完成后,会回调在该方法中,注册的 SourceFilteringListener 监听器。详细解析,见 [「5. SourceFilteringListener」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 。
|
||
|
||
# 5. SourceFilteringListener
|
||
|
||
`org.springframework.context.event.SourceFilteringListener` ,实现 GenericApplicationListener、SmartApplicationListener 监听器,实现将原始对象触发的事件,转发给指定监听器。代码如下:
|
||
|
||
```
|
||
// SourceFilteringListener.java
|
||
|
||
public class SourceFilteringListener implements GenericApplicationListener, SmartApplicationListener {
|
||
|
||
/**
|
||
* 原始类
|
||
*/
|
||
private final Object source;
|
||
/**
|
||
* 代理的监听器
|
||
*/
|
||
@Nullable
|
||
private GenericApplicationListener delegate;
|
||
|
||
/**
|
||
* Create a SourceFilteringListener for the given event source.
|
||
* @param source the event source that this listener filters for,
|
||
* only processing events from this source
|
||
* @param delegate the delegate listener to invoke with event
|
||
* from the specified source
|
||
*/
|
||
public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {
|
||
this.source = source;
|
||
this.delegate = (delegate instanceof GenericApplicationListener ?
|
||
(GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
|
||
}
|
||
|
||
/**
|
||
* Create a SourceFilteringListener for the given event source,
|
||
* expecting subclasses to override the {@link #onApplicationEventInternal}
|
||
* method (instead of specifying a delegate listener).
|
||
* @param source the event source that this listener filters for,
|
||
* only processing events from this source
|
||
*/
|
||
protected SourceFilteringListener(Object source) {
|
||
this.source = source;
|
||
}
|
||
|
||
|
||
@Override
|
||
public void onApplicationEvent(ApplicationEvent event) {
|
||
if (event.getSource() == this.source) { // 判断来源
|
||
onApplicationEventInternal(event);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public boolean supportsEventType(ResolvableType eventType) {
|
||
return (this.delegate == null || this.delegate.supportsEventType(eventType));
|
||
}
|
||
|
||
@Override
|
||
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
|
||
return supportsEventType(ResolvableType.forType(eventType));
|
||
}
|
||
|
||
@Override
|
||
public boolean supportsSourceType(@Nullable Class<?> sourceType) {
|
||
return (sourceType != null && sourceType.isInstance(this.source));
|
||
}
|
||
|
||
@Override
|
||
public int getOrder() {
|
||
return (this.delegate != null ? this.delegate.getOrder() : Ordered.LOWEST_PRECEDENCE);
|
||
}
|
||
|
||
|
||
/**
|
||
* Actually process the event, after having filtered according to the
|
||
* desired event source already.
|
||
* <p>The default implementation invokes the specified delegate, if any.
|
||
* @param event the event to process (matching the specified source)
|
||
*/
|
||
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);
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
- 这个类的核心代码,就是 `#onApplicationEvent(ApplicationEvent event)` 方法中,判断事件的来源,就是原始类 `source` 。如果是,则调用 `#onApplicationEventInternal(ApplicationEvent event)` 方法,将事件转发给 `delegate` 监听器。
|
||
|
||
------
|
||
|
||
我们在回看下 `#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)` 方法,创建 SourceFilteringListener 对象时,传入的两个参数:
|
||
|
||
- `source` 属性,就是 `wac` 对象。
|
||
- `delegate` 属性,就是 ContextRefreshListener 对象。
|
||
|
||
下面,让我们来看看 ContextRefreshListener 具体的代码实现,代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
|
||
|
||
@Override
|
||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||
FrameworkServlet.this.onApplicationEvent(event);
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
- ContextRefreshListener 是 FrameworkServlet 的内部类。
|
||
|
||
- 在 `#onApplicationEvent(ContextRefreshedEvent event)` 方法中,会回调 `FrameworkServlet#onApplicationEvent(event)` 方法,代码如下:
|
||
|
||
```
|
||
// FrameworkServlet.java
|
||
|
||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||
// <1> 标记 refreshEventReceived 为 true
|
||
this.refreshEventReceived = true;
|
||
// <2> 处理事件中的 ApplicationContext 对象。这个方法,目前是空实现,由子类 DispatcherServlet 来实现。
|
||
onRefresh(event.getApplicationContext());
|
||
}
|
||
```
|
||
|
||
- `<1>` 处,标记 `refreshEventReceived` 为 `true` 。这样,在 `#initWebApplicationContext()` 方法的 `<3>` 的逻辑,就**不会**调用 `#onRefresh()` 方法。
|
||
- `<2>` 处,调用 `#onRefresh(ApplicationContext context)` 方法,也就回到了 [「4.5 onRefresh」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 的逻辑了。
|
||
|
||
# 666. 彩蛋
|
||
|
||
舒服,真舒服。哈哈哈哈~写舒服了。
|
||
|
||
参考和推荐如下文章:
|
||
|
||
- 田小波 [《Spring MVC 原理探秘 - 容器的创建过程》](https://www.tianxiaobo.com/2018/06/30/Spring-MVC-原理探秘-容器的创建过程/)
|
||
- 郝佳 [《Spring 源码深度解析》](https://item.jd.com/11311737.html) 的 [「11.3 DispatcherServlet」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 小节
|
||
- 韩路彪 [《看透 Spring MVC:源代码分析与实践》](https://item.jd.com/11807414.html) 的 [「第9章 创建 Spring MVC 之器」](https://svip.iocoder.cn/Spring-MVC/context-init-Servlet-WebApplicationContext/#) 小节 |