说明:本文所用的SpringMVC版本为4.3.4.RELEASE,应用服务器为TomCat8.0.33。
在上一篇文章中(点这里查看)我们说了ContextLoaderListener初始化Web上下文的过程,这篇文章中我们说一下DispatcherServlet初始化上下文的过程。我们先来看一下DispatcherServlet相关的UML类图:
从上图中我们可以看到DispatcherServlet也是一个HttpServlet的一个子类,并间接的实现了ApplicationContextAware这个接口。DispatcherServlet既然是一个Servlet的实现类,那么它也是遵守Servlet的生命周期的。也会有实例化、初始化(执行init方法)、接收请求处理请求(执行service方法)、销毁(执行destroy()方法)。所以DispatcherServlet的初始化过程,我们也是从init()这个方法开始(注意:我们这里说的初始化时执行init()方法,和类的初始化不是一回事,要区分开)。在开始之前我们还是要看一下相关的一些堆栈信息。
为什么要把这些堆栈信息截出来呢?因为这些堆栈信息是相当重要的东西。这也是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; }在这个方法中主要干了下面几件事:
- 获取根WebApplicationContext(即在ContextLoaderListener中创建的XmlWebApplicationContext,可以把它当做是一个父容器)
- 如果在实例化DispatcherServlet的时候,如果有传入WebApplicationContext,则使用传入的WebApplicationContext。
- 如果2没有,则从ServletContext中查找配置的WebApplicationContext,
- 如果3也没有找到,则创建WebApplicationContext
- 调用onRefresh方法,进行一系列的初始化动作
- 将初始化之后的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; }这个方法中主要干了这几件事:
- 获取WebApplicationContext上下文的类。
- 校验是不是ConfigurableWebApplicationContext的子类。
- 实例化WebApplicationContext
- 设置父容器
- 设置SpringMVC的配置文件
- 进行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(); }上面这些代码中主要做了这几件事:
- 设置id值(还没完全明白它的具体实际作用)
- 设置ServletContext
- 设置ServletConfig
- 设置nameSpace
- 设置一些监听器
- 初始化一些属性信息
- 如果有配置ApplicationContextInitializer相关的类,则调用ApplicationContextInitializer的initialize方法进行一些初始化的操作。
- 调用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的这个编辑器总是失焦呢、、、