摘要
SpringMVC 是一种基于 Java 实现的 MVC(Model-View-Controller)设计模型的请求驱动类型的轻量级 Web 框架。它是 Spring FrameWork 的后续产品。SpringMVC 通过将 web 层进行职责解耦,将复杂的 web 应用分成逻辑清晰的几部分,从而简化了开发过程,减少了出错的可能性,并方便了开发人员之间的协作。那么 SpringMVC 是如何将职责解耦的,中间又做了什么工作,为什么请求发过来就可以找到相应的处理方法?本篇文章正是解答这些问题。
关键词:SpringMVC、MVC、Spring
1、引言
在讲解 SpringMVC 的原理之前首先要理解什么是 MVC,SpringMVC 之所以可以进行职责的解耦正是因为它遵循了 MVC 设计模式。那么什么是 MVC?
MVC 是指 Model-View-Controller,是一种软件设计模式,它将应用程序分为三个部分:模型、视图和控制器。MVC 模式的核心思想是将应用程序的表示和处理分开,从而使得应用程序更加灵活、易于维护和扩展。这种模式可以提高代码的可读性和可维护性,同时也可以促进代码的服用和分工。
图1-1 MVC 模型
SpringMVC 和 MVC 之间的关系主要体现在 SpringMVC 是 MVC 设计模式在 Java Web 开发中的一个具体实现框架,它实现了 MVC 设计模式,并继承了 Servlet API。SpringMVC 作为 Spring 框架的一部分,提供了构建 Web 应用程序的全功能 MVC 模块,它主要有以下几个核心组件:
- DispatcherServlet(前端控制器):负责接收用户的请求并转发给相应的处理器。
- HandlerMapping(处理器映射器):负责根据请求的 URL 找到相应的处理器(Controller)。
- HandlerAdapter(处理器适配器):负责按照一定的规则去执行处理器。
- Controller(处理器):负责处理用户请求并返回 ModelAndView 对象。
- ViewResolver(视图解析器):负责将 ModelAndView 对象中的视图名称解析为具体的视图。
SpringMVC 通过这些组件的协作,实现了MVC设计模式的各个部分,SpringMVC 还提供了丰富的功能和灵活的配置选项,支持多种视图技术(如 JSP、Thymeleaf、Freemarker 等)和多种请求处理方式(如 GET、POST 等),从而提高了开发效率和应用程序的可维护性。本文接下来会按照处理请求的过程来介绍 SpringMVC 的处理过程。
2、相关工作
2.1、SpringMVC 处理流程
当前端用户请求发来后最终都会到达 DispatcherServlet 中的 doDispatch 这个方法,整个请求的大致处理过程都在这个方法中。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 请求对象
HttpServletRequest processedRequest = request;
// 处理器执行链对象
HandlerExecutionChain mappedHandler = null;
// 用于标示是否经过了对文件请求的包装
boolean multipartRequestParsed = false;
// 获取异步处理管理器,servlet3.0后支持异步处理,可以在子线程中响应用户请求
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
// 模型和视图
ModelAndView mv = null;
// 异常对象
Exception dispatchException = null;
try {
// 检查该请求是否是文件类型请求,如果是则包装为文件类型的请求
processedRequest = this.checkMultipart(request);
// 如果经过了包装,那么返回true,否则false
multipartRequestParsed = processedRequest != request;
// 获得该请求的HandlerExecutionChain对象
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
// 如果没有找到对应的处理器链,则返回
this.noHandlerFound(processedRequest, response);
return;
}
// 获取该控制器的控制器适配器
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
// 获得请求中的方法
String method = request.getMethod();
// 判断请求是否是Get请求
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
// 检查资源是否自上次请求以来未被修改
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
// 如果是,则直接返回304 Not Modified响应
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用处理器链中preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
// 如果返回false,就结束
return;
}
// 调用处理器链中的处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果当前处理是异步的,则直接返回,不继续后续流程
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 如果没有设置视图名,则调用applyDefaultViewName方法应用默认视图名
this.applyDefaultViewName(processedRequest, mv);
// 调用处理器链中的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new ServletException("Handler dispatch failed: " + var21, var21);
}
// 处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
// 调用拦截器的afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
triggerAfterCompletion(processedRequest, response, mappedHandler, new ServletException("Handler processing failed: " + var23, var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
// 对于异步处理的情况,调用异步处理的拦截器AsyncHandlerInterceptor的afterConcurrentHandlingStarted方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
asyncManager.setMultipartRequestParsed(multipartRequestParsed);
} else if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
// 对于multipart的请求,清理资源,比如文件上传的请求,在上传的过程中文件会被保存到临时文件中,这里就会对这些文件继续清理
this.cleanupMultipart(processedRequest);
}
}
}
2.1.1、请求预处理
doDispatch 方法是 Spring MVC 中处理 HTTP 请求的核心逻辑,它负责从接收请求到生成响应的整个过程,包括请求预处理、请求处理、响应生成、异常处理以及请求完成后的清理工作。在前端控制器接收到请求后便会去判断该请求是否为多部分请求(如包含文件),其核心逻辑就在于使用 multipartResolver 解析器的 isMultipart 方法
图2-1 StandardServletMultipartResolver 类中的 isMultipart 方法
从图 2-1 可知,在方法体内,首先通过调用 request.getContentType() 方法获取请求的 Content-Type 头部信息。这个头部信息通常用于指示请求体的媒体类型(MIME 类型),对于多部分请求(如文件上传),这个值通常是 multipart/form-data,如果匹配上了则返回 true,并包装原请求为多部分请求。当请求预处理完成后便去获取该请求的处理器链。
2.1.2、获取处理器链
图2-2 DispatcherServlet 类中 getHandler 方法
从图 2-2 可知,该方法首先获取所有处理器映射器,然后迭代处理器映射器,让每一个处理器映射器去拿到请求,看能否获取处理器链,核心逻辑就在图 2-2 红色框中的方法上。
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 获取对应的处理器
Object handler = this.getHandlerInternal(request);
if (handler == null) {
// 如果处理器为空,则使用一个默认的处理器
handler = this.getDefaultHandler();
}
if (handler == null) {
// 如果获取默认处理器依旧为空,则返回空,表示没有找到任何处理器来处理该请求
return null;
} else {
if (handler instanceof String) {
// 如果找到的处理器是一个字符串,这意味着处理器名称被存储在字符串中,而不是处理器对象本身
String handlerName = (String)handler;
// 从Spring容器中获取实际的处理器对象
handler = this.obtainApplicationContext().getBean(handlerName);
}
// 请求的查找路径尚未被缓存
if (!ServletRequestPathUtils.hasCachedPath(request)) {
// 调用initLookupPath(request)来初始化这个路径
// 这对于后续可能需要的路径查找是有用的
this.initLookupPath(request);
}
// 将处理器和请求作为参数传入,构建并返回一个HandlerExecutionChain对象
// 这个对象不仅包含了处理器,还可能包含了一系列与之关联的拦截器
HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
if (request.getAttribute(SUPPRESS_LOGGING_ATTRIBUTE) == null) {
// 如果请求的属性中没有设置SUPPRESS_LOGGING_ATTRIBUTE(一个用于抑制日志记录的属性),则会根据日志级别记录找到的处理器或处理器执行链的信息
if (this.logger.isTraceEnabled()) {
this.logger.trace("Mapped to " + handler);
} else if (this.logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
this.logger.debug("Mapped to " + executionChain.getHandler());
}
}
// 检查是否需要对当前请求应用CORS(跨源资源共享)配置或请求是否为预检请求
if (this.hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
// 如果需要CORS配置,则从处理器或全局配置中获取CORS配置,并将它们合并(如果全局配置存在的话)。然后,对CORS配置进行验证
CorsConfiguration config = this.getCorsConfiguration(handler, request);
if (this.getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = this.getCorsConfigurationSource().getCorsConfiguration(request);
config = globalConfig != null ? globalConfig.combine(config) : config;
}
if (config != null) {
config.validateAllowCredentials();
config.validateAllowPrivateNetwork();
}
// 返回可能已经被CORS配置修改的处理器链对象。这个对象将被用于处理传入的HTTP请求
executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
}
这段代码是 Spring MVC 中处理请求映射和执行的核心部分,它负责根据请求找到对应的处理器,并构建一个包含处理器和拦截器的执行链来处理该请求。同时,它还支持 CORS 配置和日志记录等高级功能。在该方法中获取处理器是通过 getHandlerInternal 方法
图2-3 实现 getHandlerInternal 方法的类
getHandlerInternal 方法在 AbstractHandlerMapping 中是一个抽象方法,要想看它的具体实现需要进入 AbstractHandlerMapping 类的一个实现类中。也就是 AbstractHandlerMethodMapping
图2-4 AbstractHandlerMethodMapping 中 getHandlerInternal 方法
该方法首先获取请求的查找路径,接着,方法通过 mappingRegistry.acquireReadLock() 获取一个读锁。mappingRegistry 是存储映射信息的注册表,需要加锁来保证线程安全,接着调用 lookupHandlerMethod 方法来尝试根据查找路径和请求找到对应的 HandlerMethod。如果找到了匹配的 HandlerMethod,则通过调用 createWithResolvedBean 方法创建一个新的 HandlerMethod 实例。这个步骤通常是为了确保返回的 HandlerMethod 中的 bean(即控制器实例)是已经被 Spring 容器解析和管理的。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
// 存储所有匹配的映射信息
List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
// 通过直接路径匹配来查找映射
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
// 如果存在直接匹配的映射,则将这些映射添加到matches列表中
this.addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 如果matches列表为空(即没有找到直接匹配的映射),则遍历所有已注册的映射,并检查它们是否与lookupPath匹配,匹配的映射同样被添加到matches列表中
this.addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (matches.isEmpty()) {
// 如果在直接路径匹配和模式匹配后matches列表仍然为空,则调用handleNoMatch方法来处理无匹配的情况
return this.handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
} else {
// 首先从matches中取出第一个match,标示为最匹配的match
AbstractHandlerMethodMapping<T>.Match bestMatch = (Match)matches.get(0);
if (matches.size() > 1) {
// 如果匹配到的match多于一个,则对所有match进行比较
Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new MatchComparator(this.getMappingComparator(request));
matches.sort(comparator);
// 经过比较排序后的,此时放在第一个的match,就是最匹配的match
bestMatch = (Match)matches.get(0);
if (this.logger.isTraceEnabled()) {
Log var10000 = this.logger;
int var10001 = matches.size();
var10000.trace("" + var10001 + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
Iterator var7 = matches.iterator();
while(var7.hasNext()) {
// 如果是预检请求(CORS预检),且任何匹配项都配置了CORS配置,则返回一个特殊的值(如PREFLIGHT_AMBIGUOUS_MATCH)来表示模糊匹配
AbstractHandlerMethodMapping<T>.Match match = (Match)var7.next();
if (match.hasCorsConfig()) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
}
} else {
AbstractHandlerMethodMapping<T>.Match secondBestMatch = (Match)matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.getHandlerMethod().getMethod();
Method m2 = secondBestMatch.getHandlerMethod().getMethod();
String uri = request.getRequestURI();
// 如果最佳匹配和第二佳匹配之间的比较结果为0(即它们被认为是等价的),则抛出异常,指出存在模糊匹配的处理器方法
throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
}
// 将最佳匹配的HandlerMethod设置为请求的属性
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
// 调用handleMatch方法来进行一些匹配后的处理工作,如缓存匹配结果、记录日志等
this.handleMatch(bestMatch.mapping, lookupPath, request);
// 最后,返回最佳匹配的HandlerMethod,以便后续处理请求
return bestMatch.getHandlerMethod();
}
}
这段代码展示了在 Spring MVC 中如何根据请求的 URL 和其他请求属性来查找并确定最合适的处理器方法来处理该请求,在这段代码中出现了一个 Match 类,该类代表匹配的映射信息,那这个类中包含了哪些属性呢?
图2-5 AbstractHandlerMethodMapping 的成员内部类 Match
可以看到 Match 是一个成员内部类,其中有两个常量,并且这两个常量都被泛型修饰,而这个泛型是 AbstractHandlerMethodMapping 类的子类所确定的,因此不妨看看该类有哪些子类
图2-6 AbstractHandlerMethodMapping 的子类
图 2-6 中 RequestMappingHandlerMapping 类是 RequestMappingInfoHandlerMapping 的子类,因此去 RequestMappingInfoHandlerMapping 的子类查看
图2-7 RequestMappingInfoHandlerMapping 类
从图 2-7 可以看出 RequestMappingInfoHandlerMapping 类实现了 AbstractHandlerMethodMapping 类,并指定了其中的泛型为 RequestMappingInfo。
图2-8 RequestMappingInfo 类
RequestMappingInfo 是 Spring MVC 框架中的一个类,它用于封装了关于请求映射的详细信息。在 Spring MVC 中,@RequestMapping、@GetMapping、@PostMapping 等注解用于将 HTTP 请求映射到特定的处理方法上。RequestMappingInfo 类就是用来表示这些映射信息的对象,具体来说,RequestMappingInfo 包含了如下一些关键信息:
- 路径模式(Path Patterns):定义了哪些 URL 路径可以匹配到该映射。这通常是通过 @RequestMapping 注解的 value 或 path 属性指定的。
- HTTP 方法(HTTP Methods):指定了哪些 HTTP 方法(如 GET、POST、PUT、DELETE 等)可以匹配到该映射。
- 请求参数(Request Parameters):可以通过 @RequestMapping 注解的 params 属性来指定哪些请求参数必须存在或不能存在,以便匹配到该映射。
- 请求头(Request Headers):类似于请求参数,但它是基于 HTTP 请求头信息的。这可以通过 @RequestMapping 注解的 headers 属性来指定。
- 消费媒体类型(Consumes Media Types):指定了请求体(body)的媒体类型(如 application/json),这通常用于 POST 或 PUT 请求中。这可以通过 @RequestMapping 注解的 consumes 属性来指定。
- 生成媒体类型(Produces Media Types):指定了响应体的媒体类型。这可以通过 @RequestMapping 注解的 produces 属性来指定,它告诉客户端可以期待什么样的响应体格式。
那么在 Match 中便会存储 RequestMappingInfo 以及 MappingRegistration<RequestMappingInfo >,而在 MappingRegistration 类中又记载了 RequestMappingInfo 、处理器以及直接匹配的路径信息。因此找到最佳匹配的 match 就是找到了可以处理该请求的处理器。
图2-9 HandlerMapping 相关联的类
至此返回到 AbstractHandlerMapping 类中的 getHandler方法中,便获取到了可以处理该请求的处理器,接下来就是构造处理器链,进入 getHandlerExecutionChain 方法中
图2-10 AbstractHandlerMapping 类中的 getHandlerExecutionChain 方法
getHandlerExecutionChain 方法所做的工作就是获取所有拦截器,并依次遍历添加到处理器链中,最后返回处理器链。
2.1.3、获取处理器适配器
SpringMVC 框架支持多种类型的处理器(Handler),包括 Controller、HttpRequestHandler、SimpleServletHandler 等。每种处理器都有其特定的实现方式和处理逻辑。然而,这些处理器并不能直接与 SpringMVC 的核心处理流程无缝对接,因为它们的接口和方法可能各不相同。处理器适配器的作用就是将这些不同类型的处理器适配到 SpringMVC 的框架中,使得它们能够被框架正确地调用和执行。
在 2.1.2 小节中已经获取到了可以处理该请求的处理器链,接下来便是获取其处理器适配器。获取处理器适配器的方法是 DispatcherServlet 类中的 getHandlerAdapter 方法,该方法需要的参数正是处理器链。
图2-11 DispatcherServlet 类中的 getHandlerAdapter 方法
从图 2-11 可以看出该方法主要逻辑就是先获取所有处理器适配器,然后遍历,依次判断该处理器适配器是否支持该处理器链,如果支持就返回该处理器适配器。
图2-12 常见的处理器适配器
常见的处理器适配器如图 2-12 所示,在日常开发中用到最多的便是 RequestMappingHandlerAdapter 适配器。
2.1.4、执行拦截器的 preHandle 方法
在获取到处理器适配器后便要准备执行处理器,但是在此之前需要先检查是否有拦截器需要对请求做前置处理的。
图2-13 执行拦截器的 preHandle 方法
从图 2-13 可以看出,这段代码是遍历所有拦截器对象,从前往后依次执行 preHandle 方法,如果 preHandle 方法返回 false,则会执行 triggerAfterCompletion 方法,如果返回 true 则继续,并记录拦截器的位置。
图2-14 HandlerExecutionChain 类中的 triggerAfterCompletion 方法
triggerAfterCompletion 方法是反向依次调用那些 preHandle 方法返回 ture 的拦截器的 afterCompletion 方法。
2.1.5、执行处理器
在执行完拦截器的 preHandle 方法后,便要开始真正的执行处理请求的方法。本文以处理 http 请求为例,因此选用的处理器适配器是 RequestMappingHandlerAdapter,运行处理方法的是其中的 handleInternal 方法。
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 检查请求,包括检查请求参数、安全性校验等
this.checkRequest(request);
// 准备ModelAndView对象,用于存储模型和视图信息。这个对象最终会被用来渲染HTTP响应。
ModelAndView mav;
if (this.synchronizeOnSession) {
// 如果synchronizeOnSession标志为true,表示需要进行会话同步。
// 这意味着如果当前请求涉及到一个会话(Session),则会在会话的互斥锁(mutex)下执行处理器方法,以避免多线程问题。
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized(mutex) {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// 负责实际调用处理器方法(即Controller中的方法),处理请求,并填充ModelAndView对象。这是整个请求处理的核心部分
mav = this.invokeHandlerMethod(request, response, handlerMethod);
}
// 在生成ModelAndView之后,代码检查HTTP响应是否已包含Cache-Control头部。如果没有,它将根据是否存在会话属性(Session Attributes)来决定如何设置缓存控制。
if (!response.containsHeader("Cache-Control")) {
if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
this.prepareResponse(response);
}
}
return mav;
}
从源码中可以看出,真正执行处理方法的在 invokeHandlerMethod 方法中。而在 invokeHandlerMethod 方法中核心逻辑在 InvocableHandlerMethod 类中的 invokeForRequest 方法。
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 负责从NativeWebRequest(可能是一个HTTP请求)和ModelAndViewContainer(用于存储模型和视图信息)中提取控制器方法所需的参数值。
// providedArgs可能包含一些已经预解析的参数,它们会被合并到最终的参数数组中
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 检查是否应该验证方法参数,并且是否已经配置了一个验证器
if (this.shouldValidateArguments() && this.methodValidator != null) {
this.methodValidator.applyArgumentValidation(this.getBean(), this.getBridgedMethod(), this.getMethodParameters(), args, this.validationGroups);
}
// 通过反射执行处理请求的方法
Object returnValue = this.doInvoke(args);
// 检查是否应该验证方法的返回值,并且是否已经配置了一个验证器
if (this.shouldValidateReturnValue() && this.methodValidator != null) {
this.methodValidator.applyReturnValueValidation(this.getBean(), this.getBridgedMethod(), this.getReturnType(), returnValue, this.validationGroups);
}
// 返回结果
return returnValue;
}
可以看到在拿到了执行方法的参数后就可以通过反射来执行处理请求的方法,那如何拿到的参数呢?就是通过 HandlerMethodArgumentResolver 对象,叫做:处理器方法参数解析器,用来解析请求,得到方法需要的参数。HandlerMethodArgumentResolver 是一个接口,该接口有很多实现类,下面例举一些常用的实现类。
实现类 | 控制器参数 | 说明 |
---|---|---|
PathVariableMapMethodArgumentResolver | @PathVariable 标注参数 | 从 url 中提取参数的值 |
RequestHeaderMethodArgumentResolver | @RequestHeader 标注参数 | 从 http 头中提取参数值 |
RequestParamMethodArgumentResolver | @RequestParam 标注参数 | http 请求参数中获取值 |
RequestResponseBodyMethodProcessor | @RequestBody 标注参数 | 提取 body 数据,转换为参数类型 |
ServletResponseMethodArgumentResolver | ServletResponse、OutputStream、Writer 这 3 种类型的参数 | 这几种类型用来控制 http 请求的响应输出流 |
HttpEntityMethodProcessorHttpEntity | HttpEntity 类型的参数 | HttpEntity 中包含了 http 请求头和 body 的所有信息 |
ExpressionValueMethodArgumentResolver | @Value 标注的参数 | spel 表达式,从 spring 容器中获取值 |
MapMethodProcessor | 参数为 Map 或者子类型 | - |
ModelMethodProcessor | 参数为 org.springframework.ui.Model 或子类型 | - |
ModelAttributeMethodProcessor | @ModelAttribute 标注的参数 | - |
表2-1 常见处理器方法参数解析器
在拿到方法返回值后,会对反射调用的结果 returnValue 进行处理。
图2-15 HandlerMethodReturnValueHandlerComposite 类的 handleReturnValue 方法
从该方法中可知首先调用 selectHandler,根据返回值找到对应的返回值解析器,并调用其处理方法。
图2-16 返回值解析器
图 2-16 可以看到返回值解析器是个接口,其有众多返回值解析器,例举几个常见的返回值解析器:
实现类 | 说明 |
---|---|
ViewNameMethodReturnValueHandler | 返回值为视图名称时的解析器 |
MapMethodProcessor | 返回值为 Map 的解析器 |
StreamingResponseBodyReturnValueHandler | 返回值为 ResponseEntity 类型时的解析器 |
DeferredResultMethodReturnValueHandler | 返回值为 DeferredResult 类型时的解析器,表示异步请求 |
CallableMethodReturnValueHandler | 返回值为 Callable 类型时的解析器,表示异步请求 |
ModelMethodProcessor | 返回值为 Model 类型时的解析器 |
ModelAndViewMethodReturnValueHandler | 返回值为 ModelAndView 类型时的解析器 |
RequestResponseBodyMethodProcessor | 方法上标注有 @ResponseBody 注解时返回值的解析器 |
HttpEntityMethodProcessor | 返回值为 HttpEntity 类型但是非 RequestEntity 类型时的解析器 |
表2-2 常见返回值解析器
在当今十分流行的开发方式是前后端分离,因此 RequestResponseBodyMethodProcessor 解析器用的十分频繁
public boolean supportsReturnType(MethodParameter returnType) {
// 判断类上或者目标方法上是否有@ResponseBody注解
return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class);
}
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// 标注为请求已处理,因为当前handleReturnValue方法会直接将结果输出到客户端
// 所以后续就不需要再进行视图渲染了,表示请求已经被处理了
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
if (returnValue instanceof ProblemDetail detail) {
outputMessage.setStatusCode(HttpStatusCode.valueOf(detail.getStatus()));
if (detail.getInstance() == null) {
URI path = URI.create(inputMessage.getServletRequest().getRequestURI());
detail.setInstance(path);
}
this.invokeErrorResponseInterceptors(detail, (ErrorResponse)null);
}
// 将结果输出到客户端
this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
2.1.6、执行拦截器的 postHandle 方法
在执行完处理请求的处理方法并返回经过返回值解析器的结果后,接下来便要执行拦截器的 postHandle 方法。
图2-17 执行拦截器的 postHandle 方法
从图 2-17 可知,拦截器的 postHandle 方法是获取所有拦截器然后从后向前依次执行。
2.1.7、渲染视图
在执行完拦截器的 postHandle 方法后,最后要做的就是处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
ModelAndViewDefiningException mavDefiningException = (ModelAndViewDefiningException)exception;
this.logger.debug("ModelAndViewDefiningException encountered", exception);
mv = mavDefiningException.getModelAndView();
} else {
Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
// 如有异常,进行全局异常处理
mv = this.processHandlerException(request, response, handler, exception);
errorView = mv != null;
}
}
if (mv != null && !mv.wasCleared()) {
// 渲染视图
this.render(mv, request, response);
if (errorView) {
// 调用request.removeAttribute方法清理request中错误信息
WebUtils.clearErrorRequestAttributes(request);
}
} else if (this.logger.isTraceEnabled()) {
this.logger.trace("No view rendering, null ModelAndView returned.");
}
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
// 调用拦截器的afterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}
}
}
从源码中可以看到,如果在处理器执行的过程中出现了异常但是不捕获的时候,是可以在处理分发结果这一步当中去进行全局异常处理的,而要进行全局异常处理就是要定义一个类,该类上标注 @ControllerAdvice 注解,其中的方法上标注 @ExceptionHandler 注解。最终这俩注解定义的异常处理会被 ExceptionHandlerExceptionResolver 这个类进行处理。
3、总结
接下来对本文内容做一个总结,对于 HTTP 请求来说,tomcat 执行了 HttpServlet#service 方法,继承了 HttpServlet 的 FrameworkServlet 则是执行 doService 方法,而 SpringMVC 的 DispatcherServlet 则是继承了 FrameworkServlet,进入到 SpringMVC 的流程中,在 DispatcherServlet 中的流程如下:
- 先通过 HandlerMapping 拿到 request 对应的 HandlerExecutionChain,然后再拿到 HandlerExecutionChain 中 handler 对应的 HandlerAdapter,执行 HandlerExecutionChain 中 interceptor#prehandle 方法。
- 再通过 HandlerAdapter 去执行 handler,在执行之前要去判断参数,这一步需要参数解析器。反射调用完之后,需要调用返回值解析器。
- 真正方法执行完之后,再执行 HandlerExecutionChain 中 interceptor#posthandle 方法进行拦截器的后置处理。
- SpringMVC 执行完之后返回的是 ModelAndView,因此还需要对 ModelAndView 进行 render,即把 ModelAndView 中的 view 渲染到 response 中。
- 当发生异常时,会将异常拉到用户业务自己的异常处理方法中,这时也需要对参数的返回值进行解析,此时就需要用到 HandlerExceptionResolver 系列。因为用户标记的 @ExceptionHandler 方法已经被 ExceptionHandlerMethodResolver 找到并且注册(key 为对应异常,value 为对应方法),只需要调用该方法就可以对异常进行处理,此时的方法调用和之前的 handler 几乎没有区别。
图3-1 SpringMVC 的执行流程