容器的初始化之 Servlet WebApplicationContext 容器

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

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

HttpServletBean ,负责将 ServletConfig 设置到当前 Servlet 对象中。类上的简单注释如下:

FrameworkServlet ,负责初始化 Spring Servlet WebApplicationContext 容器。类上的简单注释如下:

DispatcherServlet ,负责初始化 Spring MVC 的各个组件,以及处理客户端的请求。类上的简单注释如下:

每一层的 Servlet 实现类,执行对应负责的逻辑。

下面,我们逐个类来进行解析。

HttpServletBean

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

1. 构造方法

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

为什么 environment 属性,能够被自动注入呢?答案是 实现了EnvironmentAware 接口。

requiredProperties 属性,必须配置的属性的集合。可通过 #addRequiredProperty(String property) 方法,添加到其中。代码如下:

2. init

#init() 方法,负责将 ServletConfig 设置到当前 Servlet 对象中。代码如下:

<1> 处,解析 Servlet 配置的 <init-param /> 标签,封装到 PropertyValues pvs 中。其中,ServletConfigPropertyValues 是 HttpServletBean 的私有静态类,继承 MutablePropertyValues 类,ServletConfig 的 PropertyValues 封装实现类。代码如下:

代码简单,实现两方面的逻辑:<1> 处,遍历 ServletConfig 的初始化参数集合,添加到 ServletConfigPropertyValues 中;<2> 处,判断要求的属性是否齐全。如果不齐全,则抛出 ServletException 异常。

  1. <2.1> 处,将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中。简单来说,BeanWrapper 是 Spring 提供的一个用来操作 Java Bean 属性的工具,使用它可以直接修改一个对象的属性
  2. <2.2> 处,注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析。
  3. <2.3> 处,空实现,留给子类覆盖。代码如下:

然而,目前子类并没有任何实现。

  1. <2.4> 处,以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中,即设置到当前 Servlet 对象中。可能比较费解,我们还是举个例子。假设如下:

此处有配置了 contextConfigLocation 属性,那么通过 <2.4> 处的逻辑,会反射设置到 FrameworkServlet.contextConfigLocation 属性。代码如下:

<3> 处,调用 #initServletBean() 方法,子类来实现,实现自定义的初始化逻辑。目前,FrameworkServlet 实现类该方法。代码如下:

FrameworkServlet

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

1. 构造方法

FrameworkServlet 的属性还是非常多,我们还是只看部分的关键属性。代码如下:

其中,contextClass 属性,创建的 WebApplicationContext 类型,默认为 DEFAULT_CONTEXT_CLASS 。代码如下:

又是我们熟悉的 XmlWebApplicationContext 类。在上一篇文章的 ContextLoader.properties 配置文件中,我们已经看到咯

  1. contextConfigLocation 属性,配置文件的地址。例如:/WEB-INF/spring-servlet.xml 
  2. webApplicationContext 属性,WebApplicationContext 对象,即本文的关键,Servlet WebApplicationContext 容器。它有四种方式进行“创建”。

方式一:通过构造方法,代码如下:

通过方法参数 webApplicationContext 

方式二:因为实现 ApplicationContextAware 接口,也可以 Spring 注入。代码如下:

方式三:见 #findWebApplicationContext() 方法。

方式四:见 #createWebApplicationContext(WebApplicationContext parent) 方法。

2. initServletBean

#initServletBean() 方法,进一步初始化当前 Servlet 对象。实际上,重心在初始化 Servlet WebApplicationContext 容器。代码如下:

3. initWebApplicationContext

#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 对象。代码如下:

  1. 一般情况下,我们不会配置 contextAttribute 属性。所以,这段逻辑暂时无视。

========== 第三种情况 ==========

这种情况,就是我们在 「4.1 构造方法」 提到的 Servlet WebApplicationContext 容器的第四种方式。

如果此处 wac 还是为空,则调用 #createWebApplicationContext(WebApplicationContext parent) 方法,创建一个 WebApplicationContext 对象。代码如下:

    1. <a> 处,获得 context 的类,即 contextClass 属性。并且,如果非 ConfigurableWebApplicationContext 类型,抛出 ApplicationContextException 异常。
    2. <b> 处,创建 context 类的对象。
    3. <c> 处,设置 environmentparentconfigLocation 属性。其中,configLocation 是个重要属性。
    4. <d> 处,调用 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 

详细解析,见 4. configureAndRefreshWebApplicationContext

========== END ==========

<3> 处,如果未触发刷新事件,则调用 #onRefresh(ApplicationContext context) 主动触发刷新事件。详细解析,见 5. onRefresh 中。

<4> 处,如果 publishContext 为 true 时,则将 context 设置到 ServletContext 中。

4. configureAndRefreshWebApplicationContext

#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 。代码如下:

  1. 实际上,大体逻辑上,和 Root WebApplicationContext 容器 的 ContextLoader#configureAndRefreshWebApplicationContext 方法是一致的。
  2. 【相同】<1> 处,如果 wac 使用了默认编号,则重新设置 id 属性。
  3. 【类似】<2> 处,设置 wac 的 servletContextservletConfignamespace 属性。
  4. 【独有】<3> 处,添加监听器 SourceFilteringListener 到 wac 中。这块的详细解析,见  5. onRefresh 中。
  5. 【相同】<4> 处,初始化属性资源
  6. 【独有】<5> 处,执行处理完 WebApplicationContext 后的逻辑。目前是个空方法,暂无任何实现。
  7. 【相同】<6> 处,执行自定义初始化 context 。
  8. 【相同】<7> 处,刷新 wac ,从而初始化 wac 

5. onRefresh

#onRefresh(ApplicationContext context) 方法,当 Servlet WebApplicationContext 刷新完成后,触发 Spring MVC 组件的初始化。代码如下:

这是一个空方法,具体的实现,在子类 DispatcherServlet 中。代码如下:

  1. 这里,我们先不深究,在 DispatcherServlet 的初始化过程中,详细解析。

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

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

详细解析,见  5. SourceFilteringListener

6. SourceFilteringListener

SourceFilteringListener实现了ApplicationListener接口,重写了onApplicationEvent方法,关键就是这个方法。

调用的是delegate.onApplicationEvent(event); 这里的delegate指的其实就是构造函数传进来的ContextRefreshListener。

所以最终调用的是org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener#onApplicationEvent()。

这里,把refreshEventReceived设置成了true,回到了 5. onRefresh

以上,就是Servlet WebApplicationContext 容器启动过程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章