该系列文档是本人在学习 Spring MVC 的源码过程当中总结下来的,可能对读者不太友好,请结合个人源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读html
Spring 版本:5.2.4.RELEASE前端
该系列其余文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》java
HandlerAdapter 组件,处理器的适配器。由于处理器 handler
的类型是 Object 类型,须要有一个调用者来实现 handler
是怎么被执行。Spring 中的处理器的实现多变,好比用户的处理器能够实现 Controller 接口或者 HttpRequestHandler 接口,也能够用 @RequestMapping
注解将方法做为一个处理器等,这就致使 Spring MVC 没法直接执行这个处理器。因此这里须要一个处理器适配器,由它去执行处理器git
因为 HandlerMapping 组件涉及到的内容较多,考虑到内容的排版,因此将这部份内容拆分红了五个模块,依次进行分析:github
本文是接着《HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver》一文来分享 HandlerMethodReturnValueHandler 组件。在 HandlerAdapter
执行处理器的过程当中,具体的执行过程交由 ServletInvocableHandlerMethod
对象来完成,其中须要先经过 HandlerMethodArgumentResolver
参数解析器从请求中解析出方法的入参,而后再经过反射机制调用对应的方法,获取到执行结果后须要经过 HandlerMethodReturnValueHandler 结果处理器来进行处理。web
先来回顾一下 ServletInvocableHandlerMethod
在哪里调用返回值处理器的,能够回到 《HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》 中 ServletInvocableHandlerMethod 小节下面的 invokeAndHandle
方法,以下:spring
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 组合对象,包含了许多的结果处理器org.springframework.web.method.support.HandlerMethodReturnValueHandler
,返回结果处理器json
public interface HandlerMethodReturnValueHandler { /** * 是否支持该类型 */ boolean supportsReturnType(MethodParameter returnType); /** * 处理返回值 */ void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception; }
由于返回结果类型是多变的,因此会有许多的 HandlerMethodReturnValueHandler 的实现类,上图仅列出了本文会分析的两个实现类后端
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()
方法,得到 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 类,并增长了部分经常使用方法,比较简单
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
的修改地方,实际在 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
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(MethodParameter returnType)
方法,得到方法返回值对应的 HandlerMethodReturnValueHandler 对象,方法以下:
@Nullable private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) { for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) { if (handler.supportsReturnType(returnType)) { return handler; } } return null; }
很简单,遍历全部的 HandlerMethodReturnValueHandler 实现类,若是支持这个返回结果,则直接返回
这里为何不加缓存呢?
supportsReturnType(MethodParameter returnType)
方法,判断是否支持该返回类型,方法以下:
@Override public boolean supportsReturnType(MethodParameter returnType) { return getReturnValueHandler(returnType) != null; }
实际上就是调用 getReturnValueHandler(MethodParameter returnType)
方法,存在对应的 HandlerMethodReturnValueHandler 实现类表示支持
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 的判断
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
,继承 AbstractMessageConverterMethodProcessor 抽象类,处理方法参数添加了 @RequestBody
注解方法入参,或者处理方法添加了 @ResponseBody
注解的返回值。
由于先后端分离以后,后端基本是提供 Restful API ,因此 RequestResponseBodyMethodProcessor 成为了目前最经常使用的 HandlerMethodReturnValueHandler 实现类。
从图中,咱们也会发现,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(MethodParameter returnType)
方法,判断是否支持处理该方法参数,方法以下:
@Override public boolean supportsParameter(MethodParameter parameter) { // 该参数是否有 @RequestBody 注解 return parameter.hasParameterAnnotation(RequestBody.class); }
该方法参数是否有 @RequestBody
注解
实现 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)
方法,从请求体中解析出方法入参对象
从请求体中解析出方法入参,方法以下:
@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 实现类进行解析
注意:上面无论请求体是否为空,都会调用 RequestResponseBodyAdvice
的 afterBodyRead
方法,存在 RequestBodyAdvice 则对方法入参进行修改
校验解析出来的方法入参对象是否为空,抛出异常或者返回null
返回方法入参对象body
方法虽然很长,可是不难理解,大体逻辑就是找到合适的 HttpMessageConverter 实现类从请求体中获取到方法入参对象
逻辑和下面的 writeWithMessageConverters
差很少,咱们重点来看到下面这个方法😈
实现 supportsReturnType(MethodParameter returnType)
方法,判断是否支持处理该返回类型,方法以下:
@Override public boolean supportsReturnType(MethodParameter returnType) { // 该方法或者所在类是否有 @ResponseBody 注解 return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); }
该方法或者所在类是否有 @ResponseBody
注解
实现 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(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(断点续传,客户端已下载一部分数据),这里不作过多的讲述
========== 第一步 ==========
selectedMediaType
selectedMediaType
acceptableTypes
。默认实现是,从请求头 ACCEPT 中获取producibleTypes
body
非空,而且无可产生的 MediaType 数组 producibleTypes
,则抛出 HttpMediaTypeNotAcceptableException 异常acceptableTypes
来比对,将符合的 producibleType
添加到 mediaTypesToUse
结果数组中body
非空,则抛出 HttpMediaTypeNotAcceptableException 异常mediaTypesToUse
进行排序application/json;q=0.8
========== 第二步 ==========
selectedMediaType
不为空,则进行写入逻辑
application/json;q=0.8
移除后为 application/json
messageConverters
数组,也就是全部的 HttpMessageConverter 实现类canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType)
方法进行判断========== 第三步:写入响应体==========
若是 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》中分析吧
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(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(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 源码分析》