Spring MVC 源码解析:跟踪一次简单的请求处理过程

上一篇我们分析了 web 环境下容器的初始化过程,探究了在 web 项目启动过程中,Spring MVC 所执行的一系列初始化工作,本篇中我们将一起来跟踪一次 Spring MVC 对于请求的具体处理过程,从整体上对 Spring MVC 的逻辑处理进行感知,先把握整体,后追究细节。

我们定义的控制器方法很简单,接收请求参数,然后记录到 Model 中并回显到页面上,实现如下:

1
2
3
4
5
6
7
8
9
// http://localhost:8080/demo/hello?name=zhenchao
@RequestMapping("/hello")
public ModelAndView hello(@RequestParam("name") String name) {
log.info("Do demo hello, name={}", name);
ModelAndView mav = new ModelAndView();
mav.setViewName("hello");
mav.addObject("name", name);
return mav;
}

当我们在浏览器中输入请求地址 http://localhost:8080/demo/hello?name=zhenchao 的时候,在请求到达服务器之前会经历域名解析、TCP连接、数据发送等过程,而这些都不是本文所要关注的重点,我们所要关注的是请求到达了 servlet 容器,执行一系列操作之后准备向客户端发送响应这中间的过程,排除服务器软件解析和封装的操作。

image

上面的时序图展示了本次请求处理的完整过程,接下来我们从源码层面对整个过程进行分析。当该请求到达服务器之后,服务器首先会为该请求创建一个 socket 连接,然后创建对应的 request 和 response 对象封装请求信息,并交由相应的 servlet 进行处理。请求首先会进入 HttpServlet 的 service(ServletRequest req, ServletResponse res) 方法,该方法会将 ServletRequest 和 ServletResponse 分别转换成 HttpServletRequest 和 HttpServletResponse 对象,然后调用 service(HttpServletRequest request, HttpServletResponse response) 方法进行处理,FrameworkServlet 覆盖实现了该方法:

1
2
3
4
5
6
7
8
9
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
// PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新
this.processRequest(request, response);
} else {
super.service(request, response);
}
}

该方法主要功能是判定当前请求的方法类型,如果是 PATCH 方法或未知的方法类型则调用 processRequest 进行处理,否则直接委托父类的 service(HttpServletRequest request, HttpServletResponse response) 方法。而我们这里是以 GET 方法请求,所以直接进入 else 分支,即进入 HttpServlet 的 service 方法,该方法也是一个决策方法,用于判断当前的请求类型,并调用相应的 do 方法,这里我们会进入 doGet 方法逻辑,位于 FrameworkServlet 中的覆盖:

1
2
3
4
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}

这里只是简单的调用了 processRequest 方法,前面在判定为 PATCH 请求方法类型时调用的也是此方法,而 GET 方法这样绕一下也是为了对请求目标对象的变更时间进行处理:

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
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;

// 暂存 LocaleContextHolder 中持有的之前的 LocaleContext 对象(存放当前的语言环境)
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 从当前请求中构建 localeContext 对象
LocaleContext localeContext = this.buildLocaleContext(request);

// 暂存 RequestContextHolder 中持有的之前的 RequestAttributes 对象(用于管理 request 和 session 中的属性)
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 从当前请求中构建 ServletRequestAttributes 对象
ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);

// 获取异步处理管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

// 将由当前请求构建的 localeContext 和 requestAttributes 设置到 LocaleContextHolder 和 RequestContextHolder 中
this.initContextHolders(request, localeContext, requestAttributes);

try {
// 核心方法
this.doService(request, response);
} catch (ServletException ex) {
failureCause = ex;
throw ex;
} catch (IOException ex) {
failureCause = ex;
throw ex;
} catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
} finally {
// 恢复之前的配置,不影响其它操作
this.resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
// 省略此处的 debug 日志

// 发布请求处理完成事件消息
this.publishRequestHandledEvent(request, response, startTime, failureCause);
}
}

processRequest 首先获取并记录之前的 LocaleContext 和 RequestAttributes 对象,然后基于当前的请求构造新的 LocaleContext 和 RequestAttributes 对象并调用 initContextHolders 方法将其记录到相应的 holder 中,然后调用核心方法 doService 继续处理请求,等到该方法返回时会利用之前记录的 LocaleContext 和 RequestAttributes 对象更新对应的 holder。接下来我们继续探究 doService 方法:

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
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中的attribute,便于后续恢复
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}

// 记录一些属性
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext()); // 应用上下文对象
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); // localeResolver
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); // themeResolver
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource()); // themeSource

// flashMap 相关参数,主要用于 redirect 时的参数传递
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 {
// 核心方法
this.doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// 还原之前备份的attribute
if (attributesSnapshot != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}

该方法主要还是为调用 doDispatch 方法做一些准备工作,记录了一些信息到 attribute 中。这里对 FlashMap 做一下说明,FlashMap 的主要功能是简化 redirect 请求传参,我们都知道记录在 request 中的参数在 redirect 时无法带到目标方法,基于纯 JSP 开发过 web 服务的同学一定曾经为此机制感到苦恼过,而解决这种问题最朴素的方法就是将参数拼接在请求地址的后面,但是这样的方式除了有长度限制之外,有时候还会有一定的安全问题。Spring MVC 则提供了 FlashMap 组件以解决这一问题,我们可以通过将需要 redirect 时传递的参数记录到 FlashMap 中即可实现传递,框架会自动将这些参数记录到目标接口的 Model 中,而这一机制本质上是基于 session 实现的。下面来举个例子帮大家回忆一下 FlashMap 的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping("/redirect")
public String redirectParams(HttpServletRequest request, RedirectAttributes ra) {
/*1. 基于 RedirectAttributes 获取*/
ra.addFlashAttribute("user_id", RandomUtils.nextLong()); // 放在flashMap中
ra.addAttribute("username", "zhenchao"); // 拼接在请求地址的后面

/*2. 基于 RequestContextUtils 工具类来获取*/
FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
flashMap.put("order_id", RandomUtils.nextLong());

/*3. 更加底层的方法*/
flashMap = (FlashMap) request.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
flashMap.put("imei", RandomStringUtils.randomAlphanumeric(32));

return "redirect:display-result";
}

我们可以通过 RedirectAttributes 记录需要 redirect 传递的参数,RedirectAttributes 提供了两种方式,addFlashAttribute 方法会将参数记录到 FlashMap 对象中进行传递,而 addAttribute 方法则会将参数拼接在请求地址后面进行传递;此外我们还可以通过 RequestContextUtils 获取 FlashMap 对象直接往里面注入值,而这一方法本质上与上面列子中的第三种方法是相同的,框架最终还是以 DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE 作为 key 将 FlashMap 对象记录在 attribute 中。

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
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为MultipartHttpServletRequest类型
processedRequest = this.checkMultipart(request);
multipartRequestParsed = (processedRequest != request); // 标记是否是上传请求

// 依据 request 寻找对应的 Handler (包括控制器方法和拦截器)
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
// 未找到对应的 Handler
this.noHandlerFound(processedRequest, response);
return;
}

// 依据当前的 handler 寻找对应的 HandlerAdapter
HandlerAdapter adapter = this.getHandlerAdapter(mappedHandler.getHandler());

// 对于 GET 或 HEAD 方法检查目标页面是否有变更(Last-Modified)
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = adapter.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
// 页面未变更,且是Get方法
return;
}
}

// 调用所有拦截器的 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// 激活 Handler 进行逻辑处理并返回视图(这里会调用我们所写的控制器方法)
mv = adapter.handle(processedRequest, response, mappedHandler.getHandler());

// 需要异步处理
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

// 检测,如果 Handler 没有设置视图,则使用默认视图
this.applyDefaultViewName(processedRequest, mv);

