精尽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 源码分析》

相关文章
相关标签/搜索