Spring源码学习【八】SpringMVC(一)DispatcherServlet

前言

Web环境是Spring框架的重要应用场景,而SpringMVC又是Web开发中一个常用的框架,因此我们有必要学习一下SpringMVC的实现原理。

回到Web项目的配置文件web.xml中,在使用SpringMVC时我们需要进行如下的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/springMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

熟悉Spring的同学对以上的配置肯定不陌生,这里配置了一个DispatcherServlet,这个Servlet是由Spring实现的,是SpringMVC最核心的部分,如上配置的这个Servlet会接收所有的请求,最终将请求分发至对应的Controller进行处理,下面我们就从DsipatcherServlet入手,学习SpringMVC的实现。

源码学习

首先,来看一看DsipatcherServlet的类继承关系(省略了部分接口):

从上图中可以看到,DispatcherServlet间接继承了HttpServlet,可用于处理Http请求。

既然DispatcherServlet也是Servlet家族中的一员,那么它肯定要遵循Servlet的生命周期,即:

  • 初始化阶段,调用init()方法
  • 响应客户请求阶段,调用service()方法
  • 销毁阶段,调用destroy()方法

有了这些了解,我们就可以顺着DispatcherServlet的生命周期来学习SpringMVC的实现了。

初始化阶段 -> init()

首先,定位到初始化阶段,在这个阶段会调用init()方法,这个方法定义在Serlvet接口中,我们可以发现这个方法的最终实现在DispatcherServlet的父类HttpServletBean中,这个方法被定义为final方法,不可被子类覆盖,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {

@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// 获取配置的 init parameters,设置Bean的属性
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 (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 这个方法是一个模板方法,默认实现为空,交由其子类FrameworkServlet实现
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}

}

在上面的代码中,首先获取了init parameters,也就是web.xml中的节点,并将init parameters设置为这个Servlet Bean的属性,然后调用了子类FrameworkServlet的initServletBean()方法,进行额外的初始化处理,FrameworkServlet代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

@Override
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 | 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");
}
}

protected WebApplicationContext initWebApplicationContext() {
// 首先从ServletContext中取得根应用上下文,也就是上一篇中在ContextLoader中创建的IOC容器
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 若在构造Servlet时已经注入应用上下文,则直接使用
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) {
// 构造Servlet时未注入应用上下文,则到ServletContext中获取
wac = findWebApplicationContext();
}
if (wac == null) {
// ServletContext中未获取到,则创建一个应用上下文
// 这里创建应用上下文的处理与上一篇中的处理类似,不同之处在于创建完成后即进行了应用上下文的配置和刷新
wac = createWebApplicationContext(rootContext);
}

if (!this.refreshEventReceived) {
// 触发初始化刷新,这里指的不是应用上下文的刷新
// 这个方法是一个模板方法,默认实现为空,交由子类DisispatcherServlet实现
onRefresh(wac);
}

if (this.publishContext) {
// 将当前的应用上下文发布到ServletContext中,key为:FrameworkServlet.class.getName() + ".CONTEXT." + servletName
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;
}

}

上面的代码中,取得了一个应用上下文,作为了根IOC容器的子容器,这样,DispatcherServlet中的IOC容器就建立起来了,细心的同学会发现,在返回应用上下文之前调用了onRefresh(wac)方法,这个方法由其子类DispatcherServlet实现,用于初始化Web层需要的策略,下面让我们一起来看一看这部分的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DispatcherServlet extends FrameworkServlet {

@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}

}

从上面的代码中可以看到,在获取应用上下文的过程中初始化了DispatcherServlet中需要的各种解析器,其中包括文件解析器、区域解析器、主题解析器等。

解析器的初始化过程大体相同,都是从应用上下文中取得相应的Bean,若不存在则使用默认解析器策略。

具体关于各解析器的介绍大家可以参考一篇博客:SpringMVC解析器

到这里,DispatcherServlet的初始化阶段就完成了,在这个过程中,一方面创建了DispatcherServlet的IOC容器,并将这个IOC容器作为根IOC容器的子容器,另一方面,初始化了DispatcherServlet需要的各种解析策略,接下来,DispatcherServlet将会在处理HTTP请求时发挥重要的作用。

响应客户请求 -> service()

