該系列文檔是本人在學習 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 源碼分析》