精盡Spring MVC源碼分析 – HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler

該系列文檔是本人在學習 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 組件(四)之 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.ignoreDefaultModelOnRedirectredirectModel 重定向 Model 為,並且 ignoreDefaultModelOnRedirecttrue ,即忽略 defaultModel
  • 那麼,問題就來了,redirectModelScenario 和 ignoreDefaultModelOnRedirect 什麼時候被改變?

    • redirectModelScenario 屬性,在下文的 ViewNameMethodReturnValueHandlerhandleReturnValue方法中會設置為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 結果處理器去處理

  • 在後文的 RequestResponseBodyMethodProcessorhandleReturnValue 會設置為 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》RequestMappingHandlerAdapter1.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 註解

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)這個核心方法,大致邏輯如下:

  1. 獲取使用的 MediaType 對象 contentType

    1. 從請求頭中獲取 Content-Type
    2. 請求頭中沒有則設置為默認的類型 application/octet-stream
  2. 獲取方法參數的 containing classtargetClass 目標類型,用於 HttpMessageConverter 解析

  3. 獲取 HTTP 方法

  4. 開始從請求中解析方法入參Object body

    1. 將請求消息對象封裝成 EmptyBodyCheckingHttpInputMessage,用於校驗是否有請求體,沒有的話設置為 null

    2. 遍歷所有的 HttpMessageConverter 實現類,調用其 canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType)方法,判斷當前 HttpMessageConverter 實現類是否支援解析該方法入參,如果返回 true,則使用該 HttpMessageConverter 實現類進行解析

      1. 如果請求體不為空,則通過該 HttpMessageConverter 從請求體中解析出方法入參對象
      2. 如果請求體為空,則無需解析請求體

      注意:上面不管請求體是否為空,都會調用 RequestResponseBodyAdviceafterBodyRead 方法,存在 RequestBodyAdvice 則對方法入參進行修改

  5. 校驗解析出來的方法入參對象是否為空,拋出異常或者返回null

  6. 返回方法入參對象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);
}
  1. 設置 mavContainer 已處理,也就是修改它的 requestHandled 屬性為 true,表示請求已處理,後續獲取到的 ModelAndView 對象就為 null

  2. 創建請求和響應,這裡是獲取到 javax.servlet.http.HttpServletRequestjavax.servlet.http.HttpServletResponse,然後分別封裝成 org.springframework.http.server.ServletServerHttpRequestorg.springframework.http.server.ServletServerHttpResponse 對象,便於從請求中獲取數據,往響應中設置數據

  3. 調用父類 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> 處,獲得 bodyvalueTypetargetType 三個屬性,例如上面提供的示例,三個值分對應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(斷點續傳,客戶端已下載一部分數據),這裡不做過多的講述

========== 第一步 ==========

  1. 選擇使用的 MediaType 對象 selectedMediaType
    1. 獲得響應中的 ContentType 的值,如果存在 ContentType 的值,並且不包含通配符,則使用它作為 selectedMediaType
    2. 否則,從請求中找到合適的 MediaType 對象
      1. 從請求中,獲得可接受的 MediaType 數組 acceptableTypes。默認實現是,從請求頭 ACCEPT 中獲取
      2. 獲得可產生的 MediaType 數組 producibleTypes
      3. 如果 body 非空,並且無可產生的 MediaType 數組 producibleTypes,則拋出 HttpMediaTypeNotAcceptableException 異常
      4. 通過 acceptableTypes 來比對,將符合的 producibleType 添加到 mediaTypesToUse 結果數組中
      5. 如果沒有符合的,並且 body 非空,則拋出 HttpMediaTypeNotAcceptableException 異常
      6. 按照 MediaType 的 specificity 和 quality 排序(權重),對mediaTypesToUse 進行排序
      7. 選擇其中一個最匹配的,主要考慮不包含通配符的,例如 application/json;q=0.8

========== 第二步 ==========

  1. 如果匹配到 MediaType 對象 selectedMediaType 不為空,則進行寫入邏輯
    1. 移除 quality 。例如,application/json;q=0.8 移除後為 application/json
    2. 遍歷 messageConverters 數組,也就是所有的 HttpMessageConverter 實現類
    3. 判斷當前 HttpMessageConverter 是否支援轉換目標類型,調用其 canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) 方法進行判斷

========== 第三步:寫入響應體==========

  1. 如果 4.3 的結果為 true,表示當前 HttpMessageConverter 實現類可以處理該返回類型

    1. 調用 RequestResponseBodyAdvicebeforeBodyWrite 方法,存在 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 實現類在這裡會被調用

    2. body 非空,則進行寫入,如果沒有 Content-Disposition 請求頭,則嘗試添加一個,關於文件相關的內容

    3. 調用當前 HttpMessageConverter 實現類的 write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) 方法,將 body 寫入響應中

    4. return 返回,結束整個邏輯

  2. 到達了此處,說明第 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》RequestMappingHandlerAdapter1.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

注意,此時 mavContainerrequestHandled 屬性,並未並未像 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 實現類
    1. 核心邏輯不複雜,主要是通過 HttpMessageConverter 實現類從請求體中獲取方法入參或者將返回結果設置到響應體中,關於 HttpMessageConverter 相關內容在下一篇文檔《HandlerAdapter 組件(五)之 HttpMessageConverter》中分析
    2. 在處理返回結果時,會將 ModelAndViewContainerrequestHandled 屬性設置為 true,表示請求已經處理完成了,後續獲取 ModelAndView 對象時直接返回 null,不會進行視圖渲染,也就和前端分離了~
  • ViewNameMethodReturnValueHandler:處理返回結果是視圖名HandlerMethodReturnValueHandler實現類
    1. 如果你的方法返回值時void或者字元串,該類都可以處理,將你的返回結果直接設置為視圖名
    2. 這裡不會將ModelAndViewContainerrequestHandled 屬性設置為 true,因為後續需要獲取 ModelAndView 對象進行視圖渲染

你是否會疑惑?如果我返回的是字元串不是視圖名,被ViewNameMethodReturnValueHandler處理了怎麼辦?

放心,在 HandlerMethodReturnValueHandlerComposite 中判斷是否支援處理該返回結果中,會遍歷所有的 HandlerMethodReturnValueHandler 實現類,而 RequestResponseBodyMethodProcessor 排在 ViewNameMethodReturnValueHandler 前面,所以優先交給前者處理。

至於為什麼 RequestResponseBodyMethodProcessor 排在前面在本文中已經講過了,因為所有的 HandlerMethodReturnValueHandler 實現類用 ArrayList 集合保存,RequestResponseBodyMethodProcessor 默認先添加進去😈

參考文章:芋道源碼《精盡 Spring MVC 源碼分析》