Spring MVC 原理


十月的尾巴赶出来一篇吧,这周学习 Spring MVC 的原理。

十月份是放松的一个月,国庆出游,节后迎接朋友,还没怎么学习就过去了,回到生活的常态了,认真学习。


我一直不太清楚 Spring 官方文档的结构和入口,比如,似乎根本就不存在 Spring MVC 这个模块,它被包含在 Spring Web Flow 或是 Web on Servlet Stack 的模块中,而且这两个模块我也搞不懂是什么关系。

我大致梳理出来这么一个逻辑:Spring MVC 并不在 Spring 的顶层结构中,Spring Web 才是顶层的设计,Spring MVC 只是 Web 中的一种解决方案,如果不用它,还可以使用 Struts 等其他的网络应用框架(但是现在大家都在使用 Spring MVC)。这种逻辑跟 Spring 的设计调性有关,它们希望做得更开放包容一些。

我在去年刚入行时学习 Spring 全家桶,就反复看到这张奇丑无比的图(为什么行业的审美这么低……):

SpringMVC丑图

当时连 Java 的语法都写得不利索,按照这个学习更是像趟沼泽似的一脚深一脚浅。如今回来看就豁然开朗了很多。

Spring MVC 的核心中转站是 DispatcherServlet,网络请求的接收、执行、渲染视图、返回等,它跟 Spring 接触的全过程,都发生在这个类当中。这篇文章说是学习 Spring MVC 的原理,其实也只是学习这个 DispatcherServlet 类。

在学习这部分时,发现了写得非常好的博客,记录一下:《安迪源文-专栏-Spring MVC 分析》。


dispatcher 的意思是调度员,因此 DispatcherServlet 类是一个用于调度、中转的类,这是 Spring MVC 的前端控制器。它调用了很多个方法,把一个请求从接收到返回的所有方法逐个执行完毕,这中间一共有 5 个步骤,下面一个个步骤过。

以下分析基于 spring-webmvc-5.2.9.RELEASE 版本。

-1. 进入 DispatcherServlet 之前

Spring MVC 的底层使用的是 Tomcat。一个 HTTP 请求,将首先到达 Tomcat,再又 Tomcat 转发给 Spring MVC 去处理。

一个 HTTP 请求根据 Java EE 的网络协议,将首先进入到 Servlet 接口中,就是 Tomcat,Tomcat 将这个请求处理后,封装出两个对象:HttpServletRequestHttpServletResponse,最终交付给 Spring MVC,进入到 DispatcherServlet 类中。

进入 DispatcherServlet 类之前的动作,需要理解网络通信和 Tomcat 的设计,这部分暂且忽略,以后再学习。

最终我们进入到 DispatcherServlet 类中,调用 doDispatch(...) 方法,这个方法有两个参数,分别是 HttpServletRequestHttpServletResponse

0. DispatcherServlet 类

任何 Spring MVC 的教程帖,包括官方文档,都会重点说 DispatcherServlet 类。它被称为 Spring MVC 的前端控制器,这是因为它是所有 HTTP 请求的总中转站。这个类在 JavaDoc 中是这么描述的:

Central dispatcher for HTTP request handlers/controllers……

我们这篇只关注 DispatcherServlet 类的一个方法:doDispatch(...)

Spring 框架接到 HTTP 请求后,将会执行 DispatcherServlet 类的 doDispatch(...) 方法,用来做分发(dispatch)。后端 Java 应用有那么多的接口,这个 HTTP 请求到底是访问哪一个接口,得找出来,并且得执行这个接口,分发就是指找到并执行这个接口(把这个请求分发出去)。

Spring MVC 把处理请求的对象称为 handler,与之相关的概念还有 HandlerExecutionChainHandlerAdapter,它们都跟 handler 有关,下文讲到了再说。

doDispatch(...) 方法按顺序做了这些事情:

  1. 找出 handler
  2. 找出负责执行 handler 的 handlerAdapter
  3. 处理缓存
  4. 执行 handler
  5. 渲染视图结果

