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

来源:互联网 发布:租用临时备案域名 编辑:程序博客网 时间:2024/05/22 06:40

说明:本文所用的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中创建的XmlWebApplicationContextWebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;//如果有webApplicationContext注入的话,则使用注入的webApplicationContextif (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的话,即没有注入webApplicationContextif (wac == null) {//则从ServletContext中查找配置的WebApplicationContextwac = findWebApplicationContext();}//如果上下文中也没有WebApplicationContext,则创建WebApplicationContextif (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());//初始化WebApplicationContextconfigureAndRefreshWebApplicationContext(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

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

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

阅读全文
0 0
原创粉丝点击