SpringMVC之浅析上下文初始化(二)

时间:2022-08-29 17:29:01

说明:本文所用的SpringMVC版本为4.3.4.RELEASE,应用服务器为TomCat8.0.33。

在上一篇文章中(点这里查看)我们说了ContextLoaderListener初始化Web上下文的过程,这篇文章中我们说一下DispatcherServlet初始化上下文的过程。我们先来看一下DispatcherServlet相关的UML类图:

SpringMVC之浅析上下文初始化(二)

从上图中我们可以看到DispatcherServlet也是一个HttpServlet的一个子类,并间接的实现了ApplicationContextAware这个接口。DispatcherServlet既然是一个Servlet的实现类,那么它也是遵守Servlet的生命周期的。也会有实例化、初始化(执行init方法)、接收请求处理请求(执行service方法)、销毁(执行destroy()方法)。所以DispatcherServlet的初始化过程,我们也是从init()这个方法开始(注意:我们这里说的初始化时执行init()方法,和类的初始化不是一回事,要区分开)。在开始之前我们还是要看一下相关的一些堆栈信息。

SpringMVC之浅析上下文初始化(二)

为什么要把这些堆栈信息截出来呢?因为这些堆栈信息是相当重要的东西。这也是TomCat的体系架构中很重要的一些类和组成部分。从上图中我们可以看到init()这个方法是在StandarWrapper中的initServlet中被调用的(StandarWrapper代表着一个Servlet)。OK,下面进入我们的正题吧。

GenericServlet#init

从上面的UML图中和堆栈信息那张图中我们可以看到Servlet的第一个实现类是GenericServlet,并且最先调用的也是GenericServlet中的init方法,所以我们首先分析的也就是它了。我们先看一下这个方法的源码:
    @Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
源码中的东西非常简单啊,只有两句话话,一句是给ServletConfig赋值,一句是调用无参的init()方法。这里的ServletConfig的实现类为:StandardWrapperFacade。下面我们进入到无参的init方法中看看这个方法中执行了哪些内容。我们在GenericServlet这个类的init方法中发现代码是这样的:
   public void init() throws ServletException {
// NOOP by default
}
WHAT?空实现?空实现我们还怎么玩?别着急,我们在HttpServletBean中找到了一个重写的init方法。下面我们进转到HttpServletBean中去看一下。

HttpServletBean#init

我们看一下init这个方法的源码(省略了一些不重要的代码):
public final void init() throws ServletException {
try {
//创建属性编辑器类 (1)
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//创建一个编辑属性值的BeanWrapper (2)
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//用Resource加载Resource类型的属性(3)
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//初始化一些其他的BeanWrapper信息(其实是一个空方法)
initBeanWrapper(bw);
//为DispatcherServlet中的属性赋值
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
throw ex;
}
// Let subclasses do whatever initialization they like.
//继续执行初始化的动作(4)
initServletBean();
}
简单来说这个方法中干了这样的两件事,一是为DispatcherServlet中的属性赋值,二是调用initServletBean()方法。这里有几个地方需要说明一下,PropertyValue是一个存放一个对象的属性名字和值的类,即它保存一个bean的单独属性的值信息。PropertyValues是PropertyValue的集合。在(1)处创建了一个PropertyValues的对象,它的具体实现类是ServletConfigPropertyValues,从名字我们也能看出来这是一个获取Servlet配置信息的PropertyValues的属性值的结合,即它存放的是在Servlet中配置的信息。在这里它还做了另外的一件事,即校验一些必须配置的属性信息。在(2)处创建了一个BeanWrapper的实现类,BeanWrapper也是Spring框架中很重要的一个组件类,它可以用来编辑对象的属性值,所以这里创建的是一个编辑DispatcherServlet属性值的BeanWrapper的实现类。(3)处,如果有Resource类型的资源,则用相应的ResourceLoader来进行处理。bw.setPropertyValues这里就是给DispatcherServlet中的属性进行赋值的动作了。说了那么多,到底会给哪些属性赋值呢?又赋值的是哪些属性呢?举几个例子说明一下吧:
我们在web.xml中配置DispatcherServlet的时候,如果更改默认的SpringMVC配置文件的话,一般都会这样配置:
    <servlet>
<servlet-name>spring-miscellaneous</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-miscellaneous-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
注意,我们在<servlet>标签中添加了一个<init-param>的标签,指定了SpringMVC的配置文件的位置,我们在FrameworkServlet中发现有这样的一个属性contextConfigLocation,和我们的初始化参数的名字一样,然后我们翻遍代码也找不到有调用setContextConfigLocation这个方法的地方,那什么时候给这个属性进行赋值的呢?答案很明显了,就是在调用bw.setPropertyValues(pvs, true);的时候了。还有detectAllHandlerMappings等等属性,也是这样进行赋值的。(4)这个方法是一个很重要的方法,主要的初始化就是在这里完成。我们看一下这个方法的源码:

FrameworkServlet#initServletBean

