在开始之前,我们还是回过头看一眼 web.xml 的配置。代码如下:
HttpServletBean ,负责将 ServletConfig 设置到当前 Servlet 对象中。类上的简单注释如下:
FrameworkServlet ,负责初始化 Spring Servlet WebApplicationContext 容器。类上的简单注释如下:
DispatcherServlet ,负责初始化 Spring MVC 的各个组件,以及处理客户端的请求。类上的简单注释如下:
实现 EnvironmentCapable、EnvironmentAware 接口,继承 HttpServlet 抽象类,负责将 ServletConfig 集成到 Spring 中。当然,HttpServletBean 自身也是一个抽象类。
为什么 environment 属性,能够被自动注入呢?答案是 实现了EnvironmentAware 接口。
requiredProperties 属性,必须配置的属性的集合。可通过 #addRequiredProperty(String property) 方法,添加到其中。代码如下:
#init() 方法,负责将 ServletConfig 设置到当前 Servlet 对象中。代码如下:
<1> 处,解析 Servlet 配置的 <init-param /> 标签,封装到 PropertyValues pvs 中。其中,ServletConfigPropertyValues 是 HttpServletBean 的私有静态类,继承 MutablePropertyValues 类,ServletConfig 的 PropertyValues 封装实现类。代码如下:
代码简单,实现两方面的逻辑:<1> 处,遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues 中;<2> 处,判断要求的属性是否齐全。如果不齐全,则抛出 ServletException 异常。
- <2.1> 处,将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中。简单来说,BeanWrapper 是 Spring 提供的一个用来操作 Java Bean 属性的工具,使用它可以直接修改一个对象的属性。
- <2.2> 处,注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析。
- <2.3> 处,空实现,留给子类覆盖。代码如下:
此处有配置了 contextConfigLocation 属性,那么通过 <2.4> 处的逻辑,会反射设置到 FrameworkServlet.contextConfigLocation 属性。代码如下:
<3> 处,调用 #initServletBean() 方法,子类来实现,实现自定义的初始化逻辑。目前,FrameworkServlet 实现类该方法。代码如下:
实现 ApplicationContextAware 接口,继承 HttpServletBean 抽象类,负责初始化 Spring Servlet WebApplicationContext 容器。同时,FrameworkServlet 自身也是一个抽象类。
FrameworkServlet 的属性还是非常多,我们还是只看部分的关键属性。代码如下:
其中,contextClass 属性,创建的 WebApplicationContext 类型,默认为 DEFAULT_CONTEXT_CLASS 。代码如下:
又是我们熟悉的 XmlWebApplicationContext 类。在上一篇文章的 ContextLoader.properties 配置文件中,我们已经看到咯。
- contextConfigLocation 属性,配置文件的地址。例如:/WEB-INF/spring-servlet.xml 。
- webApplicationContext 属性,WebApplicationContext 对象,即本文的关键,Servlet WebApplicationContext 容器。它有四种方式进行“创建”。
通过方法参数 webApplicationContext 。
方式二:因为实现 ApplicationContextAware 接口,也可以 Spring 注入。代码如下:
方式三:见 #findWebApplicationContext() 方法。
方式四:见 #createWebApplicationContext(WebApplicationContext parent) 方法。
#initServletBean() 方法,进一步初始化当前 Servlet 对象。实际上,重心在初始化 Servlet WebApplicationContext 容器。代码如下:
#initWebApplicationContext() 方法,初始化 Servlet WebApplicationContext 对象。代码如下:
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 = 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.
synchronized (this.onRefreshMonitor) {
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 对象,
这就是在 Root WebApplicationContext 容器中初始化的呀。代码如下:
而这个是在Root WebApplicationContext 容器初始化的时候进行设置的:
org.springframework.web.context.ContextLoader#initWebApplicationContext
<2> 处,获得 WebApplicationContext wac 变量。下面,会分成三种情况。
如果构造方法已经传入 webApplicationContext 属性,则直接使用。实际上,就是我们在 1. 构造方法 提到的 Servlet WebApplicationContext 容器的第一、二种方式。
实际上,这块代码和 ContextLoader#initWebApplicationContext(ServletContext servletContext) 的中间段是一样的。除了 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 的具体实现代码不同。详细解析,见 4. configureAndRefreshWebApplicationContext 。
这种情况,就是我们在 4.1 构造方法 提到的 Servlet WebApplicationContext 容器的第三种方式。
如果此处 wac 还是为空,则调用 #findWebApplicationContext() 方法,从 ServletContext 获取对应的 WebApplicationContext 对象。代码如下:
这种情况,就是我们在 「4.1 构造方法」 提到的 Servlet WebApplicationContext 容器的第四种方式。
如果此处 wac 还是为空,则调用 #createWebApplicationContext(WebApplicationContext parent) 方法,创建一个 WebApplicationContext 对象。代码如下:
-
- <a> 处,获得 context 的类,即 contextClass 属性。并且,如果非 ConfigurableWebApplicationContext 类型,抛出 ApplicationContextException 异常。
- <b> 处,创建 context 类的对象。
- <c> 处,设置 environment、parent、configLocation 属性。其中,configLocation 是个重要属性。
- <d> 处,调用 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 。
详细解析,见 4. configureAndRefreshWebApplicationContext。
<3> 处,如果未触发刷新事件,则调用 #onRefresh(ApplicationContext context) 主动触发刷新事件。详细解析,见 5. onRefresh 中。
<4> 处,如果 publishContext 为 true 时,则将 context 设置到 ServletContext 中。
4. configureAndRefreshWebApplicationContext
#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 。代码如下:
- 实际上,大体逻辑上,和 Root WebApplicationContext 容器 的 ContextLoader#configureAndRefreshWebApplicationContext 方法是一致的。
- 【相同】<1> 处,如果 wac 使用了默认编号,则重新设置 id 属性。
- 【类似】<2> 处,设置 wac 的 servletContext、servletConfig、namespace 属性。
- 【独有】<3> 处,添加监听器 SourceFilteringListener 到 wac 中。这块的详细解析,见 5. onRefresh 中。
- 【相同】<4> 处,初始化属性资源。
- 【独有】<5> 处,执行处理完 WebApplicationContext 后的逻辑。目前是个空方法,暂无任何实现。
- 【相同】<6> 处,执行自定义初始化 context 。
- 【相同】<7> 处,刷新 wac ,从而初始化 wac 。
#onRefresh(ApplicationContext context) 方法,当 Servlet WebApplicationContext 刷新完成后,触发 Spring MVC 组件的初始化。代码如下:
这是一个空方法,具体的实现,在子类 DispatcherServlet 中。代码如下:
- 方式一,在 3. initWebApplicationContext 中,有两种情形,会触发。
- 方式二,在 3. initWebApplicationContext 中,也有两种情况,会触发。不过相比方式一来说,过程会“曲折”一点。
详细解析,见 5. SourceFilteringListener 。
SourceFilteringListener实现了ApplicationListener接口,重写了onApplicationEvent方法,关键就是这个方法。
调用的是delegate.onApplicationEvent(event); 这里的delegate指的其实就是构造函数传进来的ContextRefreshListener。
所以最终调用的是org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener#onApplicationEvent()。
这里,把refreshEventReceived设置成了true,回到了 5. onRefresh 。
以上,就是Servlet WebApplicationContext 容器启动过程。