一句话总结:
主角是这三个类:HttpServletBean、FrameworkServlet和DispatcherServlet,他们有继承关系,见下图,他们使用了模板方法的设计模式,所以一般都是父类会留下一个模板方法,子类去覆盖这个模板方法,以实现子类的特殊功能。
可以看到在Servlet的继承结构中一共有5个类,GenericServlet和HttpServlet在java的servlet规范中,剩下的三个类HttpServletBean、FrameworkServlet和DispatcherServlet是Spring MVC提供的(前2者是抽象类,最后一个是非抽象类)。
这三个类直接实现三个接口:EnvironmentCapable、EnvironmentAware和Application-ContextAware。
- XXXAware在spring里表示对XXX可以感知,通俗点解释就是:如果在某个类里面想要使用spring的一些东西,就可以通过实现XXXAware接口告诉spring,spring看到后就会给你送过来,而接收的方式是通过实现接口唯一的方法set-XXX。
比如,有一个类想要使用当前的ApplicationContext,那么我们只需要让它实现ApplicationContextAware接口,然后实现接口中唯一的方法void setApplicationContext(ApplicationContext applicationContext)就可以了,spring会自动调用这个方法将applicationContext传给我们,我们只需要接收就可以了!很方便吧!
- EnvironmentCapable,顾名思义,当然就是具有Environment的能力,也就是实现类可以提供Environment,所以EnvironmentCapable唯一的方法是Environment getEnvironment(),用于实现EnvironmentCapable接口的类,就是告诉spring,实现类它可以提供Environment,当spring需要Environment的时候就会调用实现类的getEnvironment方法跟它要。其实getEnvironment()函数里返回的Environment是从哪来的呢?答案是EnvironmentAware接口的setEnvironment函数,所以说要实现EnvironmentCapable提供Environment,那么你必须实现EnvironmentAware获取Environment。
1. HttpServletBean
它实现了HttpServlet,HttpServlet中有一个无参数的init()模板方法,HttpServletBean需要覆盖这个模板方法,做一些定制操作。
// org.springframework.web.servlet.HttpServletBean
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
try {
//将Servlet中配置的参数封装到pvs变量中,requiredProperties为必需参数,如果没配置将报异常
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new esourceEditor(resourceLoader, getEnvironment()));
//模板方法,可以在子类调用,做一些初始化工作。bw代表DispatcherServlet
initBeanWrapper(bw);
//将配置的初始化值(如contextConfigLocation)设置到DispatcherServlet
bw.setPropertyValues(pvs, true);
}catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// 模板方法,子类初始化的入口方法
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
BeanWrapper是干嘛的?
它是Spring提供的一个用来操作JavaBean属性的工具,使用它可以直接修改一个对象的属性。举个例子:
public class BeanWrapperTest {
public static void main(String[] args) {
User user = new User();
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(user);
bw.setPropertyValue("userName", "张三");
System.out.println(user.getUserName()); //输出张三
PropertyValue value = new PropertyValue("userName", "李四");
bw.setPropertyValue(value);
System.out.println(user.getUserName()); //输出李四
}
}
知道了BeanWrapper是干嘛的后,你一定会对这个注释有疑问:“bw代表DispatcherServlet”。创建bw的时候明明传的是this,这样它代表的应该是HttpServletBean,为什么注释说bw代表DispatcherServlet呢?
因为,HttpServletBean是一个抽象类,DispatcherServlet是间接实现了HttpServletBean的,所以这里的this指的是DispatcherServlet,而不是HttpServletBean。
解决了疑问以后,再来总结init()函数干了个啥:
- 首先将Servlet中配置的参数使用BeanWrapper设置到DispatcherServle的相关属性,
- 然后调用模板方法initServletBean,子类就通过这个方法初始化。
所以,它跟HttpServlet一样,HttpServletBean也提供了一个模板方法initServletBean(),供子类做定制化。
2. FrameWorkServlet
它继承了HttpServletBean,而HttpServletBean提供的模板方法是initServletBean,所以它的初始化入口方法应该是initServletBean。
// org.springframework.web.servlet.FrameworkServlet
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "':
initialization completed in " +
elapsedTime + " ms");
}
}
可以看到这里的核心代码只有两句:
- 一句用于初始化WebApplicationContext,也就是创建spring容器。
- 另一句用于初始化FrameworkServlet,而且initFrameworkServlet方法是模板方法,子类可以覆盖然后在里面做一些初始化的工作。
下面来看一下initWebApplicationContext方法:
// org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext initWebApplicationContext() {
//获取rootContext,rootContext早就在ContextLoaderListener已经被创建了,是属于Spring源码分析范畴的,这里就不分析它怎么来的了
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//如果已经通过构造方法设置了webApplicationContext
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 当webApplicationContext已经存在ServletContext中时,通过配置在Servlet中的contextAttribute参数获取
wac = findWebApplicationContext();
}
if (wac == null) {
// 如果webApplicationContext还没有创建,则创建一个
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 当ContextRefreshedEvent事件没有触发时调用此方法,模板方法,可以在子类重写
onRefresh(wac);
}
if (this.publishContext) {
// 将ApplicationContext保存到ServletContext中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
initWebApplicationContext方法做了三件事:
获取spring的根容器rootContext。(rootContext早就在ContextLoaderListener被调用时已经被创建了,是属于Spring源码分析范畴的,这里就不分析它怎么来的了)
-
获取或者创建webApplicationContext,并根据情况调用onRefresh方法。
获取或者创建webApplicationContext一共有三种方法:- 第一种方法是在构造方法中已经传递webApplicationContext参数,这时只需要对其进行一些设置即可。这种方法主要用于Servlet3.0以后的环境中,Servlet3.0之后可以通过代码添加ServletWebApplicationContext, 创建DispatcherServlet过程中会使用ServletContext.addServlet(new DispatcherServlet(servletAppContext))方式注册Servlet, 这种情况下new DispatcherServlet的时候带了参数, 而这个参数就是之前通过代码添加的ServletWebApplicationContext,所以构造函数里就有了webApplicationContext),这样就可以直接获取了。
- 第二种略过把
- 第三种方法是在前面两种方式都无效的情况下自己创建一个。常规SpringMVC项目就是使用的这种方式。创建过程在createWebApplicationContext方法中,createWebApplicationContext内部又调用了configureAndRefreshWebApplicationContext方法。
将webApplicationContext设置到ServletContext中。
上面说的第三种方法中的configureAndRefreshWebApplicationContext函数,代码如下:
// org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//获取创建类型,通过读取web.xml中的contextClass属性,如果没配则默认使用org.springframework.web.context.support.XmlWebApplicationContext
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
//检查创建类型
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());
// 将它的父Spring容器设置为Root WebApplicationContext
wac.setParent(parent);
//将web.xml中设置的contextConfigLocation参数传给wac,默认传入WEB-INFO/[ServletName]-Servlet.xml
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
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());
//添加监听ContextRefreshedEvent的监听器
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
// 执行refresh
wac.refresh();
}
3. DispatcherServlet
DispatcherServlet虽然继承了FrameworkServlet,但是它并没有去重写FrameworkServlet提供的initFrameworkServlet()模板函数。它比较重要的函数是doService(...)函数(也是FrameworkServlet提供的模板函数,Servlet容器有请求进来会间接回调到这个函数)。
所以,DispatcherServlet里面执行处理的入口方法应该是doService,不过doServic并没有直接进行处理,而是交给了doDispatch进行具体的处理。
- doService:在这里主要是对request设置了一些属性,如果是include请求还会对request当前的属性做快照备份,并在处理结束后恢复。最后将请求转发给doDispatch方法。
- doDispatch方法也非常简洁,从顶层设计了整个请求处理的过程。doDispatch中最核心的代码只要4句,它们的任务分别是:
①根据request从HandlerMapping找到Handler;
②根据Handler找到对应的HandlerAdapter;
③用HandlerAdapter处理Handler;
④调用processDispatchResult方法处理上面处理之后的结果(包含找到View并渲染输出给用户),对应的代码如下:
mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
3.1 doDispatch函数的具体处理流程
- doDispatch中首先检查是不是上传请求,如果是上传请求,则将request转换为MultipartHttpServletRequest,并将multipartRequestParsed标志设置为true。其中使用到了Multipart-Resolver。
- 然后通过getHandler方法获取Handler处理器链,其中使用到了HandlerMapping,返回值为HandlerExecutionChain类型,HandlerExecutionChain中包含着与当前request相匹配的Interceptor和Handler。
执行时先依次执行Interceptor的preHandle方法,最后执行Handler,返回的时候按相反的顺序执行Interceptor的postHandle方法。就好像要去一个地方,Interceptor是要经过的收费站,Handler是目的地,去的时候和返回的时候都要经过加油站,但两次所经过的顺序是相反的。
getHandler代码如下:
// org.springframework.web.servlet.DispatcherServlet
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
接下来是处理GET、HEAD请求的Last-Modified。当浏览器第一次跟服务器请求资源(GET、Head请求)时,服务器在返回的请求头里面会包含一个Last-Modified的属性,代表本资源最后是什么时候修改的。在浏览器以后发送请求时会同时发送之前接收到的Last-Modified,服务器接收到带Last-Modified的请求后会用其值和自己实际资源的最后修改时间做对比,如果资源过期了则返回新的资源(同时返回新的Last-Modified),否则直接返回304状态码表示资源未过期,浏览器直接使用之前缓存的结果。
接下来依次调用相应Interceptor的preHandle
处理完Interceptor的preHandle后就到了此方法最关键的地方——让HandlerAdapter使用Handler处理请求,Controller就是在这个地方执行的。这里主要使用了HandlerAdapter.
Handler处理完请求后,如果需要异步处理,则直接返回,如果不需要异步处理,当view为空时(如Handler返回值为void),设置默认view,然后执行相应Interceptor的postHandle。设置默认view的过程中使用到了ViewNameTranslator
接下来使用processDispatchResult方法处理前面返回的结果,其中包括处理异常、渲染页面、触发Interceptor的afterCompletion方法三部分内容。
整个过程的流程图大致如下,中间是doDispatcher的处理流程图,左边是Interceptor相关处理方法的调用位置,右边是doDispatcher方法处理过程中所涉及的组件。图中上半部分的处理请求对应着MVC中的Controller也就是C层,下半部分的processDispatchResult主要对应了MVC中的View也就是V层,M层也就是Model贯穿于整个过程中。