精盡Spring MVC源碼分析 – HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler
- 2020 年 12 月 21 日
- 筆記
- Spring MVC, 源碼解析, 精盡Spring MVC源碼分析
該系列文檔是本人在學習 Spring MVC 的源碼過程中總結下來的,可能對讀者不太友好,請結合我的源碼注釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀
Spring 版本:5.2.4.RELEASE
該系列其他文檔請查看:《精盡 Spring MVC 源碼分析 – 文章導讀》
HandlerAdapter 組件
HandlerAdapter 組件,處理器的適配器。因為處理器 handler
的類型是 Object 類型,需要有一個調用者來實現 handler
是怎麼被執行。Spring 中的處理器的實現多變,比如用戶的處理器可以實現 Controller 介面或者 HttpRequestHandler 介面,也可以用 @RequestMapping
註解將方法作為一個處理器等,這就導致 Spring MVC 無法直接執行這個處理器。所以這裡需要一個處理器適配器,由它去執行處理器
由於 HandlerMapping 組件涉及到的內容較多,考慮到內容的排版,所以將這部分內容拆分成了五個模組,依次進行分析:
- 《HandlerAdapter 組件(一)之 HandlerAdapter》
- 《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》
- 《HandlerAdapter 組件(三)之 HandlerMethodArgumentResolver》
- 《HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler》
- 《HandlerAdapter 組件(五)之 HttpMessageConverter》
HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler
本文是接著《HandlerAdapter 組件(三)之 HandlerMethodArgumentResolver》一文來分享 HandlerMethodReturnValueHandler 組件。在 HandlerAdapter
執行處理器的過程中,具體的執行過程交由 ServletInvocableHandlerMethod
對象來完成,其中需要先通過 HandlerMethodArgumentResolver
參數解析器從請求中解析出方法的入參,然後再通過反射機制調用對應的方法,獲取到執行結果後需要通過 HandlerMethodReturnValueHandler 結果處理器來進行處理。
回顧
先來回顧一下 ServletInvocableHandlerMethod
在哪裡調用返回值處理器的,可以回到 《HandlerAdapter 組件(二)之 ServletInvocableHandlerMethod》 中 ServletInvocableHandlerMethod 小節下面的 invokeAndHandle
方法,如下:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// <1> 執行調用
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// <2> 設置響應狀態碼
setResponseStatus(webRequest);
// <3> 設置 ModelAndViewContainer 為請求已處理,返回,和 @ResponseStatus 註解相關
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
// <4> 設置 ModelAndViewContainer 為請求未處理
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// <5> 處理返回值
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
<5>
處調用returnValueHandlers
對返回結果進行處理returnValueHandlers
為 HandlerMethodReturnValueHandlerComposite 組合對象,包含了許多的結果處理器
HandlerMethodReturnValueHandler 介面
org.springframework.web.method.support.HandlerMethodReturnValueHandler
,返回結果處理器
public interface HandlerMethodReturnValueHandler {
/**
* 是否支援該類型
*/
boolean supportsReturnType(MethodParameter returnType);
/**
* 處理返回值
*/
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
類圖
因為返回結果類型是多變的,所以會有許多的 HandlerMethodReturnValueHandler 的實現類,上圖僅列出了本文會分析的兩個實現類
ModelAndViewContainer
org.springframework.web.method.support.ModelAndViewContainer
,主要是作為 Model 和 View 的容器
構造方法
public class ModelAndViewContainer {
/**
* 是否在 redirect 重定向時,忽略 {@link #redirectModel}
*/
private boolean ignoreDefaultModelOnRedirect = false;
/**
* 視圖,Object 類型。
*
* 實際情況下,也可以是 String 類型的邏輯視圖
*/
@Nullable
private Object view;
/**
* 默認使用的 Model 。實際上是個 Map
*/
private final ModelMap defaultModel = new BindingAwareModelMap();
/**
* redirect 重定向的 Model ,在重定向時使用。
*/
@Nullable
private ModelMap redirectModel;
/**
* 處理器返回 redirect 視圖的標識
*/
private boolean redirectModelScenario = false;
/**
* Http 響應狀態
*/
@Nullable
private HttpStatus status;
private final Set<String> noBinding = new HashSet<>(4);
private final Set<String> bindingDisabled = new HashSet<>(4);
/**
* 用於設置 SessionAttribute 的標識
*/
private final SessionStatus sessionStatus = new SimpleSessionStatus();
/**
* 請求是否處理完的標識
*/
private boolean requestHandled = false;
}
getModel
getModel()
方法,獲得 Model 對象。程式碼如下:
public ModelMap getModel() {
// 是否使用默認 Model
if (useDefaultModel()) {
return this.defaultModel;
}
else {
if (this.redirectModel == null) {
this.redirectModel = new ModelMap();
}
return this.redirectModel;
}
}
/**
* Whether to use the default model or the redirect model.
*/
private boolean useDefaultModel() {
return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
}
-
從程式碼中,可以看出,有兩種情況下,使用
defaultModel
默認 Model :- 情況一
!this.redirectModelScenario
,處理器返回 redirect 視圖的標識為false
的時候,即不重定向 - 情況二
this.redirectModel == null && !this.ignoreDefaultModelOnRedirect
,redirectModel
重定向 Model 為空,並且ignoreDefaultModelOnRedirect
為true
,即忽略defaultModel
- 情況一
-
那麼,問題就來了,redirectModelScenario 和 ignoreDefaultModelOnRedirect 什麼時候被改變?
-
redirectModelScenario
屬性,在下文的 ViewNameMethodReturnValueHandler的handleReturnValue方法中會設置為true
,詳情見下文 -
ignoreDefaultModelOnRedirect
屬性,和 RequestMappingHandlerAdapter 的ignoreDefaultModelOnRedirect
的屬性是一致的,默認為false
在 RequestMappingHandlerAdapter 的
invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod)
方法中,進行設置
-
-
另外,
org.springframework.ui.ModelMap
是繼承 LinkedHashMap 類,並增加了部分常用方法,比較簡單
View 相關的方法
public void setViewName(@Nullable String viewName) {
this.view = viewName;
}
@Nullable
public String getViewName() {
return (this.view instanceof String ? (String) this.view : null);
}
public void setView(@Nullable Object view) {
this.view = view;
}
@Nullable
public Object getView() {
return this.view;
}
public boolean isViewReference() {
return (this.view instanceof String);
}
requestHandled 屬性
請求是否處理完的標識
關於 requestHandled
的修改地方,實際在 Spring MVC 地方蠻多處都可以進行修改,例如:
-
在本文的開始處,
ServletInvocableHandlerMethod
對象的invokeAndHandle
方法中,會先設置為false
,表示請求還未處理,再交由 HandlerMethodReturnValueHandler 結果處理器去處理 -
在後文的
RequestResponseBodyMethodProcessor
的handleReturnValue
會設置為true
處理完結果後,接下來 RequestMappingHandlerAdapter
需要通過 ModelAndViewContainer
獲取 ModelAndView
對象,會用到 requestHandled
這個屬性
// RequestMappingHandlerAdapter.java
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
// 情況一,如果 mavContainer 已處理,則返回「空」的 ModelAndView 對象。
if (mavContainer.isRequestHandled()) {
return null;
}
// 情況二,如果 mavContainer 未處理,則基於 `mavContainer` 生成 ModelAndView 對象
ModelMap model = mavContainer.getModel();
// 創建 ModelAndView 對象,並設置相關屬性
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
看到沒,如果已處理,則返回的 ModelAndView
對象為 null
HandlerMethodReturnValueHandlerComposite
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
,實現 HandlerMethodReturnValueHandler 介面,複合的 HandlerMethodReturnValueHandler 實現類
構造方法
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
/** HandlerMethodReturnValueHandler 數組 */
private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
}
在《HandlerAdapter 組件(一)之 HandlerAdapter》的RequestMappingHandlerAdapter小節的 getDefaultReturnValueHandlers
方法中可以看到,默認的 returnValueHandlers
有哪些 HandlerMethodReturnValueHandler 實現類,注意這裡是有順序的添加哦
getReturnValueHandler
getReturnValueHandler(MethodParameter returnType)
方法,獲得方法返回值對應的 HandlerMethodReturnValueHandler 對象,方法如下:
@Nullable
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
很簡單,遍歷所有的 HandlerMethodReturnValueHandler 實現類,如果支援這個返回結果,則直接返回
這裡為什麼不加快取呢?
supportsReturnType
supportsReturnType(MethodParameter returnType)
方法,判斷是否支援該返回類型,方法如下:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return getReturnValueHandler(returnType) != null;
}
實際上就是調用 getReturnValueHandler(MethodParameter returnType)
方法,存在對應的 HandlerMethodReturnValueHandler 實現類表示支援
handleReturnValue
handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法,處理返回值,方法如下:
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// <x> 獲得 HandlerMethodReturnValueHandler 對象
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
這裡好奇的是沒有調用 getReturnValueHandler(MethodParameter returnType)
方法獲取對應的 HandlerMethodReturnValueHandler 對象,而是調用 selectHandler(Object value, MethodParameter returnType)
方法,方法如下:
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
// 判斷是否為非同步返回值
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
// 遍歷 HandlerMethodReturnValueHandler 數組,逐個判斷是否支援
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
// 如果支援,則返回
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
private boolean isAsyncReturnValue(@Nullable Object value, MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler instanceof AsyncHandlerMethodReturnValueHandler &&
((AsyncHandlerMethodReturnValueHandler) handler).isAsyncReturnValue(value, returnType)) {
return true;
}
}
return false;
}
在 getReturnValueHandler(MethodParameter returnType)
的基礎上,增加了非同步處理器 AsyncHandlerMethodReturnValueHandler 的判斷
【重點】RequestResponseBodyMethodProcessor
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
,繼承 AbstractMessageConverterMethodProcessor 抽象類,處理方法參數添加了 @RequestBody
註解方法入參,或者處理方法添加了 @ResponseBody
註解的返回值。
因為前後端分離之後,後端基本是提供 Restful API ,所以 RequestResponseBodyMethodProcessor 成為了目前最常用的 HandlerMethodReturnValueHandler 實現類。
從圖中,我們也會發現,RequestResponseBodyMethodProcessor 也是 HandlerMethodArgumentResolver 的實現類。示例程式碼:
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/walks")
public List<User> walk(@RequestBody User user) {
List<User> users = new ArrayList();
users.add(new User().setUsername("nihao"));
users.add(new User().setUsername("zaijian"));
return users;
}
}
雖然,walks()
方法的返回值沒添加 @ResponseBody
註解,但是 @RestController
註解,默認有 @ResponseBody
註解
構造方法
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
super(converters);
}
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable ContentNegotiationManager manager) {
super(converters, manager);
}
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable List<Object> requestResponseBodyAdvice) {
super(converters, null, requestResponseBodyAdvice);
}
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
super(converters, manager, requestResponseBodyAdvice);
}
}
-
converters
參數,HttpMessageConverter 數組。關於 HttpMessageConverter,就是將返回結果設置到響應中,供客戶端獲取。例如,我們想要將 POJO 對象,返回成 JSON 數據給前端,就會使用到 MappingJackson2HttpMessageConverter 類。 -
requestResponseBodyAdvice
參數,在父類 AbstractMessageConverterMethodArgumentResolver 中會將其轉換成 RequestResponseBodyAdviceChain 對象advice
,不知你是否還記得這個參數,來回顧一下:在《HandlerAdapter 組件(一)之 HandlerAdapter》的RequestMappingHandlerAdapter的1.afterPropertiesSet 初始化方法中,第一步就會初始化所有 ControllerAdvice 相關的類
然後在1.4 getDefaultReturnValueHandlers方法中,創建 RequestResponseBodyMethodProcessor 處理器時,會傳入
requestResponseBodyAdvice
參數
supportsParameter
實現 supportsParameter(MethodParameter returnType)
方法,判斷是否支援處理該方法參數,方法如下:
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 該參數是否有 @RequestBody 註解
return parameter.hasParameterAnnotation(RequestBody.class);
}
該方法參數是否有 @RequestBody
註解
resolveArgument
實現 resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)
方法,從請求中解析出帶有 @RequestBody
註解的參數,方法如下:
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 從請求體中解析出方法入參對象
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
// 數據綁定相關
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
// 返回方法入參對象,如果有必要,則通過 Optional 獲取對應的方法入參
return adaptArgumentIfNecessary(arg, parameter);
}
調用readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType)
方法,從請求體中解析出方法入參對象
【核心】readWithMessageConverters
從請求體中解析出方法入參,方法如下:
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// <1> 創建 ServletServerHttpRequest 請求對象
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
// <2> 讀取請求體中的消息並轉換成入參對象
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
// <3> 校驗方法入參對象
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
// AbstractMessageConverterMethodArgumentResolver.java
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// <1> 獲取使用的 MediaType 對象
MediaType contentType;
boolean noContentType = false;
try {
// <1.1> 從請求頭中獲取 "Content-Type"
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
// <1.2> 為空則默認為 application/octet-stream
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
// <2> 獲取方法參數的 containing class 和 目標類型,用於 HttpMessageConverter 解析
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
// 如果為空,則從方法參數中解析出來
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
// <3> 獲取 HTTP 方法
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
// <4> 開始從請求中解析方法入參
EmptyBodyCheckingHttpInputMessage message;
try {
// <4.1> 將請求消息對象封裝成 EmptyBodyCheckingHttpInputMessage,用於校驗是否有請求體,沒有的話設置為 `null`
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
// <4.2> 遍歷 HttpMessageConverter
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
// 如果該 HttpMessageConverter 能夠讀取當前請求體解析出方法入參
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
// <4.2.1> 如果請求體不為空
if (message.hasBody()) {
HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
// 通過該 HttpMessageConverter 從請求體中解析出方法入參對象
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
// 調用 RequestResponseBodyAdvice 的 afterBodyRead 方法,存在 RequestBodyAdvice 則對方法入參進行修改
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
// <4.2.2> 如果請求體為空,則無需解析請求體
else {
// 調用 RequestResponseBodyAdvice 的 afterBodyRead 方法,存在 RequestBodyAdvice 則對方法入參進行修改
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
// <5> 校驗解析出來的方法入參對象是否為空
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
// 列印日誌
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
});
// <6> 返回方法入參對象
return body;
}
我們直接看到父類 AbstractMessageConverterMethodArgumentResolver 的 readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType)
這個核心方法,大致邏輯如下:
-
獲取使用的 MediaType 對象
contentType
- 從請求頭中獲取
Content-Type
- 請求頭中沒有則設置為默認的類型
application/octet-stream
- 從請求頭中獲取
-
獲取方法參數的
containing class
和targetClass 目標類型
,用於 HttpMessageConverter 解析 -
獲取 HTTP 方法
-
開始從請求中解析方法入參
Object body
-
將請求消息對象封裝成 EmptyBodyCheckingHttpInputMessage,用於校驗是否有請求體,沒有的話設置為
null
-
遍歷所有的 HttpMessageConverter 實現類,調用其
canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType)
方法,判斷當前 HttpMessageConverter 實現類是否支援解析該方法入參,如果返回true
,則使用該 HttpMessageConverter 實現類進行解析- 如果請求體不為空,則通過該 HttpMessageConverter 從請求體中解析出方法入參對象
- 如果請求體為空,則無需解析請求體
注意:上面不管請求體是否為空,都會調用
RequestResponseBodyAdvice
的afterBodyRead
方法,存在 RequestBodyAdvice 則對方法入參進行修改
-
-
校驗解析出來的方法入參對象是否為空,拋出異常或者返回
null
-
返回方法入參對象
body
方法雖然很長,但是不難理解,大致邏輯就是找到合適的 HttpMessageConverter 實現類從請求體中獲取到方法入參對象
邏輯和下面的 writeWithMessageConverters
差不多,我們重點來看到下面這個方法😈
supportsReturnType
實現 supportsReturnType(MethodParameter returnType)
方法,判斷是否支援處理該返回類型,方法如下:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
// 該方法或者所在類是否有 @ResponseBody 註解
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
該方法或者所在類是否有 @ResponseBody
註解
handleReturnValue
實現 handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法,方法如下:
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// <1> 設置已處理
mavContainer.setRequestHandled(true);
// <2> 創建請求和響應
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// <3> 使用 HttpMessageConverter 對對象進行轉換,並寫入到響應
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
-
設置
mavContainer
已處理,也就是修改它的requestHandled
屬性為true
,表示請求已處理,後續獲取到的ModelAndView
對象就為null
-
創建請求和響應,這裡是獲取到
javax.servlet.http.HttpServletRequest
和javax.servlet.http.HttpServletResponse
,然後分別封裝成org.springframework.http.server.ServletServerHttpRequest
和org.springframework.http.server.ServletServerHttpResponse
對象,便於從請求中獲取數據,往響應中設置數據 -
調用父類 AbstractMessageConverterMethodProcessor 的
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage)
方法,使用 HttpMessageConverter 對對象進行轉換,並寫入到響應
不知你是否還記得 HttpMessageConverter 是在哪兒會初始化呢?我們來回顧一下
回到《HandlerAdapter 組件(一)之 HandlerAdapter》的RequestMappingHandlerAdapter的構造方法中,默認會添加了四個 HttpMessageConverter 對象。當然,默認還會添加其他的,例如 MappingJackson2HttpMessageConverter 為 JSON 消息格式的轉換器,至於其他 HttpMessageConverter 實現類如何添加的,本文就不分析了,你知道就行😈
然後在 1.4 getDefaultReturnValueHandlers 方法中,創建 RequestResponseBodyMethodProcessor 處理器時,會傳入
getMessageConverters()
參數,也就是獲取所有的 HttpMessageConverter 實現類,所以在下面這個方法就需要用到
【核心】writeWithMessageConverters
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage)
方法,使用 HttpMessageConverter 對象進行轉換,並寫入到響應,方法如下:
// AbstractMessageConverterMethodProcessor.java
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// <1> 獲得 body、valueType、targetType
Object body;
Class<?> valueType;
Type targetType;
if (value instanceof CharSequence) { // 如果是字元串則直接賦值
body = value.toString();
valueType = String.class;
targetType = String.class;
}
else {
body = value;
// 獲取返回結果的類型(返回值 body 不為空則直接獲取其類型,否則從返回結果類型 returnType 獲取其返回值類型)
valueType = getReturnValueType(body, returnType);
// 獲取泛型
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
// <2> 是否為 Resource 類型
if (isResourceType(value, returnType)) {
// 設置響應頭 Accept-Ranges
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
// 數據不為空、請求頭中的 Range 不為空、響應碼為 200
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null
&& outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
// 斷點續傳,客戶端已下載一部分數據,此時需要設置響應碼為 206
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
// 獲取哪一段數據需返回
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
// <3> 選擇使用的 MediaType
MediaType selectedMediaType = null;
// <3.1> 獲得響應中的 ContentType 的值
MediaType contentType = outputMessage.getHeaders().getContentType();
// <3.1.1> 如果存在 ContentType 的值,並且不包含通配符,則使用它作為 selectedMediaType
if (contentType != null && contentType.isConcrete()) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
}
else {
HttpServletRequest request = inputMessage.getServletRequest();
// <3.2.1> 從請求中,獲得可接受的 MediaType 數組。默認實現是,從請求頭 ACCEPT 中獲取
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// <3.2.2> 獲得可產生的 MediaType 數組
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
// <3.2.3> 如果 body 非空,並且無可產生的 MediaType 數組,則拋出 HttpMediaTypeNotAcceptableException 異常
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
// <3.2.4> 通過 acceptableTypes 來比對,將符合的 producibleType 添加到 mediaTypesToUse 結果數組中
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
// <3.2.5> 如果沒有符合的,並且 body 非空,則拋出 HttpMediaTypeNotAcceptableException 異常
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
// <3.2.6> 按照 MediaType 的 specificity 和 quality 排序
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
// <3.2.7> 選擇其中一個最匹配的,主要考慮不包含通配符的,例如 application/json;q=0.8
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using '" + selectedMediaType + "', given " +
acceptableTypes + " and supported " + producibleTypes);
}
}
// <4> 如果匹配到,則進行寫入邏輯
if (selectedMediaType != null) {
// <4.1> 移除 quality 。例如,application/json;q=0.8 移除後為 application/json
selectedMediaType = selectedMediaType.removeQualityValue();
// <4.2> 遍歷 messageConverters 數組
for (HttpMessageConverter<?> converter : this.messageConverters) {
// <4.3> 判斷 HttpMessageConverter 是否支援轉換目標類型
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter
? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType)
: converter.canWrite(valueType, selectedMediaType)) {
// <5.1> 如果有 RequestResponseBodyAdvice,則可能需要對返回的結果做修改
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
// <5.2> body 非空,則進行寫入
if (body != null) {
// 列印日誌
Object theBody = body; // 這個變數的用途是,列印是匿名類,需要有 final
LogFormatUtils.traceDebug(logger, traceOn -> "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
// 添加 CONTENT_DISPOSITION 頭,一般情況下用不到
addContentDispositionHeader(inputMessage, outputMessage);
// <5.3> 寫入內容
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
// <5.4> return 返回,結束整個邏輯
return;
}
}
}
// <6> 如果到達此處,並且 body 非空,說明沒有匹配的 HttpMessageConverter 轉換器,則拋出 HttpMediaTypeNotAcceptableException 異常
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
方法有點長,慢慢來看,核心邏輯簡單😈
<1>
處,獲得 body
、valueType
、targetType
三個屬性,例如上面提供的示例,三個值分對應users返回結果
、ArrayList 類型
、User 類型
<2>
處,調用 isResourceType(Object value, MethodParameter returnType)
方法,判斷是否為 Resource 類型,方法如下:
// AbstractMessageConverterMethodProcessor.java
protected boolean isResourceType(@Nullable Object value, MethodParameter returnType) {
Class<?> clazz = getReturnValueType(value, returnType);
return clazz != InputStreamResource.class && Resource.class.isAssignableFrom(clazz);
}
設置響應頭 Accept-Ranges 為 “bytes”,如果數據不為空,且請求頭中的 Range 不為空,且響應碼為 200,則設置狀態碼為 206(斷點續傳,客戶端已下載一部分數據),這裡不做過多的講述
========== 第一步 ==========
- 選擇使用的 MediaType 對象
selectedMediaType
- 獲得響應中的 ContentType 的值,如果存在 ContentType 的值,並且不包含通配符,則使用它作為
selectedMediaType
- 否則,從請求中找到合適的 MediaType 對象
- 從請求中,獲得可接受的 MediaType 數組
acceptableTypes
。默認實現是,從請求頭 ACCEPT 中獲取 - 獲得可產生的 MediaType 數組
producibleTypes
- 如果
body
非空,並且無可產生的 MediaType 數組producibleTypes
,則拋出 HttpMediaTypeNotAcceptableException 異常 - 通過
acceptableTypes
來比對,將符合的producibleType
添加到mediaTypesToUse
結果數組中 - 如果沒有符合的,並且
body
非空,則拋出 HttpMediaTypeNotAcceptableException 異常 - 按照 MediaType 的 specificity 和 quality 排序(權重),對
mediaTypesToUse
進行排序 - 選擇其中一個最匹配的,主要考慮不包含通配符的,例如
application/json;q=0.8
- 從請求中,獲得可接受的 MediaType 數組
- 獲得響應中的 ContentType 的值,如果存在 ContentType 的值,並且不包含通配符,則使用它作為
========== 第二步 ==========
- 如果匹配到 MediaType 對象
selectedMediaType
不為空,則進行寫入邏輯- 移除 quality 。例如,
application/json;q=0.8
移除後為application/json
- 遍歷
messageConverters
數組,也就是所有的 HttpMessageConverter 實現類 - 判斷當前 HttpMessageConverter 是否支援轉換目標類型,調用其
canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType)
方法進行判斷
- 移除 quality 。例如,
========== 第三步:寫入響應體==========
-
如果
4.3
的結果為true
,表示當前 HttpMessageConverter 實現類可以處理該返回類型-
調用
RequestResponseBodyAdvice
的beforeBodyWrite
方法,存在 ResponseBodyAdvice 則對返回的結果進行修改// RequestResponseBodyAdviceChain.java @Override @Nullable public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType, ServerHttpRequest request, ServerHttpResponse response) { return processBody(body, returnType, contentType, converterType, request, response); } @Nullable private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType, ServerHttpRequest request, ServerHttpResponse response) { for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) { if (advice.supports(returnType, converterType)) { body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType, contentType, converterType, request, response); } } return body; }
就是你添加了@ControllerAdvice註解的 ResponseBodyAdvice 實現類在這裡會被調用
-
body
非空,則進行寫入,如果沒有Content-Disposition
請求頭,則嘗試添加一個,關於文件相關的內容 -
調用當前 HttpMessageConverter 實現類的
write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
方法,將body
寫入響應中 -
return
返回,結束整個邏輯
-
-
到達了此處,說明第
4
步 沒有找到對應的 MediaType 對象,或者第5
步沒有一個 HttpMessageConverter 實現類支援處理該返回結果如果
body
不為空,也就是說有返回值但是沒有處理,則拋出 HttpMediaTypeNotAcceptableException 異常
雖然上面的方法很長,但是不難理解,大致邏輯就是找到合適的 HttpMessageConverter 實現類去將返回結果寫入到響應體中😈
但是 HttpMessageConverter 怎麼才合適,怎麼寫入到響應體中,沒有展開討論,涉及到的內容不少,就在下一篇文檔《HandlerAdapter 組件(五)之 HttpMessageConverter》中分析吧
ViewNameMethodReturnValueHandler
org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler
,實現 HandlerMethodReturnValueHandler 介面,處理返回結果是視圖名的 ReturnValueHandler 實現類。
ViewNameMethodReturnValueHandler 適用於前後端未分離,Controller 返回視圖名的場景,例如 JSP、Freemarker 等等。
構造方法
public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
/**
* 重定向的表達式的數組
*/
@Nullable
private String[] redirectPatterns;
protected boolean isRedirectViewName(String viewName) {
// 符合 redirectPatterns 表達式,或者以 redirect: 開頭
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
}
}
redirectPatterns
:重定向的表達式的數組,用於判斷某個視圖是否為重定向的視圖,一般情況下,不進行設置。
可以看到isRedirectViewName(String viewName)
方法,判斷某個視圖是否為重定向的視圖,如果視圖名以 redirect:
開頭,也是重定向的視圖
supportsReturnType
實現 supportsReturnType(MethodParameter returnType)
方法,判斷是否支援處理該返回類型,方法如下:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
該方法的返回類型是否為void
或者字元串
你是否會疑惑?如果我返回的是字元串,想要使用 RequestResponseBodyMethodProcessor 怎麼辦,不會有問題嗎?
回到《HandlerAdapter 組件(一)之 HandlerAdapter》的RequestMappingHandlerAdapter的1.4 getDefaultReturnValueHandlers方法中,如下:
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(); // ... 省略其他 HandlerMethodReturnValueHandler 實現類的添加 handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); // Multi-purpose return value types handlers.add(new ViewNameMethodReturnValueHandler()); // ... 省略其他 HandlerMethodReturnValueHandler 實現類的添加 return handlers; }
RequestResponseBodyMethodProcessor 是在 ViewNameMethodReturnValueHandler 之前添加的,所以不會出現上述問題
handleReturnValue
實現 handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法,程式碼如下:
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 如果是 String 類型
if (returnValue instanceof CharSequence) {
// 設置視圖名到 mavContainer 中
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
// 如果是重定向,則標記到 mavContainer 中
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
// 如果是非 String 類型,而且非 void ,則拋出 UnsupportedOperationException 異常
else if (returnValue != null) {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
- 如果返回結果是
String
類型,則作為視圖名設置到mavContainer
中 - 如果是重定向,則標記到
mavContainer
中的redirectModelScenario
屬性中為true
注意,此時 mavContainer
的 requestHandled
屬性,並未並未像 RequestResponseBodyMethodProcessor 一樣,設置為 true
表示請求已處理
這是為什麼呢?因為返回結果是視圖名的場景下,需要使用 ViewResolver 從 ModelAndView 對象中解析出其對應的視圖 View 對象,然後執行 View#render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
方法,進行渲染。如果你設置為 true
,在後續獲取到的 ModelAndView
對象就為null
了,無法渲染視圖
總結
在 HandlerAdapter
執行 HandlerMethod
處理器的過程中,會將該處理器封裝成 ServletInvocableHandlerMethod
對象,通過該對象來執行處理器。該對象通過反射機制調用對應的方法,在調用方法之前,藉助 HandlerMethodArgumentResolver
參數解析器從請求中獲取到對應的方法參數值,在調用方法之後,需要藉助於HandlerMethodReturnValueHandler 返回值處理器將返回結果設置到響應中,或者設置相應的 Model 和 View 用於後續的視圖渲染。
HandlerMethodReturnValueHandler 返回值處理器的實現類非常多,採用了組合模式來進行處理,如果有某一個返回值處理器支援處理該返回值類型,則使用它對返回結果進行處理,例如將返回結果寫入響應體中。注意,這裡有一定的先後順序,因為是通過 ArrayList 保存所有的實現類,排在前面的實現類則優先處理。
本文分析了我們常用的 @ResponseBody
註解和前後端未分離時返回視圖名兩種處理方式,對應的 HandlerMethodReturnValueHandler 實現類,如下:
RequestResponseBodyMethodProcessor
:處理方法參數添加了@RequestBody
註解方法入參,或者處理方法添加了@ResponseBody
註解的返回值。在前後端分離之後,後端基本是提供 Restful API ,所以這種方式成為了目前最常用的 HandlerMethodReturnValueHandler 實現類- 核心邏輯不複雜,主要是通過
HttpMessageConverter
實現類從請求體中獲取方法入參或者將返回結果設置到響應體中,關於HttpMessageConverter
相關內容在下一篇文檔《HandlerAdapter 組件(五)之 HttpMessageConverter》中分析 - 在處理返回結果時,會將
ModelAndViewContainer
的requestHandled
屬性設置為true
,表示請求已經處理完成了,後續獲取ModelAndView
對象時直接返回null
,不會進行視圖渲染,也就和前端分離了~
- 核心邏輯不複雜,主要是通過
ViewNameMethodReturnValueHandler
:處理返回結果是視圖名的 HandlerMethodReturnValueHandler實現類- 如果你的方法返回值時
void
或者字元串
,該類都可以處理,將你的返回結果直接設置為視圖名 - 這裡不會將
ModelAndViewContainer
的requestHandled
屬性設置為true
,因為後續需要獲取ModelAndView
對象進行視圖渲染
- 如果你的方法返回值時
你是否會疑惑?如果我返回的是字元串不是視圖名,被
ViewNameMethodReturnValueHandler
處理了怎麼辦?放心,在
HandlerMethodReturnValueHandlerComposite
中判斷是否支援處理該返回結果中,會遍歷所有的 HandlerMethodReturnValueHandler 實現類,而RequestResponseBodyMethodProcessor
排在ViewNameMethodReturnValueHandler
前面,所以優先交給前者處理。至於為什麼
RequestResponseBodyMethodProcessor
排在前面在本文中已經講過了,因為所有的 HandlerMethodReturnValueHandler 實現類用 ArrayList 集合保存,RequestResponseBodyMethodProcessor
默認先添加進去😈
參考文章:芋道源碼《精盡 Spring MVC 源碼分析》