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

該系列文檔是本人在學習 Spring MVC 的源碼過程當中總結下來的,可能對讀者不太友好,請結合個人源碼註釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀html

Spring 版本:5.2.4.RELEASE前端

該系列其餘文檔請查看:《精盡 Spring MVC 源碼分析 - 文章導讀》java

HandlerAdapter 組件

HandlerAdapter 組件,處理器的適配器。由於處理器 handler 的類型是 Object 類型,須要有一個調用者來實現 handler 是怎麼被執行。Spring 中的處理器的實現多變,好比用戶的處理器能夠實現 Controller 接口或者 HttpRequestHandler 接口,也能夠用 @RequestMapping 註解將方法做爲一個處理器等,這就致使 Spring MVC 沒法直接執行這個處理器。因此這裏須要一個處理器適配器,由它去執行處理器git

因爲 HandlerMapping 組件涉及到的內容較多,考慮到內容的排版,因此將這部份內容拆分紅了五個模塊,依次進行分析:github

HandlerAdapter 組件(四)之 HandlerMethodReturnValueHandler

本文是接着《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 組合對象,包含了許多的結果處理器

HandlerMethodReturnValueHandler 接口

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 的實現類,上圖僅列出了本文會分析的兩個實現類後端

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 源碼分析》

相關文章
相關標籤/搜索