去掉一些不重要的代码。
protected final void initServletBean() throws ServletException {
try {
this.webApplicationContext = initWebApplicationContext();
//空实现
initFrameworkServlet();
}
}
这个方法里面的内容也是很简单,其实就一句话,因为initFrameworkServlet是一个空实现的方法。最主要的方法是initWebApplicationContext,这个方法是DispatcherServlet初始化最重要的一个方法了。

FrameworkServlet#initWebApplicationContext

initWebApplicationContext中的注意源码如下:
protected WebApplicationContext initWebApplicationContext() {
//根据ServletContext获取根上下文即ContextLoaderListener中创建的XmlWebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//如果有webApplicationContext注入的话,则使用注入的webApplicationContext
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
//如果注入的webApplicationContext是ConfigurableWebApplicationContext的子类
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
//如果注入的webApplicationContext还没有被激活
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
//配置webApplicationContext中的内容,并调用refresh()方法,进行初始化
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//如果wac为null的话,即没有注入webApplicationContext
if (wac == null) {
//则从ServletContext中查找配置的WebApplicationContext
wac = findWebApplicationContext();
}
//如果上下文中也没有WebApplicationContext,则创建WebApplicationContext
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
//如果还没有调用refresh()方法的话,则调用onRefresh方法
if (!this.refreshEventReceived) {
onRefresh(wac);
}
//将WebApplicationContext放入到ServletContext中
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
在这个方法中主要干了下面几件事:
  1. 获取根WebApplicationContext(即在ContextLoaderListener中创建的XmlWebApplicationContext,可以把它当做是一个父容器)
  2. 如果在实例化DispatcherServlet的时候,如果有传入WebApplicationContext,则使用传入的WebApplicationContext。
  3. 如果2没有,则从ServletContext中查找配置的WebApplicationContext,
  4. 如果3也没有找到,则创建WebApplicationContext
  5. 调用onRefresh方法,进行一系列的初始化动作
  6. 将初始化之后的WebApplicationContext放入到ServletContext中(key是FrameworkServlet.class.getName() + ".CONTEXT."+servletName)
因为我们是在实例化DispatcherServlet的时候,调用的是默认的无参构造函数,所以在实例化的时候没有传入的WebApplicationContext,我们也没有在ServletContext配置WebApplicationContext,所以这里我们直接进入到createWebApplicationContext这个方法中,进行创建WebApplicationContext。

FrameworkServlet#createWebApplicationContext

createWebApplicationContext中的主要源码如下:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//获取上下文类
Class<?> contextClass = getContextClass();
//这个类必须是ConfigurableWebApplicationContext的子类
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");
}
//实例化上下文类
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

wac.setEnvironment(getEnvironment());
//设置父ApplicationContext即设置父容器
wac.setParent(parent);
//设置contextConfigLocation中的配置文件
wac.setConfigLocation(getContextConfigLocation());
//初始化WebApplicationContext
configureAndRefreshWebApplicationContext(wac);
return wac;
}
这个方法中主要干了这几件事:
  1. 获取WebApplicationContext上下文的类。
  2. 校验是不是ConfigurableWebApplicationContext的子类。
  3. 实例化WebApplicationContext
  4. 设置父容器
  5. 设置SpringMVC的配置文件
  6. 进行WebApplicationContext相关的一些其他配置,并调用refresh方法。
我们先来看一下getContextClass这个方法。
public Class<?> getContextClass() {
return this.contextClass;
}
getContextClass这个方法也很简单,就是获取contextClass的值。contextClass这个属性有一个默认的值:XmlWebApplicationContext.class。如果没有在web.xml的<servlet>标签中进行其他值的配置的话,则contextClass就取默认值XmlWebApplicationContext.class。XmlWebApplicationContext是一个实现了ConfigurableWebApplicationContext接口的一个类,下面我们需要分析的一个方法是configureAndRefreshWebApplicationContext

FrameworkServlet#configureAndRefreshWebApplicationContext

其主要源码如下:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
上面这些代码中主要做了这几件事:
  1. 设置id值(还没完全明白它的具体实际作用)
  2. 设置ServletContext
  3. 设置ServletConfig
  4. 设置nameSpace
  5. 设置一些监听器
  6. 初始化一些属性信息
  7. 如果有配置ApplicationContextInitializer相关的类,则调用ApplicationContextInitializer的initialize方法进行一些初始化的操作。
  8. 调用refresh方法。这个方法就是读取SpringMVC配置文件,解析bean、组装bean等等一系列操作了。
关于wac.refresh()方法的调用,我们这里先不分析的。在Spring的源码分析中再进行分析。接下来我们就回到org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext这个方法的这一段代码中:
if (!this.refreshEventReceived) {
onRefresh(wac);
}
onRefresh()这个方法的实现是在DispatcherServlet中的。

DispatcherServlet#onRefresh

其源码内容如下:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
从上面的源码中我们发现它只是调用了initStrategies这个方法。关于这个方法的分析,请点击这里(SpringMVC之浅析组件初始化过程)。
OK了,到这里我们关于DispatcherServlet初始化的主干流程的分析就先结束了。接着会做一些枝干流程的分析的工作(即一些Spring的生命周期接口的一些实现类)。

PS:感觉最近CSDN的这个编辑器总是失焦呢、、、