我们知道Servlet在接收到客户请求后会调用service()方法,根据请求类型执行doGet、doPost等一系列方法,在DispatcherServlet的继承体系中,由DispatcherServlet的父类FrameworkServlet重写了HttpServlet中的service()方法以及doGet()、doPost() 等一系列方法,下面以常用的HTTP请求方法来看一看FrameworkServlet的主要实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
// 这里添加了对patch请求的支持
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
// 这里调用HttpServlet的service方法,根据请求类型调用该类中重写的doGet、doPost等方法
super.service(request, response);
}
}

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}

@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}

@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}

}

从上面的代码中可以看到, DispatcherServlet接收到用户请求后,会调用父类FrameworkServlet中的service()方法,最终根据请求类型调用FrameworkServlet中重写的doGet、doPost等方法,这些方法都调用了processRequest()方法,下面让我们看一下processRequest()的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 获取LocaleContext(语言环境), 用作备份
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 根据当前request创建LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
// 获取RequestAttributes,用作备份
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 根据当前request、response创建ServletRequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// 为当前请求注册一个拦截器,用于在请求执行前后异步初始化和重置FrameworkServlet的LocaleContextHolder和RequestContextHolder
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 根据当前request初始化ContextHolders
initContextHolders(request, localeContext, requestAttributes);
try {
// 具体处理请求,是一个模板方法,由子类DispatcherServlet实现
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
// 使用备份重置ContextHolders
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
// 向该应用注册的所有监听器发布RequestHandledEvent事件
// 监听器可以通过实现ApplicationListener接口来实现
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}

}

在上面的代码中我们可以看到一个用于处理请求的核心方法:doService(request, response),这个方法是一个模板方法,由其子类DispatcherServlet实现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class DispatcherServlet extends FrameworkServlet {

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}

// 对于include请求,首先保存request属性快照,用于请求后恢复属性
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}

// 为request设置一些必要的属性
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); // 应用上下文
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); // 语言环境解析器
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); // 主题解析器
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // 主题源,默认将应用上下文作为主题源

if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}

try {
// 分发请求
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
// 恢复include请求属性
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}

}

doService方法比较简单,主要是为request设置一些必要的属性,接下来调用了doDispatch方法进行请求的分发,这是SpringMVC中的核心功能,doDispatch方法中主要进行了如下的处理:

  • 处理拦截
  • 处理请求
  • 解析View

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class DispatcherServlet extends FrameworkServlet {

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
// 判断是否为文件上传请求,这里会尝试将request转换为文件上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

// 通过HandlerMapping获取请求匹配的处理器:一个HandlerExecutionChain类的实例
// 这个处理器中包含一个请求处理器和多个拦截器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 未找到匹配的处理器,返回404错误
noHandlerFound(processedRequest, response);
return;
}

// 根据匹配的请求处理器获取支持该处理器的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
// 对于get请求,如果从上次修改后未进行修改则不再对请求进行处理
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 执行拦截器 -> preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// 执行处理器处理请求,返回ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 若ModelAndView无视图名,则为其设置默认视图名
applyDefaultViewName(processedRequest, mv);
// 执行拦截器 -> postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理异常,解析View
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 触发拦截器的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// 触发拦截器的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// 清除文件上传请求使用的资源
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

}

到这里,客户的请求就相应完成了。在这个过程中,首先处理匹配的处理器中的拦截器,然后通过处理器处理客户的请求,最后通过视图解析器解析和渲染视图,这里还有许多细节未深入分析,我们将在后续继续学习。

销毁阶段 -> destroy()

Servlet的销毁阶段会调用destroy()方法,这个方法的实现在FrameworkServlet中,实现比较简单,就是将Servlet中的IOC容器关闭,代码如下:

1
2
3
4
5
6
7
8
9
10
11
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

@Override
public void destroy() {
getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
// Only call close() on WebApplicationContext if locally managed...
if (this.webApplicationContext instanceof ConfigurableApplicationContext && !this.webApplicationContextInjected) {
((ConfigurableApplicationContext) this.webApplicationContext).close();
}
}
}

总结

本篇中,顺着Servlet的生命周期大致分析了SpringMVC的核心类DispatcherServlet的实现,对SpringMVC的请求控制有了一定的了解,但在DispatcherServlet处理客户请求的部分有许多内容未深入分析,需要进一步学习。

参考资料:《Spring技术内幕》

最后的最后,安利一下自己写的一个Java代码生成工具,能够方便的生成Spring、SpringMVC、Mybatis架构下的Java代码,希望能对大家有所帮助,地址:Java代码生成器:Generator