// 调用所有拦截器的 postHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理返回结果(异常处理、页面渲染、执行拦截器的 afterCompletion 方法等)
this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable err) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));
} finally {
// 判断是否需要执行异步请求
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else {
// 删除上传请求过程中的资源
if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
}

doDispatch 在实现上定义了整个处理的执行流程,而具体的执行逻辑都交由相应的类来实现,可以概括如下:

  1. 检测当前是否是文件上传的请求,如果是的话则调用 MultipartResolver 的 resolveMultipart 方法解析 request 封装为 MultipartHttpServletRequest 对象
  2. 调用 HandlerMapping 获取当前请求对应的处理器
  3. 获取当前处理器对应的 HandlerAdapter
  4. 对于 GET 或 HEAD 方法检查目标页面是否有变更,没有的话直接返回
  5. 应用所有拦截器的 prehandle() 方法
  6. 激活处理器进行逻辑处理并返回视图,这一步最终会调用开发者自定义的实现
  7. 检测上一步如果没有设置视图,则使用默认视图
  8. 应用所有拦截器的 postHandle() 方法
  9. 处理返回结果,包括异常处理、页面渲染,以及执行拦截器的 afterCompletion() 方法
  10. 针对文件上传请求,执行临时数据的清理

整个处理过程的背后是 Spring MVC 九大基础核心类的支撑,在此我们先不展开,后面会用专门的篇章逐一分析,我们先简单介绍一下过程中涉及到的一些概念,以对整个过程进行整体上的感知。

上述过程涉及到三个核心组件的分工合作:Handler、HandlerMapping,以及 HandlerAdapter。方法中首先通过 HandlerMapping 基于当前请求找到对应的 Handler,然后基于 Handler 找到对应的 HandlerAdapter,最后通过 HandlerAdapter 激活 Handler 来执行业务逻辑并返回响应视图。

Handler 可以理解为处理器,简单来说它是对附加了拦截器的控制器方法的封装;而 HandlerMapping 则是用来建立请求与对应 Handler 之间的映射关系,因为一个请求最终还是要落到一个控制器方法上去处理,而 HandlerMapping 则负责中间的过程映射;最后再来看看 HandlerAdapter ,有了 Handler 和 HandlerMapping,我们可以找到一个请求对应的处理方法,并对该请求进行处理和响应,似乎已经完美了,那么我们还缺什么呢?这里还是需要记住一点,Spring MVC 是基于 servlet 的 MVC 框架,不像 Play FrameworkVert.x-Web 等 web 框架另起炉灶的解决方案,所以我们的请求最终还是要交由 servlet 去处理。那么我们为什么要引入 MVC 框架,而不直接基于 servlet 来进行 web 开发呢?简单点来说就是框架比原生的 servlet 好用,我们可以灵活的实现我们的控制器方法,而不需要受 servlet 方法约定的束缚,而这灵活的背后就是 HandlerAdapter 的功劳,HandlerAdapter 的作用从根本上来说就是建立 Handler 与原生 servlet 方法间的适配,所以它是一个适配器。

接下来我们对上述过程的几个核心步骤展开继续分析,首先看一下通过 HandlerMapping 获取 Handler 的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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() + "'");
}
// 调用 HandlerMapping 的 getHandler() 方法
HandlerExecutionChain handler = hm.getHandler(request);
// 获取到一个即返回
if (handler != null) {
return handler;
}
}
return null;
}

该方法通过遍历已注册的 HandlerMapping 来获取当前请求对应的 HandlerExecutionChain(封装了具体的 Handler 和对应的拦截器),核心还是在于调用了 HandlerMapping 的 getHandler() 方法:

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 final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 依据 request 获取对应的 Handler
Object handler = this.getHandlerInternal(request);

// 没有找到,使用默认的
if (handler == null) {
handler = this.getDefaultHandler();
}

// 默认的都没有
if (handler == null) {
return null;
}

// 如果获取到的是 beanName,则从容器中去拿 name 对象的实例
if (handler instanceof String) {
String handlerName = (String) handler;
handler = this.getApplicationContext().getBean(handlerName);
}

// 创建 HandlerExecutionChain 对象并附加定义的拦截器
HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);

// 如果是CORS(跨域资源共享)请求
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}

