首先需要明确,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