杭州做兼职网站建设,宝安做棋牌网站建设哪家好,oa办公系统企业,如何上国外购物网站HandlerAdapter 组件HandlerAdapter 组件#xff0c;处理器的适配器。因为处理器 handler 的类型是 Object 类型#xff0c;需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变#xff0c;比如用户的处理器可以实现 Controller 接口或者 HttpReques…HandlerAdapter 组件HandlerAdapter 组件处理器的适配器。因为处理器 handler 的类型是 Object 类型需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变比如用户的处理器可以实现 Controller 接口或者 HttpRequestHandler 接口也可以用 RequestMapping 注解将方法作为一个处理器等这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器由它去执行处理器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;
}类图因为返回结果类型是多变的所以会有许多的 HandlerMethodReturnValueHandler 的实现类本文会分析的其中两个实现类ModelAndViewContainerorg.springframework.web.method.support.ModelAndViewContainer主要是作为 Model 和 View 的容器构造方法public class ModelAndViewContainer {/*** 是否在 redirect 重定向时忽略 {link #redirectModel}*/private boolean ignoreDefaultModelOnRedirect false;/*** 视图Object 类型。** 实际情况下也可以是 String 类型的逻辑视图*/Nullableprivate Object view;/*** 默认使用的 Model 。实际上是个 Map*/private final ModelMap defaultModel new BindingAwareModelMap();/*** redirect 重定向的 Model 在重定向时使用。*/Nullableprivate ModelMap redirectModel;/*** 处理器返回 redirect 视图的标识*/private boolean redirectModelScenario false;/*** Http 响应状态*/Nullableprivate HttpStatus status;private final SetString noBinding new HashSet(4);private final SetString bindingDisabled new HashSet(4);/*** 用于设置 SessionAttribute 的标识*/private final SessionStatus sessionStatus new SimpleSessionStatus();/*** 请求是否处理完的标识*/private boolean requestHandled false;
}getModelgetModel() 方法获得 Model 对象。代码如下public ModelMap getModel() {// 是否使用默认 Modelif (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) {MapString, ? flashAttributes ((RedirectAttributes) model).getFlashAttributes();HttpServletRequest request webRequest.getNativeRequest(HttpServletRequest.class);if (request ! null) {RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}}return mav;
}看到没如果已处理则返回的 ModelAndView 对象为 nullHandlerMethodReturnValueHandlerCompositeorg.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite实现 HandlerMethodReturnValueHandler 接口复合的 HandlerMethodReturnValueHandler 实现类构造方法public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {/** HandlerMethodReturnValueHandler 数组 */private final ListHandlerMethodReturnValueHandler returnValueHandlers new ArrayList();
}在《HandlerAdapter 组件一之 HandlerAdapter》的RequestMappingHandlerAdapter小节的 getDefaultReturnValueHandlers 方法中可以看到默认的 returnValueHandlers 有哪些 HandlerMethodReturnValueHandler 实现类注意这里是有顺序的添加哦getReturnValueHandlergetReturnValueHandler(MethodParameter returnType) 方法获得方法返回值对应的 HandlerMethodReturnValueHandler 对象方法如下Nullable
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {if (handler.supportsReturnType(returnType)) {return handler;}}return null;
}很简单遍历所有的 HandlerMethodReturnValueHandler 实现类如果支持这个返回结果则直接返回这里为什么不加缓存呢supportsReturnTypesupportsReturnType(MethodParameter returnType)方法判断是否支持该返回类型方法如下Override
public boolean supportsReturnType(MethodParameter returnType) {return getReturnValueHandler(returnType) ! null;
}实际上就是调用 getReturnValueHandler(MethodParameter returnType) 方法存在对应的 HandlerMethodReturnValueHandler 实现类表示支持handleReturnValuehandleReturnValue(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 的判断【重点】RequestResponseBodyMethodProcessororg.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 ListUser walk(RequestBody User user) {ListUser 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(ListHttpMessageConverter? converters) {super(converters);}public RequestResponseBodyMethodProcessor(ListHttpMessageConverter? converters,Nullable ContentNegotiationManager manager) {super(converters, manager);}public RequestResponseBodyMethodProcessor(ListHttpMessageConverter? converters,Nullable ListObject requestResponseBodyAdvice) {super(converters, null, requestResponseBodyAdvice);}public RequestResponseBodyMethodProcessor(ListHttpMessageConverter? converters,Nullable ContentNegotiationManager manager, Nullable ListObject 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 参数使用示例可以参考 SpringMVC 中 ControllerAdvice 注解的三种使用场景supportsParameter实现 supportsParameter(MethodParameter returnType) 方法判断是否支持处理该方法参数方法如下Override
public boolean supportsParameter(MethodParameter parameter) {// 该参数是否有 RequestBody 注解return parameter.hasParameterAnnotation(RequestBody.class);
}该方法参数是否有 RequestBody 注解resolveArgumentresolveArgument(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-TypecontentType inputMessage.getHeaders().getContentType();}catch (InvalidMediaTypeException ex) {throw new HttpMediaTypeNotSupportedException(ex.getMessage());}if (contentType null) {noContentType true;// 1.2 为空则默认为 application/octet-streamcontentType MediaType.APPLICATION_OCTET_STREAM;}// 2 获取方法参数的 containing class 和 目标类型用于 HttpMessageConverter 解析Class? contextClass parameter.getContainingClass();ClassT targetClass (targetType instanceof Class ? (ClassT) targetType : null);if (targetClass null) {// 如果为空则从方法参数中解析出来ResolvableType resolvableType ResolvableType.forMethodParameter(parameter);targetClass (ClassT) resolvableType.resolve();}// 3 获取 HTTP 方法HttpMethod httpMethod (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);Object body NO_VALUE;// 4 开始从请求中解析方法入参EmptyBodyCheckingHttpInputMessage message;try {// 4.1 将请求消息对象封装成 EmptyBodyCheckingHttpInputMessage用于校验是否有请求体没有的话设置为 nullmessage new EmptyBodyCheckingHttpInputMessage(inputMessage);// 4.2 遍历 HttpMessageConverterfor (HttpMessageConverter? converter : this.messageConverters) {ClassHttpMessageConverter? converterType (ClassHttpMessageConverter?) 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);// 通过该 HttpMessageConverterChain 从请求体中解析出方法入参对象body (genericConverter ! null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverterT) converter).read(targetClass, msgToUse));// 调用 RequestResponseBodyAdviceChain 的 afterBodyRead 方法存在 RequestBodyAdvice 则对方法入参进行修改body getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);}// 4.2.2 如果请求体为空则无需解析请求体else {// 调用 RequestResponseBodyAdvice 的 handleEmptyBody 方法存在 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 实现类所以在下面这个方法就需要用到【核心】writeWithMessageConverterswriteWithMessageConverters(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、targetTypeObject 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-RangesoutputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, bytes);// 数据不为空、请求头中的 Range 不为空、响应码为 200if (value ! null inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) ! null outputMessage.getServletResponse().getStatus() 200) {Resource resource (Resource) value;try {ListHttpRange httpRanges inputMessage.getHeaders().getRange();// 断点续传客户端已下载一部分数据此时需要设置响应码为 206outputMessage.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 选择使用的 MediaTypeMediaType selectedMediaType null;// 3.1 获得响应中的 ContentType 的值MediaType contentType outputMessage.getHeaders().getContentType();// 3.1.1 如果存在 ContentType 的值并且不包含通配符则使用它作为 selectedMediaTypeif (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 中获取ListMediaType acceptableTypes getAcceptableMediaTypes(request);// 3.2.2 获得可产生的 MediaType 数组ListMediaType 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 结果数组中ListMediaType 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;q0.8for (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;q0.8 移除后为 application/jsonselectedMediaType 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; // 这个变量的用途是打印是匿名类需要有 finalLogFormatUtils.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断点续传客户端已下载一部分数据这里不做过多的讲述 第一步 3、选择使用的 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;q0.8 第二步 4、如果匹配到 MediaType 对象 selectedMediaType 不为空则进行写入逻辑移除 quality 。例如application/json;q0.8 移除后为 application/json遍历 messageConverters 数组也就是所有的 HttpMessageConverter 实现类判断当前 HttpMessageConverter 是否支持转换目标类型调用其 canWrite(Nullable Type type, Class? clazz, Nullable MediaType mediaType) 方法进行判断 第三步写入响应体5、如果 4.c 的结果为 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 ((ResponseBodyAdviceT) advice).beforeBodyWrite((T) body, returnType,contentType, converterType, request, response);}}return body;
}
就是你添加了ControllerAdvice注解的 ResponseBodyAdvice 实现类在这里会被调用 2、body 非空则进行写入如果没有 Content-Disposition 请求头则尝试添加一个关于文件 相关的内容 3、调用当前 HttpMessageConverter 实现类的 write(T t, Nullable Type type, Nullable MediaType contentType, HttpOutputMessage outputMessage) 方法 将 body 写入响应中 4、return 返回结束整个逻辑6、到达了此处说明第 4步 没有找到对应的 MediaType 对象或者第5步没有一个 HttpMessageConverter 实现类支持处理该返回结果如果 body 不为空也就是说有返回值但是没有处理则抛出 HttpMediaTypeNotAcceptableException 异常虽然上面的方法很长但是不难理解大致逻辑就是找到合适的 HttpMessageConverter 实现类去将返回结果写入到响应体中但是 HttpMessageConverter 怎么才合适怎么写入到响应体中没有展开讨论涉及到的内容不少就在下一篇文档中分析吧ViewNameMethodReturnValueHandlerorg.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler实现 HandlerMethodReturnValueHandler 接口处理返回结果是视图名的 ReturnValueHandler 实现类。ViewNameMethodReturnValueHandler 适用于前后端未分离Controller 返回视图名的场景例如 JSP、Freemarker 等等。构造方法public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {/*** 重定向的表达式的数组*/Nullableprivate 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 ListHandlerMethodReturnValueHandler getDefaultReturnValueHandlers() {ListHandlerMethodReturnValueHandler handlers new ArrayList();// ... 省略其他 HandlerMethodReturnValueHandler 实现类的添加handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),this.contentNegotiationManager, this.requestResponseBodyAdvice));// Multi-purpose return value typeshandlers.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 happenthrow 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(MapString, ? 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 实现类从请求体中获取方法入参或者将返回结果设置到响应体中。在处理返回结果时会将 ModelAndViewContainer 的 requestHandled 属性设置为 true表示请求已经处理完成了后续获取 ModelAndView 对象时直接返回 null不会进行视图渲染也就和前端分离了~ViewNameMethodReturnValueHandler处理返回结果是视图名的 HandlerMethodReturnValueHandler实现类如果你的方法返回值时void或者字符串该类都可以处理将你的返回结果直接设置为视图名这里不会将ModelAndViewContainer 的 requestHandled 属性设置为 true因为后续需要获取 ModelAndView 对象进行视图渲染你是否会疑惑如果我返回的是字符串不是视图名被ViewNameMethodReturnValueHandler处理了怎么办放心在 HandlerMethodReturnValueHandlerComposite 中判断是否支持处理该返回结果中会遍历所有的 HandlerMethodReturnValueHandler 实现类而 RequestResponseBodyMethodProcessor 排在ViewNameMethodReturnValueHandler 前面所以优先交给前者处理。至于为什么 RequestResponseBodyMethodProcessor 排在前面在本文中已经讲过了因为所有的 HandlerMethodReturnValueHandler 实现类用 ArrayList 集合保存RequestResponseBodyMethodProcessor 默认先添加进去