1. 概述
本文接 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》 一文,我们来分享 HandlerMethodReturnValueHandler ,HandlerMethod 的返回值的处理器接口。代码如下:
// HandlerMethodReturnValueHandler.java |
- 两个方法,分别是是否支持解析该类型、以及处理返回值。
2. 类图
HandlerMethodReturnValueHandler 的实现类非常多,如下图所示:
HandlerMethodReturnValueHandler 类图
因为实在太大,胖友可以点击 传送 查看。
下面,我要说什么化,想必熟悉我的胖友已经知道了,我们就分析几个 HandlerMethodReturnValueHandler 实现类。哈哈哈哈。
3. ModelAndViewContainer
org.springframework.web.method.support.ModelAndViewContainer
,主要是作为 Model 和 View 的容器,当然其中还有其它属性。
3.1 构造方法
// ModelAndViewContainer.java |
- 属性有点多,不要懵逼。我们带着一起在下面的几个方法来瞅瞅。
3.2 getModel
#getModel()
方法,获得 Model 对象。代码如下:
// ModelAndViewContainer.java |
- 从代码中,可以看出,有两种情况下,使用
defaultModel
默认 Model :- 情况一
!this.redirectModelScenario
,处理器返回 redirect 视图的标识为false
的时候,即不重定向。 - 情况二
this.redirectModel == null && !this.ignoreDefaultModelOnRedirect
,redirectModel
重定向 Model 为空,并且ignoreDefaultModelOnRedirect
为true
,即忽略defaultModel
。
- 情况一
- 那么,问题就来了,
redirectModelScenario
和ignoreDefaultModelOnRedirect
什么时候被改变?redirectModelScenario
属性,可见 「6.3 handleReturnValue」 中。ignoreDefaultModelOnRedirect
属性,和RequestMappingHandlerAdapter.ignoreDefaultModelOnRedirect
的属性是一致的,在RequestMappingHandlerAdapter#invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod)
方法中,进行设置。
- 另外,
org.springframework.ui.ModelMap
是继承 LinkedHashMap 类,并增加了部分常用方法。比较简单,胖友点击 传送门 瞅瞅。
3.3 View 相关的方法
比较简单,胖友自己瞅瞅。
// ModelAndViewContainer.java |
3.4 requestHandled 标识
关于 requestHandled
的修改地方,实际在 Spring MVC 地方蛮多处都可以进行修改。感兴趣的胖友,可以使用 IDEA 右键该属性,选择 “Find Usages” 进行查看。
目前,我们在 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》 的 「5.2 invokeAndHandle」 中,已经看到该属性的修改。
4. HandlerMethodReturnValueHandlerComposite
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
,实现 HandlerMethodReturnValueHandler 接口,复合的 HandlerMethodReturnValueHandler 实现类。
实际上,和我们在 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver》 的 「3. HandlerMethodArgumentResolverComposite」 是一致的。
4.1 构造方法
// HandlerMethodReturnValueHandlerComposite.java |
另外,在 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(一)之 HandlerAdapter》 的 「7.2.4 getDefaultReturnValueHandlers」 中,我们已经看到了,HandlerMethodReturnValueHandlerComposite 默认复合的所有 HandlerMethodReturnValueHandler 对象。😈 忘记的胖友,可以点下 传送门 再瞅瞅。
4.2 getReturnValueHandler
#getReturnValueHandler(MethodParameter returnType)
方法,获得方法返回值对应的 HandlerMethodReturnValueHandler 对象。代码如下:
// HandlerMethodReturnValueHandlerComposite.java |
- 😈 不过有点纳闷,这里为什么不加缓存?!
4.3 supportsParameter
实现 #getReturnValueHandler(MethodParameter returnType)
方法,如果能获得到对应的 HandlerMethodReturnValueHandler 处理器,则说明支持。代码如下:
// HandlerMethodReturnValueHandlerComposite.java |
4.4 handleReturnValue
#handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法,处理返回值。代码如下:
// HandlerMethodReturnValueHandlerComposite.java |
其中,比较神奇的是,
<x>
处,调用#selectHandler(Object value, MethodParameter returnType)
方法,获得 HandlerMethodReturnValueHandler 对象。代码如下:// HandlerMethodReturnValueHandlerComposite.java
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
// 判断是否为异步返回值
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
// 遍历 HandlerMethodReturnValueHandler 数组,逐个判断是否支持
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
// 如果是异步返回值的类型,则必须要求是 AsyncHandlerMethodReturnValueHandler 类型的处理器
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
// 如果支持,则返回
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
// 判断逻辑是,有异步处理器 AsyncHandlerMethodReturnValueHandler ,并且返回值符合异步的类型
private boolean isAsyncReturnValue(@Nullable Object value, MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler instanceof AsyncHandlerMethodReturnValueHandler &&
((AsyncHandlerMethodReturnValueHandler) handler).isAsyncReturnValue(value, returnType)) {
return true;
}
}
return false;
}- 在
#getReturnValueHandler(MethodParameter returnType)
方法的基础上,增加了异步处理器 AsyncHandlerMethodReturnValueHandler 的逻辑。
- 在
5. RequestResponseBodyMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
,继承 AbstractMessageConverterMethodProcessor 抽象类,处理请求参数添加了 @RequestBody
注解,或者返回值添加了 @ResponseBody
注解的处理。
因为前后端分离之后,后端基本是提供 Restful API ,所以 RequestResponseBodyMethodProcessor 成为了目前最常用的 HandlerMethodReturnValueHandler 实现类。
HandlerMethodReturnValueHandler 类图
从图中,我们也会发现,RequestResponseBodyMethodProcessor 也是 HandlerMethodArgumentResolver 的实现类。当然本文暂时不写,未来补充到 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver》 中。
简单示例代码如下:
|
- 虽然,
#walks()
方法的返回值没添加@ResponseBody
注解,但是@RestController
注解,默认有@ResponseBody
注解 。
5.1 构造方法
// RequestResponseBodyMethodProcessor.java |
converters
参数,HttpMessageConverter 数组。关于 HttpMessageConverter ,可以说我们非常熟悉的朋友了。例如,我们想要将 POJO 对象,返回成 JSON 数据给前端,就会使用到 MappingJackson2HttpMessageConverter 类。requestResponseBodyAdvice
参数,一般情况是 ResponseBodyAdvice 类型,可实现对返回结果的修改。具体的使用示例,可见 《@ControllerAdvice, ResponseBodyAdvice 统一处理返回值/响应体》 。
5.2 supportsReturnType
实现 #supportsReturnType(MethodParameter returnType)
方法,判断是否添加 @ResponseBody
注解。代码如下:
// RequestResponseBodyMethodProcessor.java |
5.3 handleReturnValue
实现 #handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法,代码如下:
// RequestResponseBodyMethodProcessor.java |
<1>
处,设置mavContainer
已处理。<2>
处,创建请求和响应。<3>
处,调用父类 AbstractMessageConverterMethodProcessor 的#writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage)
方法,使用 HttpMessageConverter 对对象进行转换,并写入到响应。详细解析,见 「5.4.1 writeWithMessageConverters」 。
5.4.1 writeWithMessageConverters
该方法,是 AbstractMessageConverterMethodProcessor 中提供。
#writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage)
方法,使用 HttpMessageConverter 对对象进行转换,并写入到响应。代码如下:
// AbstractMessageConverterMethodProcessor.java |
- 怎么说呢?这个方法逻辑非常多。但是实际上,核心逻辑非常简单:
- 1、获得响应使用的 MediaType 对象。
- 2、获得响应结果和 Media 对象获得对应的 HttpMessageConverter 对象。
- 3、使用 HttpMessageConverter 将响应结果进行转化,写入到响应中。
<1>
处,获得body
、valueType
、targetType
。例如上面提供的示例,这三个值分别是,users
数组、ArrayList、User 。<2>
处,调用#isResourceType(Object value, MethodParameter returnType)
方法,判断是否为 Resource 类型。代码如下:// AbstractMessageConverterMethodProcessor.java
protected boolean isResourceType(@Nullable Object value, MethodParameter returnType) {
Class<?> clazz = getReturnValueType(value, returnType);
return clazz != InputStreamResource.class && Resource.class.isAssignableFrom(clazz);
}- 因为我们实际使用时,不会返回 Resource 类型,所以暂时无视这个块逻辑。
- ========== 第一步 ==========
<3>
处,选择使用的 MediaType 对象。<3.1>
处,获得响应中的 ContentType 的值。<3.1.1>
处,如果存在 ContentType 的值,并且不包含通配符,则使用它作为selectedMediaType
。目前调试下来,<3.1>
处,返回的都是null
。所以此处的逻辑,可以无视。😈 暂时不知道,什么情况下返回的是非null
。<3.2.1>
处,调用#getAcceptableMediaTypes(HttpServletRequest request)
方法,从请求中,获得可接受的 MediaType 数组。默认实现是,从请求头"ACCEPT"
中获取。关于这个方法,比较简单,胖友自己去瞅瞅,重点实现在org.springframework.web.accept.HeaderContentNegotiationStrategy
类中。<3.2.2>
处,调用#getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type targetType)
方法,获得可产生的 MediaType 数组。代码如下:// AbstractMessageConverterMethodProcessor.java
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
// 先从请求 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性种获得。该属性的来源是 @RequestMapping(producer = xxx) 。
Set<MediaType> mediaTypes =
(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// 如果非空,则使用该属性
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes);
// <x> 如果 allSupportedMediaTypes 非空,则遍历 HttpMessageConverter 数组,进行类型匹配
} else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
} else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
// 其它,则返回 MediaType.ALL
} else {
return Collections.singletonList(MediaType.ALL);
}
}- 逻辑比较简单,胖友自己瞅瞅。重点在
<x>
处。
- 逻辑比较简单,胖友自己瞅瞅。重点在
<3.2.3>
处,如果body
非空,并且无可产生的 MediaType 数组,则抛出 HttpMediaTypeNotAcceptableException 异常。- 【重要】
<3.2.4>
处,通过acceptableTypes
来比对,将符合的producibleType
添加到mediaTypesToUse
结果数组中。 <3.2.5>
处,如果没有符合的,并且body
非空,则抛出 HttpMediaTypeNotAcceptableException 异常。- 【重要】
<3.2.6>
处,按照 MediaType 的 specificity 和 quality 排序。 - 【重要】
<3.2.7>
处,选择其中一个最匹配的,主要考虑不包含通配符的。例如application/json;q=0.8
。 - ========== 第二步 + 第三步 ==========
<4>
处,如果匹配到,则进行写入逻辑。<4.1>
处,移除quality
。例如,application/json;q=0.8
移除后为application/json
。<4.2>
处,遍历messageConverters
数组。关于messageConverters
的定义,代码如下:// AbstractMessageConverterMethodProcessor.java
/**
* HttpMessageConverter 数组
*/
protected final List<HttpMessageConverter<?>> messageConverters;<4.3>
处,判断 HttpMessageConverter 是否支持转换目标类型。<5.1>
处,如果有 RequestResponseBodyAdvice ,则可以对返回的结果,做修改。关于 RequestResponseBodyAdvice 的定义,代码如下:// AbstractMessageConverterMethodProcessor.java
/**
* RequestResponseBodyAdviceChain 对象
*/
private final RequestResponseBodyAdviceChain advice;
// 构造方法中
this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);- 关于 RequestResponseBodyAdviceChain 类,本文暂时不去做详细解析,胖友先自己去研究下。
<5.2>
处,body
非空,则进行写入。- 【重要】
<5.3>
处,写入内容。 <5.4>
处,return
返回。结束整个逻辑。<6>
处,如果到达此处,并且body
非空,说明没有匹配的 HttpMessageConverter 转换器,则抛出 HttpMediaTypeNotAcceptableException 异常。
至此,整个逻辑已经解析完成。在日常使用 Spring MVC 中,我们经常碰到的组件的调用,应该要有一个比较好的理解。如果没有,在多多调试。特别是:
- 1、HttpMessageConverter
- 2、RequestResponseBodyAdviceChain
6. ViewNameMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler
,实现 HandlerMethodReturnValueHandler 接口,处理返回结果是视图名的 ReturnValueHandler 实现类。
ViewNameMethodReturnValueHandler 适用于前后端未分离,Controller 返回视图名的场景,例如 JSP、Freemarker 等等。
6.1 构造方法
// ViewNameMethodReturnValueHandler.java |
redirectPatterns
属性,一般情况下,不进行设置。至于用途,我们来看看#isRedirectViewName(String viewName)
方法,判断是否为重定向的视图名。代码如下:// ViewNameMethodReturnValueHandler.java
protected boolean isRedirectViewName(String viewName) {
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) // 符合 redirectPatterns 表达式
|| viewName.startsWith("redirect:")); // 以 redirect: 开头
}- 😈 是不是明白落?是不是也弄清楚,为什么
"redirect:"
开头,就是重定向的视图。
- 😈 是不是明白落?是不是也弄清楚,为什么
6.2 supportsReturnType
实现 #supportsReturnType(MethodParameter returnType)
方法,判断返回值类型是否为 void
或者字符串。代码如下:
// ViewNameMethodReturnValueHandler.java |
- 那么有胖友就会有疑惑?如果想要使用 「5. RequestResponseBodyMethodProcessor」 ,结果返回 String 类型的结果,岂不是被 ViewNameMethodReturnValueHandler ?在回到 传送门 再瞅瞅,RequestResponseBodyMethodProcessor 的添加在 ViewNameMethodReturnValueHandler 之前,所以不会有这样的问题。
6.3 handleReturnValue
实现 #handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法,代码如下:
// ViewNameMethodReturnValueHandler.java |
<1>
处,设置视图名到mavContainer
中。<2>
处,如果是重定向,则标记到mavContainer
中的redirectModelScenario
属性中。- 注意噢,胖友是否有发现,此时
redirectModelScenario
的requestHandled
属性,并未并未像 「5. RequestResponseBodyMethodProcessor」 一样,设置为true
。这是为什么呢?因为返回结果是视图名的场景下,会使用 ViewResolver 解析出其对应的视图 View 对象,然后执行View#render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
方法,进行渲染。
666. 彩蛋
感觉,还有一些需要写的 HandlerMethodReturnValueHandler 实现类,暂时还没想好。
如果胖友有什么 HandlerMethodReturnValueHandler 实现类,希望艿艿来写,请在星球给我留言。TODO 9999 HandlerMethodReturnValueHandler
参考和推荐如下文章: