精尽Spring MVC源码分析 – HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler

该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读

Spring 版本:5.1.14.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;
}

类图

精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler

因为返回结果类型是多变的,所以会有许多的 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 实现类。

精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 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 源码分析》

发表评论

评论已关闭。

相关文章