Spring MVC 源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler
参考 知识星球 中 芋道源码 星球的源码解析,一个活跃度非常高的 Java 技术社群,感兴趣的小伙伴可以加入 芋道源码 星球,一起学习😄
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读
Spring 版本:5.1.14.RELEASE
该系列其他文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》
HandlerAdapter 组件
HandlerAdapter 组件,处理器的适配器。因为处理器 handler
的类型是 Object 类型,需要有一个调用者来实现 handler
是怎么被执行。Spring 中的处理器的实现多变,比如用户的处理器可以实现 Controller 接口或者 HttpRequestHandler 接口,也可以用 @RequestMapping
注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器
由于 HandlerMapping 组件涉及到的内容较多,考虑到内容的排版,所以将这部分内容拆分成了五个模块,依次进行分析:
- 《HandlerAdapter 组件(一)之 HandlerAdapter》
- 《HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》
- 《HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver》
- 《HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler》
- 《HandlerAdapter 组件(五)之 HttpMessageConverter》
HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler
本文是接着《HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver》一文来分享 HandlerMethodReturnValueHandler 组件。在 HandlerAdapter
执行处理器的过程中,具体的执行过程交由 ServletInvocableHandlerMethod
对象来完成,其中需要先通过 HandlerMethodArgumentResolver
参数解析器从请求中解析出方法的入参,然后再通过反射机制调用对应的方法,获取到执行结果后需要通过 HandlerMethodReturnValueHandler 结果处理器来进行处理。
回顾
先来回顾一下 ServletInvocableHandlerMethod
在哪里调用返回值处理器的,可以回到 《HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》 中 ServletInvocableHandlerMethod 小节下面的 invokeAndHandle
方法,如下:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// <1> 执行调用
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// <2> 设置响应状态码
setResponseStatus(webRequest);
// <3> 设置 ModelAndViewContainer 为请求已处理,返回,和 @ResponseStatus 注解相关
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
// <4> 设置 ModelAndViewContainer 为请求未处理
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// <5> 处理返回值
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
<5>
处调用returnValueHandlers
对返回结果进行处理returnValueHandlers
为 HandlerMethodReturnValueHandlerComposite 组合对象,包含了许多的结果处理器
HandlerMethodReturnValueHandler 接口
org.springframework.web.method.support.HandlerMethodReturnValueHandler
,返回结果处理器
public interface HandlerMethodReturnValueHandler {
/**
* 是否支持该类型
*/
boolean supportsReturnType(MethodParameter returnType);
/**
* 处理返回值
*/
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
类图
![](https://cdn.jsdelivr.net/gh/liu844869663/cdn@master/Spring%20MVC/HandlerMethodReturnValueHandler.png)
因为返回结果类型是多变的,所以会有许多的 HandlerMethodReturnValueHandler 的实现类,上图仅列出了本文会分析的两个实现类
ModelAndViewContainer
org.springframework.web.method.support.ModelAndViewContainer
,主要是作为 Model 和 View 的容器
构造方法
public class ModelAndViewContainer {
/**
* 是否在 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;
private final Set<String> noBinding = new HashSet<>(4);
private final Set<String> bindingDisabled = new HashSet<>(4);
/**
* 用于设置 SessionAttribute 的标识
*/
private final SessionStatus sessionStatus = new SimpleSessionStatus();
/**
* 请求是否处理完的标识
*/
private boolean requestHandled = false;
}
getModel
getModel()
方法,获得 Model 对象。代码如下:
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
属性,在下文的 ViewNameMethodReturnValueHandler的handleReturnValue方法中会设置为true
,详情见下文 -
ignoreDefaultModelOnRedirect
属性,和 RequestMappingHandlerAdapter 的ignoreDefaultModelOnRedirect
的属性是一致的,默认为false
在 RequestMappingHandlerAdapter 的
invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod)
方法中,进行设置
-
-
另外,
org.springframework.ui.ModelMap
是继承 LinkedHashMap 类,并增加了部分常用方法,比较简单
View 相关的方法
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);
}
requestHandled 属性
请求是否处理完的标识
关于 requestHandled
的修改地方,实际在 Spring MVC 地方蛮多处都可以进行修改,例如:
-
在本文的开始处,
ServletInvocableHandlerMethod
对象的invokeAndHandle
方法中,会先设置为false
,表示请求还未处理,再交由 HandlerMethodReturnValueHandler 结果处理器去处理 -
在后文的
RequestResponseBodyMethodProcessor
的handleReturnValue
会设置为true
处理完结果后,接下来 RequestMappingHandlerAdapter
需要通过 ModelAndViewContainer
获取 ModelAndView
对象,会用到 requestHandled
这个属性
// RequestMappingHandlerAdapter.java
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
// 情况一,如果 mavContainer 已处理,则返回“空”的 ModelAndView 对象。
if (mavContainer.isRequestHandled()) {
return null;
}
// 情况二,如果 mavContainer 未处理,则基于 `mavContainer` 生成 ModelAndView 对象
ModelMap model = mavContainer.getModel();
// 创建 ModelAndView 对象,并设置相关属性
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
看到没,如果已处理,则返回的 ModelAndView
对象为 null
HandlerMethodReturnValueHandlerComposite
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
,实现 HandlerMethodReturnValueHandler 接口,复合的 HandlerMethodReturnValueHandler 实现类
构造方法
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
/** HandlerMethodReturnValueHandler 数组 */
private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
}
在《HandlerAdapter 组件(一)之 HandlerAdapter》的RequestMappingHandlerAdapter小节的 getDefaultReturnValueHandlers
方法中可以看到,默认的 returnValueHandlers
有哪些 HandlerMethodReturnValueHandler 实现类,注意这里是有顺序的添加哦
getReturnValueHandler
getReturnValueHandler(MethodParameter returnType)
方法,获得方法返回值对应的 HandlerMethodReturnValueHandler 对象,方法如下:
@Nullable
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
很简单,遍历所有的 HandlerMethodReturnValueHandler 实现类,如果支持这个返回结果,则直接返回
这里为什么不加缓存呢?
supportsReturnType
supportsReturnType(MethodParameter returnType)
方法,判断是否支持该返回类型,方法如下:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return getReturnValueHandler(returnType) != null;
}
实际上就是调用 getReturnValueHandler(MethodParameter returnType)
方法,存在对应的 HandlerMethodReturnValueHandler 实现类表示支持
handleReturnValue
handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法,处理返回值,方法如下:
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// <x> 获得 HandlerMethodReturnValueHandler 对象
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
这里好奇的是没有调用 getReturnValueHandler(MethodParameter returnType)
方法获取对应的 HandlerMethodReturnValueHandler 对象,而是调用 selectHandler(Object value, MethodParameter returnType)
方法,方法如下:
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
// 判断是否为异步返回值
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
// 遍历 HandlerMethodReturnValueHandler 数组,逐个判断是否支持
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
// 如果支持,则返回
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
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 的判断
【重点】RequestResponseBodyMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
,继承 AbstractMessageConverterMethodProcessor 抽象类,处理方法参数添加了 @RequestBody
注解方法入参,或者处理方法添加了 @ResponseBody
注解的返回值。
因为前后端分离之后,后端基本是提供 Restful API ,所以 RequestResponseBodyMethodProcessor 成为了目前最常用的 HandlerMethodReturnValueHandler 实现类。
![](https://cdn.jsdelivr.net/gh/liu844869663/cdn@master/Spring%20MVC/HandlerMethodReturnValueHandler.png)
从图中,我们也会发现,RequestResponseBodyMethodProcessor 也是 HandlerMethodArgumentResolver 的实现类。示例代码:
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/walks")
public List<User> walk(@RequestBody User user) {
List<User> users = new ArrayList();
users.add(new User().setUsername("nihao"));
users.add(new User().setUsername("zaijian"));
return users;
}
}
虽然,walks()
方法的返回值没添加 @ResponseBody
注解,但是 @RestController
注解,默认有 @ResponseBody
注解
构造方法
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
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
参数,在父类 AbstractMessageConverterMethodArgumentResolver 中会将其转换成 RequestResponseBodyAdviceChain 对象advice
,不知你是否还记得这个参数,来回顾一下:在《HandlerAdapter 组件(一)之 HandlerAdapter》的RequestMappingHandlerAdapter的1.afterPropertiesSet 初始化方法中,第一步就会初始化所有 ControllerAdvice 相关的类
然后在1.4 getDefaultReturnValueHandlers方法中,创建 RequestResponseBodyMethodProcessor 处理器时,会传入
requestResponseBodyAdvice
参数
supportsParameter
实现 supportsParameter(MethodParameter returnType)
方法,判断是否支持处理该方法参数,方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 该参数是否有 @RequestBody 注解
return parameter.hasParameterAnnotation(RequestBody.class);
}
该方法参数是否有 @RequestBody
注解
resolveArgument
实现 resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)
方法,从请求中解析出带有 @RequestBody
注解的参数,方法如下:
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 从请求体中解析出方法入参对象
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
// 数据绑定相关
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
// 返回方法入参对象,如果有必要,则通过 Optional 获取对应的方法入参
return adaptArgumentIfNecessary(arg, parameter);
}
调用readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType)
方法,从请求体中解析出方法入参对象
【核心】readWithMessageConverters
从请求体中解析出方法入参,方法如下:
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// <1> 创建 ServletServerHttpRequest 请求对象
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
// <2> 读取请求体中的消息并转换成入参对象
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
// <3> 校验方法入参对象
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
// AbstractMessageConverterMethodArgumentResolver.java
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// <1> 获取使用的 MediaType 对象
MediaType contentType;
boolean noContentType = false;
try {
// <1.1> 从请求头中获取 "Content-Type"
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
// <1.2> 为空则默认为 application/octet-stream
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
// <2> 获取方法参数的 containing class 和 目标类型,用于 HttpMessageConverter 解析
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
// 如果为空,则从方法参数中解析出来
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
// <3> 获取 HTTP 方法
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
// <4> 开始从请求中解析方法入参
EmptyBodyCheckingHttpInputMessage message;
try {
// <4.1> 将请求消息对象封装成 EmptyBodyCheckingHttpInputMessage,用于校验是否有请求体,没有的话设置为 `null`
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
// <4.2> 遍历 HttpMessageConverter
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
// 如果该 HttpMessageConverter 能够读取当前请求体解析出方法入参
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
// <4.2.1> 如果请求体不为空
if (message.hasBody()) {
HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
// 通过该 HttpMessageConverter 从请求体中解析出方法入参对象
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
// 调用 RequestResponseBodyAdvice 的 afterBodyRead 方法,存在 RequestBodyAdvice 则对方法入参进行修改
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
// <4.2.2> 如果请求体为空,则无需解析请求体
else {
// 调用 RequestResponseBodyAdvice 的 afterBodyRead 方法,存在 RequestBodyAdvice 则对方法入参进行修改
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
// <5> 校验解析出来的方法入参对象是否为空
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
// 打印日志
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
});
// <6> 返回方法入参对象
return body;
}
我们直接看到父类 AbstractMessageConverterMethodArgumentResolver 的 readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType)
这个核心方法,大致逻辑如下:
-
获取使用的 MediaType 对象
contentType
- 从请求头中获取
Content-Type
- 请求头中没有则设置为默认的类型
application/octet-stream
- 从请求头中获取
-
获取方法参数的
containing class
和targetClass 目标类型
,用于 HttpMessageConverter 解析 -
获取 HTTP 方法
-
开始从请求中解析方法入参
Object body
-
将请求消息对象封装成 EmptyBodyCheckingHttpInputMessage,用于校验是否有请求体,没有的话设置为
null
-
遍历所有的 HttpMessageConverter 实现类,调用其
canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType)
方法,判断当前 HttpMessageConverter 实现类是否支持解析该方法入参,如果返回true
,则使用该 HttpMessageConverter 实现类进行解析- 如果请求体不为空,则通过该 HttpMessageConverter 从请求体中解析出方法入参对象
- 如果请求体为空,则无需解析请求体
注意:上面不管请求体是否为空,都会调用
RequestResponseBodyAdvice
的afterBodyRead
方法,存在 RequestBodyAdvice 则对方法入参进行修改
-
-
校验解析出来的方法入参对象是否为空,抛出异常或者返回
null
-
返回方法入参对象
body
方法虽然很长,但是不难理解,大致逻辑就是找到合适的 HttpMessageConverter 实现类从请求体中获取到方法入参对象
逻辑和下面的 writeWithMessageConverters
差不多,我们重点来看到下面这个方法😈
supportsReturnType
实现 supportsReturnType(MethodParameter returnType)
方法,判断是否支持处理该返回类型,方法如下:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
// 该方法或者所在类是否有 @ResponseBody 注解
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
该方法或者所在类是否有 @ResponseBody
注解
handleReturnValue
实现 handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法,方法如下:
@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);
}
-
设置
mavContainer
已处理,也就是修改它的requestHandled
属性为true
,表示请求已处理,后续获取到的ModelAndView
对象就为null
-
创建请求和响应,这里是获取到
javax.servlet.http.HttpServletRequest
和javax.servlet.http.HttpServletResponse
,然后分别封装成org.springframework.http.server.ServletServerHttpRequest
和org.springframework.http.server.ServletServerHttpResponse
对象,便于从请求中获取数据,往响应中设置数据 -
调用父类 AbstractMessageConverterMethodProcessor 的
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage)
方法,使用 HttpMessageConverter 对对象进行转换,并写入到响应
不知你是否还记得 HttpMessageConverter 是在哪儿会初始化呢?我们来回顾一下
回到《HandlerAdapter 组件(一)之 HandlerAdapter》的RequestMappingHandlerAdapter的构造方法中,默认会添加了四个 HttpMessageConverter 对象。当然,默认还会添加其他的,例如 MappingJackson2HttpMessageConverter 为 JSON 消息格式的转换器,至于其他 HttpMessageConverter 实现类如何添加的,本文就不分析了,你知道就行😈
然后在 1.4 getDefaultReturnValueHandlers 方法中,创建 RequestResponseBodyMethodProcessor 处理器时,会传入
getMessageConverters()
参数,也就是获取所有的 HttpMessageConverter 实现类,所以在下面这个方法就需要用到
【核心】writeWithMessageConverters
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;
// 获取返回结果的类型(返回值 body 不为空则直接获取其类型,否则从返回结果类型 returnType 获取其返回值类型)
valueType = getReturnValueType(body, returnType);
// 获取泛型
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
// <2> 是否为 Resource 类型
if (isResourceType(value, returnType)) {
// 设置响应头 Accept-Ranges
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
// 数据不为空、请求头中的 Range 不为空、响应码为 200
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null
&& outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
// 断点续传,客户端已下载一部分数据,此时需要设置响应码为 206
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.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
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) {
// 打印日志
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>
处,获得 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);
}
设置响应头 Accept-Ranges 为 "bytes",如果数据不为空,且请求头中的 Range 不为空,且响应码为 200,则设置状态码为 206(断点续传,客户端已下载一部分数据),这里不做过多的讲述
========== 第一步 ==========
- 选择使用的 MediaType 对象
selectedMediaType
- 获得响应中的 ContentType 的值,如果存在 ContentType 的值,并且不包含通配符,则使用它作为
selectedMediaType
- 否则,从请求中找到合适的 MediaType 对象
- 从请求中,获得可接受的 MediaType 数组
acceptableTypes
。默认实现是,从请求头 ACCEPT 中获取 - 获得可产生的 MediaType 数组
producibleTypes
- 如果
body
非空,并且无可产生的 MediaType 数组producibleTypes
,则抛出 HttpMediaTypeNotAcceptableException 异常 - 通过
acceptableTypes
来比对,将符合的producibleType
添加到mediaTypesToUse
结果数组中 - 如果没有符合的,并且
body
非空,则抛出 HttpMediaTypeNotAcceptableException 异常 - 按照 MediaType 的 specificity 和 quality 排序(权重),对
mediaTypesToUse
进行排序 - 选择其中一个最匹配的,主要考虑不包含通配符的,例如
application/json;q=0.8
- 从请求中,获得可接受的 MediaType 数组
- 获得响应中的 ContentType 的值,如果存在 ContentType 的值,并且不包含通配符,则使用它作为
========== 第二步 ==========
- 如果匹配到 MediaType 对象
selectedMediaType
不为空,则进行写入逻辑- 移除 quality 。例如,
application/json;q=0.8
移除后为application/json
- 遍历
messageConverters
数组,也就是所有的 HttpMessageConverter 实现类 - 判断当前 HttpMessageConverter 是否支持转换目标类型,调用其
canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType)
方法进行判断
- 移除 quality 。例如,
========== 第三步:写入响应体==========
-
如果
4.3
的结果为true
,表示当前 HttpMessageConverter 实现类可以处理该返回类型-
调用
RequestResponseBodyAdvice
的beforeBodyWrite
方法,存在 ResponseBodyAdvice 则对返回的结果进行修改// RequestResponseBodyAdviceChain.java @Override @Nullable public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType, ServerHttpRequest request, ServerHttpResponse response) { return processBody(body, returnType, contentType, converterType, request, response); } @Nullable private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType, ServerHttpRequest request, ServerHttpResponse response) { for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) { if (advice.supports(returnType, converterType)) { body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType, contentType, converterType, request, response); } } return body; }
就是你添加了@ControllerAdvice注解的 ResponseBodyAdvice 实现类在这里会被调用
-
body
非空,则进行写入,如果没有Content-Disposition
请求头,则尝试添加一个,关于文件相关的内容 -
调用当前 HttpMessageConverter 实现类的
write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
方法,将body
写入响应中 -
return
返回,结束整个逻辑
-
-
到达了此处,说明第
4
步 没有找到对应的 MediaType 对象,或者第5
步没有一个 HttpMessageConverter 实现类支持处理该返回结果如果
body
不为空,也就是说有返回值但是没有处理,则抛出 HttpMediaTypeNotAcceptableException 异常
虽然上面的方法很长,但是不难理解,大致逻辑就是找到合适的 HttpMessageConverter 实现类去将返回结果写入到响应体中😈
但是 HttpMessageConverter 怎么才合适,怎么写入到响应体中,没有展开讨论,涉及到的内容不少,就在下一篇文档《HandlerAdapter 组件(五)之 HttpMessageConverter》中分析吧
ViewNameMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler
,实现 HandlerMethodReturnValueHandler 接口,处理返回结果是视图名的 ReturnValueHandler 实现类。
ViewNameMethodReturnValueHandler 适用于前后端未分离,Controller 返回视图名的场景,例如 JSP、Freemarker 等等。
构造方法
public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
/**
* 重定向的表达式的数组
*/
@Nullable
private String[] redirectPatterns;
protected boolean isRedirectViewName(String viewName) {
// 符合 redirectPatterns 表达式,或者以 redirect: 开头
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
}
}
redirectPatterns
:重定向的表达式的数组,用于判断某个视图是否为重定向的视图,一般情况下,不进行设置。
可以看到isRedirectViewName(String viewName)
方法,判断某个视图是否为重定向的视图,如果视图名以 redirect:
开头,也是重定向的视图
supportsReturnType
实现 supportsReturnType(MethodParameter returnType)
方法,判断是否支持处理该返回类型,方法如下:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
该方法的返回类型是否为void
或者字符串
你是否会疑惑?如果我返回的是字符串,想要使用 RequestResponseBodyMethodProcessor 怎么办,不会有问题吗?
回到《HandlerAdapter 组件(一)之 HandlerAdapter》的RequestMappingHandlerAdapter的1.4 getDefaultReturnValueHandlers方法中,如下:
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(); // ... 省略其他 HandlerMethodReturnValueHandler 实现类的添加 handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); // Multi-purpose return value types handlers.add(new ViewNameMethodReturnValueHandler()); // ... 省略其他 HandlerMethodReturnValueHandler 实现类的添加 return handlers; }
RequestResponseBodyMethodProcessor 是在 ViewNameMethodReturnValueHandler 之前添加的,所以不会出现上述问题
handleReturnValue
实现 handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法,代码如下:
@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());
}
}
- 如果返回结果是
String
类型,则作为视图名设置到mavContainer
中 - 如果是重定向,则标记到
mavContainer
中的redirectModelScenario
属性中为true
注意,此时 mavContainer
的 requestHandled
属性,并未并未像 RequestResponseBodyMethodProcessor 一样,设置为 true
表示请求已处理
这是为什么呢?因为返回结果是视图名的场景下,需要使用 ViewResolver 从 ModelAndView 对象中解析出其对应的视图 View 对象,然后执行 View#render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
方法,进行渲染。如果你设置为 true
,在后续获取到的 ModelAndView
对象就为null
了,无法渲染视图
总结
在 HandlerAdapter
执行 HandlerMethod
处理器的过程中,会将该处理器封装成 ServletInvocableHandlerMethod
对象,通过该对象来执行处理器。该对象通过反射机制调用对应的方法,在调用方法之前,借助 HandlerMethodArgumentResolver
参数解析器从请求中获取到对应的方法参数值,在调用方法之后,需要借助于HandlerMethodReturnValueHandler 返回值处理器将返回结果设置到响应中,或者设置相应的 Model 和 View 用于后续的视图渲染。
HandlerMethodReturnValueHandler 返回值处理器的实现类非常多,采用了组合模式来进行处理,如果有某一个返回值处理器支持处理该返回值类型,则使用它对返回结果进行处理,例如将返回结果写入响应体中。注意,这里有一定的先后顺序,因为是通过 ArrayList 保存所有的实现类,排在前面的实现类则优先处理。
本文分析了我们常用的 @ResponseBody
注解和前后端未分离时返回视图名两种处理方式,对应的 HandlerMethodReturnValueHandler 实现类,如下:
RequestResponseBodyMethodProcessor
:处理方法参数添加了@RequestBody
注解方法入参,或者处理方法添加了@ResponseBody
注解的返回值。在前后端分离之后,后端基本是提供 Restful API ,所以这种方式成为了目前最常用的 HandlerMethodReturnValueHandler 实现类- 核心逻辑不复杂,主要是通过
HttpMessageConverter
实现类从请求体中获取方法入参或者将返回结果设置到响应体中,关于HttpMessageConverter
相关内容在下一篇文档《HandlerAdapter 组件(五)之 HttpMessageConverter》中分析 - 在处理返回结果时,会将
ModelAndViewContainer
的requestHandled
属性设置为true
,表示请求已经处理完成了,后续获取ModelAndView
对象时直接返回null
,不会进行视图渲染,也就和前端分离了~
- 核心逻辑不复杂,主要是通过
ViewNameMethodReturnValueHandler
:处理返回结果是视图名的 HandlerMethodReturnValueHandler实现类- 如果你的方法返回值时
void
或者字符串
,该类都可以处理,将你的返回结果直接设置为视图名 - 这里不会将
ModelAndViewContainer
的requestHandled
属性设置为true
,因为后续需要获取ModelAndView
对象进行视图渲染
- 如果你的方法返回值时
你是否会疑惑?如果我返回的是字符串不是视图名,被
ViewNameMethodReturnValueHandler
处理了怎么办?放心,在
HandlerMethodReturnValueHandlerComposite
中判断是否支持处理该返回结果中,会遍历所有的 HandlerMethodReturnValueHandler 实现类,而RequestResponseBodyMethodProcessor
排在ViewNameMethodReturnValueHandler
前面,所以优先交给前者处理。至于为什么
RequestResponseBodyMethodProcessor
排在前面在本文中已经讲过了,因为所有的 HandlerMethodReturnValueHandler 实现类用 ArrayList 集合保存,RequestResponseBodyMethodProcessor
默认先添加进去😈
参考文章:芋道源码《精尽 Spring MVC 源码分析》