方法首先调用了 getHandlerInternal 方法以解析当前请求对应的 Handler,如果解析不到则获取默认的 Handler。如果当前的 Handler 还仅仅是一个 beanName,则会从容器中去获取对应的实例,然后利用 HandlerExecutionChain 对当前 Handler 实例进行包装,并附加定义的拦截器。最后会检测并处理 CORS(Cross-origin resource sharing) 请求。这里最核心的当属 getHandlerInternal 方法,该方法尝试获取当前请求所映射的自定义控制器方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 1. 截取用于请求的有效url路径(/demo/hello)
String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock();
try {
// 2. 基于 lookupPath 和 request 找到对应的 HandlerMethod
HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);

// 省略 debug 日志

// 3. 基于当前 HandlerMethod 对象创建新的 HandlerMethod 对象(如果仅仅是 beanName 则进行从容器中获取对应的实例)
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
} finally {
this.mappingRegistry.releaseReadLock();
}
}

方法的逻辑还是很简洁的,针对本次的请求在第一步时获取到有效的请求路径 /demo/hello,然后调用 lookupHandlerMethod(String lookupPath, HttpServletRequest request) 方法基于请求路径获取对应的处理器方法:

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
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
// 基于请求路径获取对应的匹配信息(mappingRegistry在initHandlerMethods中进行初始化)
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
// 将匹配条件加入 matches 集合中
this.addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 无法根据 lookupPath 获取对应的匹配条件,则将所有条件加入 matches 集合中
this.addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

// 对包含匹配条件和 Handler 的 matches 集合排序,并取第一个作为最佳匹配
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(this.getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
// 如果存在多个最佳匹配则抛出异常
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
this.handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod; // org.zhenchao.spring.mvc.controller.DemoController#hello
} else {
// 不存在匹配的 HandlerMethod
return this.handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}

lookupHandlerMethod 的主要逻辑是基于当前的请求路径从 mappingRegistry 集合(在 AbstractHandlerMethodMapping 中基于 InitializingBean 机制执行初始化)中获取对应的请求匹配信息 RequestMappingInfo 集合,然后遍历集合从 mappingRegistry 中拿到 RequestMappingInfo 对象所匹配的 HandlerMethod ,并由 Match 对象一起封装记录到匹配信息集合中,最后对集合进行排序并选择排在第一个的 HandlerMethod 作为最佳匹配,同时校验是否存在多个最佳匹配,如果存在则抛 IllegalStateException 异常。

到这里通过 HandlerMapping 获取对应 Handler 的逻辑已经走完,方法返回了创建的 HandlerExecutionChain 对象,接下来我们继续回到 doDispatch 方法中,基于刚刚获取到的 Handler 调用 getHandlerAdapter 方法获取对应的 HandlerAdapter:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
// 遍历获取已注册的处理器适配器
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
// 判断是否支持当前的处理器,支持即返回
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
1
2
3
4
public final boolean supports(Object handler) {
// supportsInternal 直接返回 true
return (handler instanceof HandlerMethod && this.supportsInternal((HandlerMethod) handler));
}

适配器的获取过程相当简单,如上述注释。接下来继续来看 HandlerAdapter 执行处理器具体逻辑的过程:

1
2
3
4
// org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return this.handleInternal(request, response, (HandlerMethod) handler);
}
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
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// 检查是否支持当前的请求方法,以及是否需要session
this.checkRequest(request);

// 激活处理器方法
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// No HttpSession available -> no mutex necessary
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// No synchronization on session demanded at all...
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}

// 处理 Cache-Control 首部
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
this.prepareResponse(response);
}
}

return mav;
}

上述方法的逻辑已经注释的比较清晰,首先检查当前的请求方法是否被支持,以及是否需要为当前请求创建 session,然后激活处理器方法,最后为响应头添加 Cache-Control 逻辑,来进一步探究 invokeHandlerMethod 方法:

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
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 获取 WebDataBinderFactory
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
// 获取 ModelFactory
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);

// 基于 HandlerMethod 创建 ServletInvocableHandlerMethod 对象
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