首先将这部分代码贴在下面:

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
92
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

// 处理异步请求的 manager
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
// handler 处理的结果
ModelAndView mv = null;
Exception dispatchException = null;

try {
// 检查是否是上传文件的请求,如果是,转换 request
processedRequest = checkMultipart(request);
// boolean 变量:是否是上传文件的请求(request 有没有发生过转换)
multipartRequestParsed = (processedRequest != request);


/* —————————— ① 找出能处理该请求的 handler —————————— */
mappedHandler = getHandler(processedRequest);
// 如果没找到 handler,response 返回 404 异常(在 noHandlerFound(...) 方法中)
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}


/* —————————— ② 获取 HandlerAdapter,用以执行 handler —————————— */
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());


/* —————————— ③ 处理缓存(允许的话可以直接返回) —————————— */
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
// 默认情况下 lastModified 是 -1
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
// 由于 lastModified 是 -1,checkNotModified(...) 方法什么都没做,最后返回 false
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}


/* —————————— ④ handler 开始处理请求 —————————— */
// 前置拦截
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// 执行 handler,获取结果 modelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

// 如果是异步请求,直接返回,response 将一直保持打开状态
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

// 当 modelAndView 不为空,但 view 为空时,根据 request 设置默认的 view
applyDefaultViewName(processedRequest, mv);

// 后置拦截
mappedHandler.applyPostHandle(processedRequest, response, mv);

} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}


/* —————————— ⑤ 解析、渲染 mv(或者是异常) —————————— */
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

} catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable err) {
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);
}
}
}
}

1. 找出能处理该请求的 handler

(省略了一些声明变量、检查上传文件的代码)

doDispatch(...) 方法做的第一件事,是找到请求要分发到哪个接口上。

Spring MVC 将处理用户请求的接口叫做 handler,它可能是程序员开发的 Controller 方法,也有可能是框架自动配置出的某个方法。

我们首先需要找到这个方法,也就是找到 handler,这一步是执行 getHandler(...) 方法。如果没有找到这个方法,那么就可以直接返回 404 了,返回 404 的过程是在执行 noHandlerFound(...) 方法。这里很简单,直接看代码。

1
2
3
4
5
6
7
/* —————————— ① 找出能处理该请求的 handler —————————— */
mappedHandler = getHandler(processedRequest);
// 如果没找到 handler,response 返回 404 异常(在 noHandlerFound(...) 方法中)
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}

这第一步可以拆成两个部分,怎么找到 handler 的,以及如果找不到是怎么返回 404 的。

  1. getHandler(...) 方法

    首先寻找 handler,从 handlerMappings 中进行遍历寻找能够匹配的 handler,如果找不到就返回 null。

    handlerMappings 是一个 List<HandlerMapping>,它在首个请求进来时进行初始化,把所有的 handler 都包装起来加入到一个 List 中,之后每当有请求进来就遍历这个 List,逐个匹配。如果匹配上了,就通过这个 handlerMapping 获取 handler。

    需要注意的是,这里获取到的 handler 的类型,实际上是 HandlerExecutionChain,它在 handler 的基础上增加了若干个 interceptor(拦截器),可以实现类似 AOP 的效果。有关这个类的设计,我们在最后面详述。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // handlerMappings:
    // 这是一个 List<HandlerMapping>,装着所有的 handler,应用启动的时候进行初始化
    if (this.handlerMappings != null) {

    // 遍历 handlerMappings
    for (HandlerMapping mapping : this.handlerMappings) {
    // 如果找到了合适的 handler,包装成 HandlerExecutionChain 对象
    // HandlerExecutionChain 除了包含 handler 外,还包含拦截器(HandlerInterceptor 列表)

    // mapping.getHandler(...) 这个方法比较深,主要做了这几件事:
    // 1. 获取 url
    // 2. 通过 RequestCondition 的设计,比对接口参数(如路径、头部信息、请求参数等)
    // 3. 比对参数找到 handler 后,将 handler 转换成 HandlerExecutionChain 对象
    HandlerExecutionChain handler = mapping.getHandler(request);
    if (handler != null) {
    return handler;
    }
    }
    }
    return null;
    }

    handlerMappings 的初始化是在 DispatcherServlet 的 initHandlerMappings(...) 方法内完成的,在默认情况下,会从容器(以及祖先容器)获取所有类型为 HandlerMappingbean 组件,记录到 handlerMappings 并排序。这个过程可以参考博文《Spring MVC DispatcherServlet 策略初始化 – initHandlerMappings

  2. noHandlerFound(...) 方法

    (按照常理来想)如果没有对应的接口,寻找 handler 时会找不到,从而进入到 noHandlerFound(...) 方法,返回 404 异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (pageNotFoundLogger.isWarnEnabled()) {
    pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
    }
    if (this.throwExceptionIfNoHandlerFound) {
    throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request), new ServletServerHttpRequest(request).getHeaders());
    } else {
    // 这里就是 404
    response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
    }

    但是神奇的是,Spring MVC 提供了一个 handler,它的路径匹配是 \**,也就是对所有路径都匹配,因而每次有请求进来,都可以找到 handler,不会出现 handler 为 null 的情况,也就不会进入到 noHandlerFound(...) 方法中。它会在更晚的地方,handler 开始处理接口的时候去处理,最终返回 404。

    这种情况我还不是很确定,但并不关乎核心内容,因此暂且放过了。

