SpringMVC源码:DispatcherServlet的初始化(一)

首先需要明确,DispatcherServlet本质上是一个Servlet,所以其遵守Web规范(遵守Servlet的生命周期)

回顾:Servlet的生命周期

初始化阶段:tomcat 会调用 DispatcherServlet 的 init() 方法进行初始化
请求服务阶段:tomcat 会调用 DispatcherServlet 的 Service() 方法处理request请求
销毁阶段:tomcat 会调用 DispatcherServlet 的 destroy() 方法进行销毁
本文围绕DispatcherServlet的初始化阶段进行讲解,所以分析的是在 init 阶段做的事情

DispatcherServlet的继承结构

在这里插入图片描述

DispatcherServlet的 init() 方法定义在其父类 HttpServletBean 中,所以初始化的入口在 HttpServletBean#init

初始化入口:HttpServletBean的init()方法

	@Override
	public final void init() throws ServletException {

		// (第一部分)Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			//catch ...			
		}

		// (第二部分)Let subclasses do whatever initialization they like.
		initServletBean();
	}

代码分为两个部分:第一部分包括try-catch包裹的部分,第二部分就是initServletBean()

第一部分代码(重要):

作用:从 init parameter 处获取信息,来设置bean的properties
我们在 web.xml 中配置 DispatcherServlet 时,需要为其配置一个叫 contextConfigLocation 的参数

<servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

很多同学不知道为什么要配置这个contextConfigLocation,以及配置它有什么用
其实 contextConfigLocation 是 DispatcherServlet 的成员属性(在其父类FrameworkServlet中定义)。
在这里,我定义了参数 contextConfigLocation 的值为 “classpath:spring.xml”,正是因为有了 try-catch 部分的代码,这个 “classpath:spring.xml” 才能被赋值给 DispatcherServlet 中的 contextConfigLocation 属性(它的作用后续会讲解)

注:底层利用的是Spring中 “依赖注入” 的思想,通过相应的setXXX方法进行的注入

1、提取在web.xml 中配置的 DispatcherServlet 的 “< init-param>” 信息,保存在PropertyValues中(方便后续使用BeanWrapper注入)

// 注: "getServletConfig()" 获取Servlet配置对象,来构造PropertyValues 
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

注:ServletConfigPropertyValues的构造函数:

public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) throws ServletException {
	//...
	//利用传进来的ServletConfig得到<init-param>中的所有参数名(param-name)
    Enumeration<String> paramNames = config.getInitParameterNames();
    //遍历所有的 param-name ,分别得到各自的 param-value 值,组装成PropertyValue...
    while (paramNames.hasMoreElements()) {
        String property = paramNames.nextElement();
        Object value = config.getInitParameter(property);
        addPropertyValue(new PropertyValue(property, value));
        //...
    }
    //...
}

2、创建 DispatcherServlet 所对应的 BeanWrapper 对象,使用这个 BeanWrapper 对象利用上面得到的 pvs ,对 DispatcherServlet 中的属性进行赋值。BeanWrapper是Spring框架中较为基础的类,我们需要知道,其底层是通过反射调用了被包装bean的setter方法完成的属性赋值

//这里的this代表的是DispatcherServlet对象(tomcat调用DispatcherServlet的init方法进行初始化,自然会走到此处)
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); //创建DispatcherServlet的BeanWrapper对象
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());  // xxx
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));  //xxx
initBeanWrapper(bw); //初始化BeanWrapper,空实现,且没有发现有子类实现
bw.setPropertyValues(pvs, true); //对DispatcherServlet中的属性进行赋值

打xxx的部分,是向 BeanWrapper 中注册了一个 ResourceEditor 编辑器(String类型—>Resource类型)。

属性编辑器PropertyEditor:负责将 “String类型” 转换为 “其它类型” 的一种类型转换器

ResourceEditor 编辑器的作用:假如 DispatcherServlet 中有一个成员属性是 Resource 类型,由于我们在web.xml中定义的信息都是以String字符串形式体现的,所以在赋值之前就要进行一下类型转换,那么就会用到 ResourceEditor
注:BeanWrapper本身会拥有一些默认的编辑器,可以支持大多数情况下的 String—> Object 类型转换

总结

HttpServletBean类的作者是Spring之父Rod Johnson。作为POJO编程哲学的大师,他在HttpServletBean这个类的设计中,运用了依赖注入思想完成了< init-param>配置元素的读取。他抽离出HttpServletBean这个类的目的也在于此,就是 “以依赖注入的方式来读取< init-param>配置信息”,而且这里很明显是一种setter注入。明白了HttpServletBean类的设计思想,我们也就知道可以如何从中获益。具体来说,我们继承HttpServletBean类(就像DispatcherServlet做的那样),在类中定义一个属性,为这个属性加上setter方法后,我们就可以在< init-param>元素中为其定义值。在类被初始化后,值就会被注入进来,我们可以直接使用它,从而避免了样板式的getInitParameter()方法的使用。

也就是说:有了此 try-catch 代码块的处理,那么 DispatcherServlet 中带有set方法的成员属性,都可以在 web.xml 中进行配置,从而被注入相应的值。

第二部分代码

留给子类继续初始化。主要逻辑在 FrameworkServlet 中

//模板方法,留给子类继续初始化
initServletBean();

这部分代码,就是创建SpringWeb容器的地方。(模板方法,留给了子类实现)
具体的实现流程看下篇

总结

1、DispatcherServlet本质上是一个Servlet,在对象创建完成后,总会执行 init 方法进行初始化,而 init() 方法定义在其父类 HttpServletBean 中
2、HttpServletBean 完成的任务就是:让我们拥有了 “ 可以在 web.xml 中配置 DispatcherServlet 的属性值 ” 的能力。
3、创建及初始化SpringWeb容器的任务留给了子类 FrameworkServlet

参考文档:1、《看透SpringMVC源代码分析与实践》 2、https://blog.51cto.com/u_15861563/5823557

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值