// 创建 ModelAndViewContainer
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
// 初始化 Model
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}

// 激活处理器方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}

// 构建 ModelAndView 对象并返回
return this.getModelAndView(mavContainer, modelFactory, webRequest);
} finally {
// 设置 requestActive = false
webRequest.requestCompleted();
}
}

该方法所做的工作主要可以概括为三个步骤:1.构造并初始化处理器方法的执行条件;2.激活调用处理方法;3.构造 ModelAndView 对象并返回。我们来逐步探究各个过程,在第一步中我们主要来深入一下 Model 的初始化过程:

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
// org.springframework.web.method.annotation.ModelFactory#initModel
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception {

// 从 SessionAttributes 中取出保存的参数,合并到 ModelAndViewContainer 对象中
Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
container.mergeAttributes(sessionAttributes);

// 执行 @ModelAttribute 注解的方法,并将结果记录到 Model 中
this.invokeModelAttributeMethods(request, container);

/*
* 遍历 @ModelAttribute 和 @SessionAttribute 共同注解的参数
* 不要求这两个注解出现在同一个方法中,主要作用是利用其它处理器
* 方法中保存的 SessionAttributes 属性来设置当前方法注解了 @ModelAttribute 的参数
*/
for (String name : this.findSessionAttributeArguments(handlerMethod)) {
if (!container.containsAttribute(name)) {
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
}
container.addAttribute(name, value);
}
}
}

在整个初始化过程中,方法先取出 SessionAttributes 中保存的参数合并到 ModelAndViewContainer 对象中;然后执行 @ModelAttribute 注解的方法,这一步的最终目的是将方法返回值设置到 Model 中;最后遍历被 @ModelAttribute@SessionAttribute 同时注解的属性,这里的目的还是将对应的属性值记录到 Model 中,区别在于这里的属性来源是其它控制器方法记录到 session 中的值。

接下来我们分析一下处理器方法的激活调用过程,该方法主要做了两件事情:1.激活处理器方法并拿到方法返回值;2.调用注册的 HandlerMethodReturnValueHandler 对返回值进行处理。

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
// org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 激活自定义控制器方法
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
this.setResponseStatus(webRequest);

if (returnValue == null) {
if (this.isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(this.responseReason)) {
mavContainer.setRequestHandled(true);
return;
}

mavContainer.setRequestHandled(false);
try {
// 遍历调用注册的 HandlerMethodReturnValueHandler 对返回值进行处理
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}

到这里我们已经离我们自定义的控制器方法已经很近了,接下去的逻辑就是拿到我们自定义控制器方法的参数值,并基于反射执行该方法,这一过程位于 invokeForRequest 中,逻辑比较简单,这里不再展开。如果把整个跟踪的过程比作是一次旅行,那么到这里我们基本上算是到达了旅行的目的地,而接下去将开始返程了,我们首先回到 RequestMappingHandlerAdapte 的 invokeHandlerMethod 中,在执行完上面的 invokeAndHandle 后,我们继续往下执行 ModelAndView 对象的获取过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,  ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
// 更新 Model
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}

// 构建 ModelAndView 对象
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
// 设置视图
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
// 将 redirect 参数记录到 outputFlashMap 中
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}

该过程首先更新 Model,然后创建 ModelAndView 对象并设置视图,如果是 redirect 返回还会处理我们添加到 FlashMap 中的跳转参数,整个过程唯一间接实现的逻辑是更新 Model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
ModelMap defaultModel = container.getDefaultModel();
if (container.getSessionStatus().isComplete()) {
// 如果在处理器中调用了 SessionStatus.setComplete() 方法,则会清空 SessionAttributes
this.sessionAttributesHandler.cleanupAttributes(request);
} else {
// 将 Model 中的相应属性记录到 SessionAttributes 中
this.sessionAttributesHandler.storeAttributes(request, defaultModel);
}
// 判断如果需要进行页面渲染,则给 Model 中相应的参数添加 BindingResult
if (!container.isRequestHandled() && container.getModel() == defaultModel) {
this.updateBindingResult(request, defaultModel);
}
}

