code-learning/springmvc/17-Spring MVC 源码解析-HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler.md

807 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler
# 1. 概述
本文接 [《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》](http://svip.iocoder.cn/Spring-MVC/HandlerAdapter-2-ServletInvocableHandlerMethod) 一文,我们来分享 HandlerMethodReturnValueHandler HandlerMethod 的**返回值**的处理器接口。代码如下:
```
// HandlerMethodReturnValueHandler.java
public interface HandlerMethodReturnValueHandler {
/**
* 是否支持该类型
*
* Whether the given {@linkplain MethodParameter method return type} is
* supported by this handler.
* @param returnType the method return type to check
* @return {@code true} if this handler supports the supplied return type;
* {@code false} otherwise
*/
boolean supportsReturnType(MethodParameter returnType);
/**
* 处理返回值,设置到 {@param mavContainer} 中
*
* Handle the given return value by adding attributes to the model and
* setting a view or setting the
* {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
* to indicate the response has been handled directly.
* @param returnValue the value returned from the handler method
* @param returnType the type of the return value. This type must have
* previously been passed to {@link #supportsReturnType} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @throws Exception if the return value handling results in an error
*/
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
```
- 两个方法,分别是是否支持解析该类型、以及处理返回值。
# 2. 类图
HandlerMethodReturnValueHandler 的实现类非常多,如下图所示:
[![HandlerMethodReturnValueHandler 类图](17-Spring MVC 源码解析-HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler.assets/01.png)](http://static.iocoder.cn/images/Spring/2022-03-29/01.png)HandlerMethodReturnValueHandler 类图
因为实在太大,胖友可以点击 [传送](http://static.iocoder.cn/images/Spring/2022-03-29/01.png) 查看。
下面,我要说什么化,想必熟悉我的胖友已经知道了,我们就分析几个 HandlerMethodReturnValueHandler 实现类。哈哈哈哈。
# 3. ModelAndViewContainer
`org.springframework.web.method.support.ModelAndViewContainer` ,主要是作为 Model 和 View 的容器,当然其中还有其它属性。
## 3.1 构造方法
```
// ModelAndViewContainer.java
/**
* 是否在 redirect 重定向时,忽略 {@link #redirectModel}
*/
private boolean ignoreDefaultModelOnRedirect = false;
/**
* 视图Object 类型。
*
* 实际情况下,也可以是 String 类型的逻辑视图
*/
@Nullable
private Object view;
/**
* 默认使用的 Model 。实际上是个 Map
*/
private final ModelMap defaultModel = new BindingAwareModelMap();
/**
* redirect 重定向的 Model ,在重定向时使用。
*/
@Nullable
private ModelMap redirectModel;
/**
* 处理器返回 redirect 视图的标识
*/
private boolean redirectModelScenario = false;
/**
* Http 响应状态
*/
@Nullable
private HttpStatus status;
/**
* TODO
*/
private final Set<String> noBinding = new HashSet<>(4);
/**
* TODO
*/
private final Set<String> bindingDisabled = new HashSet<>(4);
/**
* 用于设置 SessionAttribute 的标识
*/
private final SessionStatus sessionStatus = new SimpleSessionStatus();
/**
* 请求是否处理完的标识
*/
private boolean requestHandled = false;
```
- 属性有点多,不要懵逼。我们带着一起在下面的几个方法来瞅瞅。
## 3.2 getModel
`#getModel()` 方法,获得 Model 对象。代码如下:
```
// ModelAndViewContainer.java
public ModelMap getModel() {
// 是否使用默认 Model
if (useDefaultModel()) {
return this.defaultModel;
} else {
if (this.redirectModel == null) {
this.redirectModel = new ModelMap();
}
return this.redirectModel;
}
}
/**
* Whether to use the default model or the redirect model.
*/
private boolean useDefaultModel() {
return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
}
```
- 从代码中,可以看出,有两种情况下,使用
```
defaultModel
```
默认 Model
- 情况一 `!this.redirectModelScenario` ,处理器返回 redirect 视图的标识为 `false` 的时候,即不重定向。
- 情况二 `this.redirectModel == null && !this.ignoreDefaultModelOnRedirect` `redirectModel` 重定向 Model 为**空**,并且 `ignoreDefaultModelOnRedirect` 为 `true` ,即忽略 `defaultModel` 。
- 那么,问题就来了,
```
redirectModelScenario
```
```
ignoreDefaultModelOnRedirect
```
什么时候被改变?
- `redirectModelScenario` 属性,可见 [「6.3 handleReturnValue」](https://svip.iocoder.cn/Spring-MVC/HandlerAdapter-4-HandlerMethodReturnValueHandler/#) 中。
- `ignoreDefaultModelOnRedirect` 属性,和 `RequestMappingHandlerAdapter.ignoreDefaultModelOnRedirect` 的属性是一致的,在 `RequestMappingHandlerAdapter#invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod)` 方法中,进行设置。
- 另外,`org.springframework.ui.ModelMap` 是继承 LinkedHashMap 类,并增加了部分常用方法。比较简单,胖友点击 [传送门](https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/ui/ModelMap.java) 瞅瞅。
## 3.3 View 相关的方法
比较简单,胖友自己瞅瞅。
```
// ModelAndViewContainer.java
public void setViewName(@Nullable String viewName) {
this.view = viewName;
}
@Nullable
public String getViewName() {
return (this.view instanceof String ? (String) this.view : null);
}
public void setView(@Nullable Object view) {
this.view = view;
}
@Nullable
public Object getView() {
return this.view;
}
public boolean isViewReference() {
return (this.view instanceof String);
}
```
## 3.4 requestHandled 标识
关于 `requestHandled` 的修改地方,实际在 Spring MVC 地方蛮多处都可以进行修改。感兴趣的胖友,可以使用 IDEA 右键该属性,选择 “Find Usages” 进行查看。
目前,我们在 [《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》](http://svip.iocoder.cn/Spring-MVC/HandlerAdapter-2-ServletInvocableHandlerMethod) 的 [「5.2 invokeAndHandle」](https://svip.iocoder.cn/Spring-MVC/HandlerAdapter-4-HandlerMethodReturnValueHandler/#) 中,已经看到该属性的修改。
# 4. HandlerMethodReturnValueHandlerComposite
`org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite` ,实现 HandlerMethodReturnValueHandler 接口,复合的 HandlerMethodReturnValueHandler 实现类。
实际上,和我们在 [《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver》](http://svip.iocoder.cn/Spring-MVC/HandlerAdapter-3-HandlerMethodArgumentResolver) 的 [「3. HandlerMethodArgumentResolverComposite」](https://svip.iocoder.cn/Spring-MVC/HandlerAdapter-4-HandlerMethodReturnValueHandler/#) 是一致的。
## 4.1 构造方法
```
// HandlerMethodReturnValueHandlerComposite.java
/**
* HandlerMethodReturnValueHandler 数组
*/
private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
```
另外,在 [《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(一)之 HandlerAdapter》](http://svip.iocoder.cn/Spring-MVC/HandlerAdapter-1-HandlerAdapter) 的 [「7.2.4 getDefaultReturnValueHandlers」](https://svip.iocoder.cn/Spring-MVC/HandlerAdapter-4-HandlerMethodReturnValueHandler/#) 中我们已经看到了HandlerMethodReturnValueHandlerComposite 默认复合的所有 HandlerMethodReturnValueHandler 对象。😈 忘记的胖友,可以点下 [传送门](https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java#L717-L761) 再瞅瞅。
## 4.2 getReturnValueHandler
`#getReturnValueHandler(MethodParameter returnType)` 方法,获得方法返回值对应的 HandlerMethodReturnValueHandler 对象。代码如下:
```
// HandlerMethodReturnValueHandlerComposite.java
@Nullable
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
// 遍历 returnValueHandlers 数组,逐个判断是否支持
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
// 如果支持,则返回
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
```
- 😈 不过有点纳闷,这里为什么不加缓存?!
## 4.3 supportsParameter
实现 `#getReturnValueHandler(MethodParameter returnType)` 方法,如果能获得到对应的 HandlerMethodReturnValueHandler 处理器,则说明支持。代码如下:
```
// HandlerMethodReturnValueHandlerComposite.java
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return getReturnValueHandler(returnType) != null;
}
```
## 4.4 handleReturnValue
`#handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)` 方法,处理返回值。代码如下:
```
// HandlerMethodReturnValueHandlerComposite.java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// <x> 获得 HandlerMethodReturnValueHandler 对象
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
// 如果获取不到,则抛出 IllegalArgumentException 异常
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
// 处理器返回值
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
```
- 其中,比较神奇的是,`<x>` 处,调用 `#selectHandler(Object value, MethodParameter returnType)` 方法,获得 HandlerMethodReturnValueHandler 对象。代码如下:
```
// HandlerMethodReturnValueHandlerComposite.java
@Nullable
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 类图](17-Spring MVC 源码解析-HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler.assets/02.png)](http://static.iocoder.cn/images/Spring/2022-03-29/02.png)HandlerMethodReturnValueHandler 类图
从图中我们也会发现RequestResponseBodyMethodProcessor 也是 HandlerMethodArgumentResolver 的实现类。当然本文暂时不写,未来补充到 [《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver》](http://svip.iocoder.cn/Spring-MVC/HandlerAdapter-3-HandlerMethodArgumentResolver) 中。
------
简单示例代码如下:
```
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/walks")
public List<User> walk() {
List<User> users = new ArrayList();
users.add(new User().setUsername("nihao"));
users.add(new User().setUsername("zaijian"));
return users;
}
}
```
- 虽然,`#walks()` 方法的返回值没添加 `@ResponseBody` 注解,但是 `@RestController` 注解,默认有 `@ResponseBody` 注解 。
## 5.1 构造方法
```
// RequestResponseBodyMethodProcessor.java
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
super(converters);
}
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable ContentNegotiationManager manager) {
super(converters, manager);
}
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable List<Object> requestResponseBodyAdvice) {
super(converters, null, requestResponseBodyAdvice);
}
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
super(converters, manager, requestResponseBodyAdvice);
}
```
- `converters` 参数HttpMessageConverter 数组。关于 HttpMessageConverter ,可以说我们非常熟悉的朋友了。例如,我们想要将 POJO 对象,返回成 JSON 数据给前端,就会使用到 MappingJackson2HttpMessageConverter 类。
- `requestResponseBodyAdvice` 参数,一般情况是 ResponseBodyAdvice 类型,可实现对返回结果的修改。具体的使用示例,可见 [《@ControllerAdvice, ResponseBodyAdvice 统一处理返回值/响应体》](https://blog.csdn.net/u012852374/article/details/53837156) 。
## 5.2 supportsReturnType
实现 `#supportsReturnType(MethodParameter returnType)` 方法,判断是否添加 `@ResponseBody` 注解。代码如下:
```
// RequestResponseBodyMethodProcessor.java
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class)
|| returnType.hasMethodAnnotation(ResponseBody.class));
}
```
## 5.3 handleReturnValue
实现 `#handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)` 方法,代码如下:
```
// RequestResponseBodyMethodProcessor.java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// <1> 设置已处理
mavContainer.setRequestHandled(true);
// <2> 创建请求和响应
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// <3> 使用 HttpMessageConverter 对对象进行转换,并写入到响应
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
```
- `<1>` 处,设置 `mavContainer` 已处理。
- `<2>` 处,创建请求和响应。
- `<3>` 处,调用父类 AbstractMessageConverterMethodProcessor 的 `#writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage)` 方法,使用 HttpMessageConverter 对对象进行转换,并写入到响应。详细解析,见 [「5.4.1 writeWithMessageConverters」](https://svip.iocoder.cn/Spring-MVC/HandlerAdapter-4-HandlerMethodReturnValueHandler/#) 。
### 5.4.1 writeWithMessageConverters
> 该方法,是 AbstractMessageConverterMethodProcessor 中提供。
`#writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage)` 方法,使用 HttpMessageConverter 对对象进行转换,并写入到响应。代码如下:
```
// AbstractMessageConverterMethodProcessor.java
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// <1> 获得 body、valueType、targetType
Object body;
Class<?> valueType;
Type targetType;
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
} else {
body = value;
valueType = getReturnValueType(body, returnType);
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
// <2> 是否为 Resource 类型。暂时无视,实际暂时没用到
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
} catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
// <3> 选择使用的 MediaType
MediaType selectedMediaType = null;
// <3.1> 获得响应中的 ContentType 的值
MediaType contentType = outputMessage.getHeaders().getContentType();
// <3.1.1> 如果存在 ContentType 的值,并且不包含通配符,则使用它作为 selectedMediaType
if (contentType != null && contentType.isConcrete()) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
} else {
HttpServletRequest request = inputMessage.getServletRequest();
// <3.2.1> 从请求中,获得可接受的 MediaType 数组。默认实现是,从请求头 ACCEPT 中获取
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// <3.2.2> 获得可产生的 MediaType 数组
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
// <3.2.3> 如果 body 非空,并且无可产生的 MediaType 数组,则抛出 HttpMediaTypeNotAcceptableException 异常
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
// <3.2.4> 通过 acceptableTypes 来比对,将符合的 producibleType 添加到 mediaTypesToUse 结果数组中
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
// <3.2.5> 如果没有符合的,并且 body 非空,则抛出 HttpMediaTypeNotAcceptableException 异常
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
// <3.2.6> 按照 MediaType 的 specificity 和 quality 排序
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
// <3.2.7> 选择其中一个最匹配的,主要考虑不包含通配符的。例如 application/json;q=0.8 。
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
} else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using '" + selectedMediaType + "', given " +
acceptableTypes + " and supported " + producibleTypes);
}
}
// <4> 如果匹配到,则进行写入逻辑
if (selectedMediaType != null) {
// <4.1> 移除 quality 。例如application/json;q=0.8 移除后为 application/json 。
selectedMediaType = selectedMediaType.removeQualityValue();
// <4.2> 遍历 messageConverters 数组,
for (HttpMessageConverter<?> converter : this.messageConverters) {
// <4.3> 判断 HttpMessageConverter 是否支持转换目标类型
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
// <5.1> 如果有 RequestResponseBodyAdvice ,则可以对返回的结果,做修改。
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
// <5.2> body 非空,则进行写入
if (body != null) {
// 打印日志
final Object theBody = body; // 这个变量的用途是,打印是匿名类,需要有 final
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, traceOn) + "]");
// 添加 CONTENT_DISPOSITION 头。一般情况下用不到,暂时忽略
addContentDispositionHeader(inputMessage, outputMessage);
// <5.3> 写入内容
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
} else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
// <5.4> return 返回。结果整个逻辑
return;
}
}
}
// <6> 如果到达此处,并且 body 非空,说明没有匹配的 HttpMessageConverter 转换器,则抛出 HttpMediaTypeNotAcceptableException 异常
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
```
- 怎么说呢?这个方法逻辑非常多。但是实际上,核心逻辑非常简单:
- 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
/**
* 重定向的表达式的数组
*/
@Nullable
private String[] redirectPatterns;
```
- `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
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
```
- 那么有胖友就会有疑惑?如果想要使用 [「5. RequestResponseBodyMethodProcessor」](https://svip.iocoder.cn/Spring-MVC/HandlerAdapter-4-HandlerMethodReturnValueHandler/#) ,结果返回 String 类型的结果,岂不是被 ViewNameMethodReturnValueHandler ?在回到 [传送门](https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java#L717-L761) 再瞅瞅RequestResponseBodyMethodProcessor 的添加在 ViewNameMethodReturnValueHandler 之前,所以不会有这样的问题。
## 6.3 handleReturnValue
实现 `#handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)` 方法,代码如下:
```
// ViewNameMethodReturnValueHandler.java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 如果是 String 类型
if (returnValue instanceof CharSequence) {
// 设置视图名到 mavContainer 中
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
// 如果是重定向,则标记到 mavContainer 中
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
// 如果是非 String 类型,而且非 void ,则抛出 UnsupportedOperationException 异常
} else if (returnValue != null){
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
```
- `<1>` 处,设置视图名到 `mavContainer` 中。
- `<2>` 处,如果是重定向,则标记到 `mavContainer` 中的 `redirectModelScenario` 属性中。
- 注意噢,胖友是否有发现,此时 `redirectModelScenario` 的 `requestHandled` 属性,并未并未像 [「5. RequestResponseBodyMethodProcessor」](https://svip.iocoder.cn/Spring-MVC/HandlerAdapter-4-HandlerMethodReturnValueHandler/#) 一样,设置为 `true` 。这是为什么呢?因为**返回结果是视图名**的场景下,会使用 ViewResolver 解析出其对应的视图 View 对象,然后执行 `View#render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)` 方法,进行渲染。
# 666. 彩蛋
感觉,还有一些需要写的 HandlerMethodReturnValueHandler 实现类,暂时还没想好。
如果胖友有什么 HandlerMethodReturnValueHandler 实现类希望艿艿来写请在星球给我留言。TODO 9999 HandlerMethodReturnValueHandler
参考和推荐如下文章:
- 韩路彪 [《看透 Spring MVC源代码分析与实践》](https://item.jd.com/11807414.html) 的 [「第 13.8 HandlerMethodReturnValueHandler」](https://svip.iocoder.cn/Spring-MVC/HandlerAdapter-4-HandlerMethodReturnValueHandler/#) 小节