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

  1. 云栖社区>
  2. 博客>
  3. 正文

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

木叶之荣 2017-07-01 23:42:25 浏览642
展开阅读全文

说明:本文所用的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;
	}
在这个方法中主要干了下面几件事:
  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的这个编辑器总是失焦呢、、、

网友评论

登录后评论
0/500
评论
木叶之荣
+ 关注