updateModel 是 ModelFactory 中主要包含的两个方法之一(另外一个是 initModel,已在前面解读过),该方法的功能如注释所示,主要做了两件事情:

  1. 对于 SessionAttributes 进行处理,如果在控制器中调用了 SessionStatus.setComplete() 方法,那么这里会执行对 SessionAttributes 的清空,否则将 Model 中相应的属性记录到 SessionAttributes 中。
  2. 判断是否需要执行页面渲染,若需要则为相应的属性添加 BindingResult 对象。

getModelAndView 方法执行完成之后,接下来我们回到 doDispatch 方法继续后续的处理过程。如果处理器方法未设置视图,则接下来会为本次请求设置一个默认的视图,然后调用所有拦截器的 postHandle() 方法,接着开始执行页面的渲染逻辑:

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
private void processDispatchResult(
HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
throws Exception {

boolean errorView = false;

// 1. 如果处理过程出现异常,则进行处理
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
} else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = this.processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}

// 2. 对页面进行渲染
if (mv != null && !mv.wasCleared()) {
this.render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request handling");
}
}

// 如果启用了异步处理则返回
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return;
}

// 3. 激活拦截器的 afterCompletion 方法
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}

上述方法首先会判断前面的处理过程中是否出现异常,如果有异常则需要对异常信息进行处理,需要注意的一点是这里的处理逻辑位于页面渲染之前,如果渲染过程中出现了异常则不会被处理。具体的异常处理过程不再展开,留到以后专门讲解,下面来看一下页面的渲染过程:

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
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. 设置响应 locale
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);

// 2. 获取视图对象
View view;
if (mv.isReference()) {
// 如果是视图名称,则解析视图名称对应的视图对象
view = this.resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'");
}
} else {
// 已经包含了视图对象
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + getServletName() + "'");
}
}

if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}

// 3. 页面渲染过程
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
} catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'", ex);
}
throw ex;
}
}

整个方法分为三步来执行,如注释所示,其中第一、二步比较基础,而第三步的页面渲染过程则委托给视图对象来执行:

1
2
3
4
5
6
7
8
9
10
11
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + " and static attributes " + this.staticAttributes);
}
// 属性解析(包含动态和静态属性值)
Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);
// 响应准备工作
this.prepareResponse(request, response);
// 页面渲染
this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);
}

渲染的过程首先就是解析请求过程的动态和静态属性值,并封装到一个 map 中以便后续取值;然后执行一些准备工作,默认的实现是为了修复 IE 浏览器中响应 HTTPS 下载请求的 bug;最后执行渲染逻辑,这一块留到后续分析视图实现时进行针对性的说明。

继续回到 doDispatch,拦截器 HandlerInterceptor 中声明了三个方法:preHandle、postHandle,以及 afterCompletion。前面的执行过程围绕处理器方法分别在前后执行了 preHandle 和 postHandle,而 afterCompletion 也在页面渲染过程完成之后被触发。如果本次是上传请求,那么接下来会执行清理上传过程中产生的临时文件的过程,到这里就完成了 doDispatch 整个方法过程的执行。

回到开始位于 FrameworkServlet 中的 processRequest 方法,记得一开始我们对之前的 LocaleContext 和 RequestAttributes 进行了暂存,而此时则需要将相应的 holder 利用这些暂存的值恢复到最开始的状态,从而不影响其它请求的执行,并将 requestActive 置为 false,标记本次请求的完成,最后调用 publishRequestHandledEvent 方法,发布请求处理完成的通知消息。

到这里,这一次简单的请求就处理完成了,我们可以在浏览器中看到具体渲染后的页面。虽然是一个简单的请求,但是通过分析我们也看到框架背后复杂的处理逻辑,这还仅仅是整个处理过程的主线流程,后续我们会针对各个核心支撑组件逐一展开来进行针对性的分析,本篇文章的目的主要还是对整个请求有一个全局的感知,后续我们在具体分析各个组件时时可以具体定位到具体是发生在哪一步的请求,从而能够联系上下文进行理解。