2. 找出负责执行 handler 的 handlerAdapter

获取 handler 之后并不是直接执行它的,负责执行 handler 的类是 HandlerAdapter,也就是说一个请求的真正执行,是由 HandlerAdapter 来处理的。这是单一职责原则的体现,有关这个类的具体设计见本文最后。

获取 handlerAdapter 只要一行代码:

1
2
/* —————————— ② 获取 HandlerAdapter,用以执行 handler —————————— */
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

跟进去看 getHandlerAdapter(...) 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
// 自己写的接口,默认的 handlerAdapter 是 RequestMappingHandlerAdapter
// 这个类对 @RequestMapping 的方法提供支持
if (adapter.supports(handler)) {
return adapter;
}
}
}
// 默认情况下是不可能的,只要不瞎改源码瞎扩展
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

代码也比较简单,遍历 handlerAdapters,找到合适的 handlerAdapter。

对于自己在 Controller 中写的接口(使用 @RequestMapping 注解的方法),默认的 handlerAdapter 是 RequestMappingHandlerAdapter 的实现类。

要遍历的 handlerAdapters 是 DispatcherServlet 的一个成员变量,它跟 handlerMappings 一样,都是在第一个请求进来时进行加载,加载的方法是 initHandlerAdapters(...)

3. 处理缓存

第三步是处理缓存相关的事情,这一部分主要跟 HTTP 的 304 状态码相关,先贴代码吧。

1
2
3
4
5
6
7
8
9
10
11
/* —————————— ③ 处理缓存(允许的话可以直接返回) —————————— */
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
// 默认情况下 lastModified 是 -1
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
// 由于 lastModified 是 -1,checkNotModified(...) 方法什么都没做,最后返回 false
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}

默认的实现,这里什么都没做,也就是说默认情况下,自己写的接口是不会使用缓存机制的。

关于 HTTP 的 304 状态码,last-modified 的含义,HTTP 缓存的概念,可以参考这篇文章《HTTP 缓存》。

4. 执行 handler

在 DispatcherServlet 中,执行 handler 分五步走

  1. 前置拦截
  2. 执行 handler
  3. 考虑处理异步请求
  4. 考虑设置默认 view
  5. 后置拦截
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* —————————— ④ handler 开始处理请求 —————————— */
// 前置拦截
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// 执行 handler,获取结果 modelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

// 如果是异步请求,直接返回,response 将一直保持打开状态
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

// 当 modelAndView 不为空,但 view 为空时,根据 request 设置默认的 view
applyDefaultViewName(processedRequest, mv);

// 后置拦截
mappedHandler.applyPostHandle(processedRequest, response, mv);
  1. 前置拦截

    前文有说过,真正执行 handler 的是 handleAdapter,这个类是在 handler 的基础上增加了若干个 interceptor,起到前后拦截的作用。第一步,就是遍历 interceptors 进行前置拦截,来看 mappedHandler.applyPreHandle(processedRequest, response) 这行代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 检查 interceptors 是否不为空
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {

    // 遍历 interceptors
    for (int i = 0; i < interceptors.length; i++) {

    // 让每个 interceptor 执行 preHandle(...) 方法,执行前置拦截方法
    // 具体前置拦截执行内容,由 interceptor 自己定义
    HandlerInterceptor interceptor = interceptors[i];
    if (!interceptor.preHandle(request, response, this.handler)) {

    // 只有在 preHandle(...) 方法返回 false 的情况下才会执行下面(也就是说前置拦截中途失败了)
    // 这个方法会执行 interceptor 的 afterCompletion(...) 方法,然后请求直接返回
    // 意思是如果前置执行失败了,那么 handler 就不执行了,处理完一些操作请求就直接返回了
    triggerAfterCompletion(request, response, null);
    return false;
    }
    this.interceptorIndex = i;
    }
    }
    return true;
    }
  2. 执行 handler

    处理完前置拦截的 interceptor 们之后,就可以正式执行 handler 了,对应于我们自己的业务代码,就是去执行 @RequestMapping 注解的 Controller 方法。

    执行 Controller 方式是通过反射实现的,步骤很深,就不细研究了。主要的思路是,从 handler 中获取 handlerMethod(也就是 Controller 方法),从 request 中获取接口参数,然后使用反射调用方法,获取结果,包装进 response 里。具体的执行细节可以去看 RequestMappingHandlerAdapter 类中的 invokeHandlerMethod(...) 方法。

    执行 handle 方法,接口返回的是 ModelAndView,这是 MVC 中的 M(Model)和 V(View),但是对于我们自己编写 Controller 方法而言(使用 RestController),handle 方法返回的 ModelAndView 是 null,因为并没有必要返回视图,结果直接被封装进 response 里了。这里涉及到 HTTP 协议和返回体相关的知识,以后再补吧(怎么这么多要补的……)

  3. 考虑处理异步请求

    如果请求是异步的,那么可以提前返回,之后再异步传回请求结果。

    判断请求是否异步的代码如下:

    1
    2
    3
    public boolean isConcurrentHandlingStarted() {
    return (this.asyncWebRequest != null && this.asyncWebRequest.isAsyncStarted());
    }
  4. 考虑设置默认 view

    如果 ModelAndView 对象不为 null,但是对象内部的 view 部分是空的,那么考虑设置默认的 view。

    这个方法并没有执行,因为 ModelAndView 对象此时是 null,方法直接就返回了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
    // 如果 ModelAndView 对象不为空,但是 view 为空,那么考虑设置默认的 view
    if (mv != null && !mv.hasView()) {
    String defaultViewName = getDefaultViewName(request);
    if (defaultViewName != null) {
    mv.setViewName(defaultViewName);
    }
    }
    }
  5. 后置拦截

    跟前置拦截的过程差不多,但是这次是从后往前遍历执行拦截方法的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
    // 如果 interceptors 不为空
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
    // 从后往前遍历每个 interceptor
    for (int i = interceptors.length - 1; i >= 0; i--) {
    HandlerInterceptor interceptor = interceptors[i];
    // 执行每个 interceptor 的 postHandle(...) 方法
    interceptor.postHandle(request, response, this.handler, mv);
    }
    }
    }

5. 渲染视图结果

执行到这里时(processDispatchResult(...) 方法),有三种可能:

  1. 执行正常,且不需要 ModelAndView(RestController 直接返回了 JSON/XML 数据)

    processDispatchResult(...) 什么都没执行,逛一圈就返回了。

  2. 执行正常,且需要 ModelAndView

    将 ModelAndView 渲染进 response 里(也就是渲染视图结果)

  3. 执行异常

    将异常转换为 ModelAndView 对象(转换成视图对象),然后渲染进 response 里(如果转换不了就再抛出去异常)

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

boolean errorView = false;

// 如果执行 handler 发生了异常,将 mv 转换为异常情况下的 mv
if (exception != null) {
// 如果是 ModelAndViewDefiningException 类型的异常,可以直接获取 mv
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
// 如果是其他异常,需要找一个 HandlerExceptionResolver 处理异常获取 mv
// 也有可能这里也处理不了,那就再抛异常出去
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}

// 解析、渲染 mv
if (mv != null && !mv.wasCleared()) {
// render:渲染
// 将 mv 解析渲染进 response 中
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}

// 异步并发相关,不懂,略
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return;
}

// 处理每个 interceptor 的完成拦截逻辑
if (mappedHandler != null) {
// 这个方法在第四步处理 handler 的第一步前置拦截时看到过,如果前置拦截发生了异常,就会执行这个方法,然后直接返回
mappedHandler.triggerAfterCompletion(request, response, null);
}
}

至此,doDispatch(...) 方法的五步就都看完了。


Spring MVC 设计了很多接口类,将各自的职责摘得很干净,这里学习其中几个类的设计。

HandlerExecutionChain

doDispatch(...) 方法在第一步获取的 handler,它的类型是 HandlerExecutionChain。这个类在 handler 的基础上,还有若干个 HandlerInterceptor 对象,在 handler 执行的前后起到拦截的效果。

HandlerExecutionChain 的内部有两部分:

  • handler:实际执行请求方法
  • interceptors:在 handler 的前后做拦截方法,起到类似 AOP 的效果

官方在 Javadoc 中对 HandlerExecutionChain 的描述有这么一句,可以感受一下:

a handler and any interceptors for this request.

interceptors 是 HandlerInterceptor 接口对象的集合,这个接口定义了三个方法,具体的实现可能由框架提供,也可能由开发人员提供。这三个方法会在 doDispatch(...) 方法的第四步执行 handle 时被调用,分别是:

  • preHandle(…)

    前置拦截方法,按照 interceptors 的顺序逐个执行,方法有返回值,如果返回 false 则不会执行 handle 方法,调用 afterCompletion(…) 后返回

  • postHandle(…)

    后置拦截方法,按照 interceptors 的顺序,倒序逐个执行,方法没有返回值

  • afterCompletion(…)

    完成后拦截方法(完成既指正常完成,也指异常完成,总之一定会执行),方法没有返回值

学习该类可参考博文《Spring MVC : 概念模型 HandlerExecutionChain》。

HandlerMapping

doDispatch(...) 方法的第一步获取 handler 的过程中,匹配 handler 是通过 HandlerMapping 来实现的。

HandlerMapping 代表着【请求 URL】到【请求处理器】之间的映射,在首个请求进入 Spring MVC 之后,框架将整理出 handlerMappings 集合,然后 DispatcherServlet 会为每个请求遍历 handlerMappings,询问哪个请求处理器能够处理当下的请求。

HandlerMapping 接口只定义了一个方法 getHandler(request),它的接收参数是一个请求,它返回一个 handler。如果返回的 handler 不为 null,就代表它可以处理这个请求,这个返回的 handler 也就是上面所说的 HandlerExecutionChain 的实例对象。

学习该类可参考博文《Spring MVC : HandlerMapping 和 HandlerAdapter 简介 : 概念和如何工作》。

HandlerAdapter

doDispatch(...) 方法获取到 handler 之后,并不是使用它直接执行请求方法的,而是又找到 HandlerAdapter 对象,使用它来处理请求,执行请求方法。

HandlerAdapter 抽象了对【请求处理器】的【真正执行】,这使得 DispatcherServlet 可以无视 handler 的执行细节,当要支持更多的 handler 类型时,只需要实现 HandlerAdapter 接口就可以了。

学习该类可参考博文《DispatcherServlet 请求处理主逻辑 : 2. 选择 Handler 对应的 HandlerAdapter》。


本周先学习到这里吧。