code-learning/springmvc/17-Spring MVC 源码解析-HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler.md

35 KiB
Raw Blame History

精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler

1. 概述

本文接 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》 一文,我们来分享 HandlerMethodReturnValueHandler HandlerMethod 的返回值的处理器接口。代码如下:

// HandlerMethodReturnValueHandler.java

public interface HandlerMethodReturnValueHandler {

    /**
     * 是否支持该类型
     *
     * Whether the given {@linkplain MethodParameter method return type} is
     * supported by this handler.
     * @param returnType the method return type to check
     * @return {@code true} if this handler supports the supplied return type;
     * {@code false} otherwise
     */
    boolean supportsReturnType(MethodParameter returnType);

    /**
     * 处理返回值,设置到 {@param mavContainer} 中
     *
     * Handle the given return value by adding attributes to the model and
     * setting a view or setting the
     * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
     * to indicate the response has been handled directly.
     * @param returnValue the value returned from the handler method
     * @param returnType the type of the return value. This type must have
     * previously been passed to {@link #supportsReturnType} which must
     * have returned {@code true}.
     * @param mavContainer the ModelAndViewContainer for the current request
     * @param webRequest the current request
     * @throws Exception if the return value handling results in an error
     */
    void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}
  • 两个方法,分别是是否支持解析该类型、以及处理返回值。

2. 类图

HandlerMethodReturnValueHandler 的实现类非常多,如下图所示:

![HandlerMethodReturnValueHandler 类图](17-Spring MVC 源码解析-HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler.assets/01.png)HandlerMethodReturnValueHandler 类图

因为实在太大,胖友可以点击 传送 查看。

下面,我要说什么化,想必熟悉我的胖友已经知道了,我们就分析几个 HandlerMethodReturnValueHandler 实现类。哈哈哈哈。

3. ModelAndViewContainer

org.springframework.web.method.support.ModelAndViewContainer ,主要是作为 Model 和 View 的容器,当然其中还有其它属性。

3.1 构造方法

// ModelAndViewContainer.java

/**
 * 是否在 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;

/**
 * TODO
 */
private final Set<String> noBinding = new HashSet<>(4);

/**
 * TODO
 */
private final Set<String> bindingDisabled = new HashSet<>(4);

/**
 * 用于设置 SessionAttribute 的标识
 */
private final SessionStatus sessionStatus = new SimpleSessionStatus();

/**
 * 请求是否处理完的标识
 */
private boolean requestHandled = false;
  • 属性有点多,不要懵逼。我们带着一起在下面的几个方法来瞅瞅。

3.2 getModel

#getModel() 方法,获得 Model 对象。代码如下:

// ModelAndViewContainer.java

public ModelMap getModel() {
    // 是否使用默认 Model
	if (useDefaultModel()) {
		return this.defaultModel;
	} else {
		if (this.redirectModel == null) {
			this.redirectModel = new ModelMap();
		}
		return this.redirectModel;
	}
}

/**
 * Whether to use the default model or the redirect model.
 */
private boolean useDefaultModel() {
	return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
}
  • 从代码中,可以看出,有两种情况下,使用

    defaultModel
    

    默认 Model

    • 情况一 !this.redirectModelScenario ,处理器返回 redirect 视图的标识为 false 的时候,即不重定向。
    • 情况二 this.redirectModel == null && !this.ignoreDefaultModelOnRedirect redirectModel 重定向 Model 为,并且 ignoreDefaultModelOnRedirecttrue ,即忽略 defaultModel
  • 那么,问题就来了,

    redirectModelScenario
    

    ignoreDefaultModelOnRedirect
    

    什么时候被改变?

    • redirectModelScenario 属性,可见 「6.3 handleReturnValue」 中。
    • ignoreDefaultModelOnRedirect 属性,和 RequestMappingHandlerAdapter.ignoreDefaultModelOnRedirect 的属性是一致的,在 RequestMappingHandlerAdapter#invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) 方法中,进行设置。
  • 另外,org.springframework.ui.ModelMap 是继承 LinkedHashMap 类,并增加了部分常用方法。比较简单,胖友点击 传送门 瞅瞅。

3.3 View 相关的方法

比较简单,胖友自己瞅瞅。

// ModelAndViewContainer.java

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);
}

3.4 requestHandled 标识

关于 requestHandled 的修改地方,实际在 Spring MVC 地方蛮多处都可以进行修改。感兴趣的胖友,可以使用 IDEA 右键该属性,选择 “Find Usages” 进行查看。

目前,我们在 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》「5.2 invokeAndHandle」 中,已经看到该属性的修改。

4. HandlerMethodReturnValueHandlerComposite

org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite ,实现 HandlerMethodReturnValueHandler 接口,复合的 HandlerMethodReturnValueHandler 实现类。

实际上,和我们在 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver》「3. HandlerMethodArgumentResolverComposite」 是一致的。

4.1 构造方法

// HandlerMethodReturnValueHandlerComposite.java

/**
 * HandlerMethodReturnValueHandler 数组
 */
private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();

另外,在 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(一)之 HandlerAdapter》「7.2.4 getDefaultReturnValueHandlers」我们已经看到了HandlerMethodReturnValueHandlerComposite 默认复合的所有 HandlerMethodReturnValueHandler 对象。😈 忘记的胖友,可以点下 传送门 再瞅瞅。

4.2 getReturnValueHandler

#getReturnValueHandler(MethodParameter returnType) 方法,获得方法返回值对应的 HandlerMethodReturnValueHandler 对象。代码如下:

// HandlerMethodReturnValueHandlerComposite.java

@Nullable
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
    // 遍历 returnValueHandlers 数组,逐个判断是否支持
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        // 如果支持,则返回
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}
  • 😈 不过有点纳闷,这里为什么不加缓存?!

4.3 supportsParameter

实现 #getReturnValueHandler(MethodParameter returnType) 方法,如果能获得到对应的 HandlerMethodReturnValueHandler 处理器,则说明支持。代码如下:

// HandlerMethodReturnValueHandlerComposite.java

@Override
public boolean supportsReturnType(MethodParameter returnType) {
	return getReturnValueHandler(returnType) != null;
}

4.4 handleReturnValue

#handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) 方法,处理返回值。代码如下:

// HandlerMethodReturnValueHandlerComposite.java

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	// <x> 获得 HandlerMethodReturnValueHandler 对象
	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	// 如果获取不到,则抛出 IllegalArgumentException 异常
	if (handler == null) {
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	}
	// 处理器返回值
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
  • 其中,比较神奇的是,<x> 处,调用 #selectHandler(Object value, MethodParameter returnType) 方法,获得 HandlerMethodReturnValueHandler 对象。代码如下:

    // HandlerMethodReturnValueHandlerComposite.java
    
    @Nullable
    private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
        // 判断是否为异步返回值
        boolean isAsyncValue = isAsyncReturnValue(value, returnType);
        // 遍历 HandlerMethodReturnValueHandler 数组,逐个判断是否支持
        for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
            // 如果是异步返回值的类型,则必须要求是 AsyncHandlerMethodReturnValueHandler 类型的处理器
            if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
                continue;
            }
            // 如果支持,则返回
            if (handler.supportsReturnType(returnType)) {
                return handler;
            }
        }
        return null;
    }
    
    // 判断逻辑是,有异步处理器 AsyncHandlerMethodReturnValueHandler ,并且返回值符合异步的类型
    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 的逻辑。

5. RequestResponseBodyMethodProcessor

org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor ,继承 AbstractMessageConverterMethodProcessor 抽象类,处理请求参数添加了 @RequestBody 注解,或者返回值添加了 @ResponseBody 注解的处理。

因为前后端分离之后,后端基本是提供 Restful API ,所以 RequestResponseBodyMethodProcessor 成为了目前最常用的 HandlerMethodReturnValueHandler 实现类。

![HandlerMethodReturnValueHandler 类图](17-Spring MVC 源码解析-HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler.assets/02.png)HandlerMethodReturnValueHandler 类图

从图中我们也会发现RequestResponseBodyMethodProcessor 也是 HandlerMethodArgumentResolver 的实现类。当然本文暂时不写,未来补充到 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver》 中。


简单示例代码如下:

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/walks")
    public List<User> walk() {
        List<User> users = new ArrayList();
        users.add(new User().setUsername("nihao"));
        users.add(new User().setUsername("zaijian"));
        return users;
    }

}
  • 虽然,#walks() 方法的返回值没添加 @ResponseBody 注解,但是 @RestController 注解,默认有 @ResponseBody 注解 。

5.1 构造方法

// RequestResponseBodyMethodProcessor.java

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 参数,一般情况是 ResponseBodyAdvice 类型,可实现对返回结果的修改。具体的使用示例,可见 《@ControllerAdvice, ResponseBodyAdvice 统一处理返回值/响应体》

5.2 supportsReturnType

实现 #supportsReturnType(MethodParameter returnType) 方法,判断是否添加 @ResponseBody 注解。代码如下:

// RequestResponseBodyMethodProcessor.java

@Override
public boolean supportsReturnType(MethodParameter returnType) {
	return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class)
            || returnType.hasMethodAnnotation(ResponseBody.class));
}

5.3 handleReturnValue

实现 #handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) 方法,代码如下:

// RequestResponseBodyMethodProcessor.java

@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 已处理。
  • <2> 处,创建请求和响应。
  • <3> 处,调用父类 AbstractMessageConverterMethodProcessor 的 #writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage) 方法,使用 HttpMessageConverter 对对象进行转换,并写入到响应。详细解析,见 「5.4.1 writeWithMessageConverters」

5.4.1 writeWithMessageConverters

该方法,是 AbstractMessageConverterMethodProcessor 中提供。

#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;
        valueType = getReturnValueType(body, returnType);
        targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
    }

    // <2> 是否为 Resource 类型。暂时无视,实际暂时没用到
    if (isResourceType(value, returnType)) {
        outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
        if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
                outputMessage.getServletResponse().getStatus() == 200) {
            Resource resource = (Resource) value;
            try {
                List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
                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.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
                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) {
                    // 打印日志
                    final 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、获得响应使用的 MediaType 对象。
    • 2、获得响应结果和 Media 对象获得对应的 HttpMessageConverter 对象。
    • 3、使用 HttpMessageConverter 将响应结果进行转化,写入到响应中。
  • <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);
    }
    
    • 因为我们实际使用时,不会返回 Resource 类型,所以暂时无视这个块逻辑。
  • ========== 第一步 ==========

  • <3> 处,选择使用的 MediaType 对象。

  • <3.1> 处,获得响应中的 ContentType 的值。

  • <3.1.1> 处,如果存在 ContentType 的值,并且不包含通配符,则使用它作为 selectedMediaType 。目前调试下来,<3.1> 处,返回的都是 null 。所以此处的逻辑,可以无视。😈 暂时不知道,什么情况下返回的是非 null

  • <3.2.1> 处,调用 #getAcceptableMediaTypes(HttpServletRequest request) 方法,从请求中,获得可接受的 MediaType 数组。默认实现是,从请求头 "ACCEPT" 中获取。关于这个方法,比较简单,胖友自己去瞅瞅,重点实现在 org.springframework.web.accept.HeaderContentNegotiationStrategy 类中。

  • <3.2.2> 处,调用 #getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, Type targetType) 方法,获得可产生的 MediaType 数组。代码如下:

    // AbstractMessageConverterMethodProcessor.java
    
    protected List<MediaType> getProducibleMediaTypes(
            HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
        // 先从请求 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性种获得。该属性的来源是 @RequestMapping(producer = xxx) 。
        Set<MediaType> mediaTypes =
                (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        // 如果非空,则使用该属性
        if (!CollectionUtils.isEmpty(mediaTypes)) {
            return new ArrayList<>(mediaTypes);
        // <x> 如果 allSupportedMediaTypes 非空,则遍历 HttpMessageConverter 数组,进行类型匹配
        } else if (!this.allSupportedMediaTypes.isEmpty()) {
            List<MediaType> result = new ArrayList<>();
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                if (converter instanceof GenericHttpMessageConverter && targetType != null) {
                    if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
                        result.addAll(converter.getSupportedMediaTypes());
                    }
                } else if (converter.canWrite(valueClass, null)) {
                    result.addAll(converter.getSupportedMediaTypes());
                }
            }
            return result;
        // 其它,则返回 MediaType.ALL
        } else {
            return Collections.singletonList(MediaType.ALL);
        }
    }
    
    • 逻辑比较简单,胖友自己瞅瞅。重点在 <x> 处。
  • <3.2.3> 处,如果 body 非空,并且无可产生的 MediaType 数组,则抛出 HttpMediaTypeNotAcceptableException 异常。

  • 【重要】<3.2.4> 处,通过 acceptableTypes 来比对,将符合的 producibleType 添加到 mediaTypesToUse 结果数组中。

  • <3.2.5> 处,如果没有符合的,并且 body 非空,则抛出 HttpMediaTypeNotAcceptableException 异常。

  • 【重要】<3.2.6> 处,按照 MediaType 的 specificity 和 quality 排序。

  • 【重要】<3.2.7> 处,选择其中一个最匹配的,主要考虑不包含通配符的。例如 application/json;q=0.8

  • ========== 第二步 + 第三步 ==========

  • <4> 处,如果匹配到,则进行写入逻辑。

  • <4.1> 处,移除 quality 。例如,application/json;q=0.8 移除后为 application/json

  • <4.2> 处,遍历 messageConverters 数组。关于 messageConverters 的定义,代码如下:

    // AbstractMessageConverterMethodProcessor.java
    
    /**
     * HttpMessageConverter 数组
     */
    protected final List<HttpMessageConverter<?>> messageConverters;
    
  • <4.3> 处,判断 HttpMessageConverter 是否支持转换目标类型。

  • <5.1> 处,如果有 RequestResponseBodyAdvice ,则可以对返回的结果,做修改。关于 RequestResponseBodyAdvice 的定义,代码如下:

    // AbstractMessageConverterMethodProcessor.java
    
    /**
     * RequestResponseBodyAdviceChain 对象
     */
    private final RequestResponseBodyAdviceChain advice;
    
    // 构造方法中
    this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
    
    • 关于 RequestResponseBodyAdviceChain 类,本文暂时不去做详细解析,胖友先自己去研究下。
  • <5.2> 处,body 非空,则进行写入。

  • 【重要】<5.3> 处,写入内容。

  • <5.4> 处,return 返回。结束整个逻辑。

  • <6> 处,如果到达此处,并且 body 非空,说明没有匹配的 HttpMessageConverter 转换器,则抛出 HttpMediaTypeNotAcceptableException 异常。


至此,整个逻辑已经解析完成。在日常使用 Spring MVC 中,我们经常碰到的组件的调用,应该要有一个比较好的理解。如果没有,在多多调试。特别是:

  • 1、HttpMessageConverter
  • 2、RequestResponseBodyAdviceChain

6. ViewNameMethodReturnValueHandler

org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler ,实现 HandlerMethodReturnValueHandler 接口,处理返回结果是视图名的 ReturnValueHandler 实现类。

ViewNameMethodReturnValueHandler 适用于前后端未分离Controller 返回视图名的场景,例如 JSP、Freemarker 等等。

6.1 构造方法

// ViewNameMethodReturnValueHandler.java

/**
 * 重定向的表达式的数组
 */
@Nullable
private String[] redirectPatterns;
  • redirectPatterns 属性,一般情况下,不进行设置。至于用途,我们来看看 #isRedirectViewName(String viewName) 方法,判断是否为重定向的视图名。代码如下:

    // ViewNameMethodReturnValueHandler.java
    
    protected boolean isRedirectViewName(String viewName) {
    	return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) // 符合 redirectPatterns 表达式
                || viewName.startsWith("redirect:")); // 以 redirect: 开头
    }
    
    • 😈 是不是明白落?是不是也弄清楚,为什么 "redirect:" 开头,就是重定向的视图。

6.2 supportsReturnType

实现 #supportsReturnType(MethodParameter returnType) 方法,判断返回值类型是否为 void 或者字符串。代码如下:

// ViewNameMethodReturnValueHandler.java

@Override
public boolean supportsReturnType(MethodParameter returnType) {
	Class<?> paramType = returnType.getParameterType();
	return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
  • 那么有胖友就会有疑惑?如果想要使用 「5. RequestResponseBodyMethodProcessor」 ,结果返回 String 类型的结果,岂不是被 ViewNameMethodReturnValueHandler ?在回到 传送门 再瞅瞅RequestResponseBodyMethodProcessor 的添加在 ViewNameMethodReturnValueHandler 之前,所以不会有这样的问题。

6.3 handleReturnValue

实现 #handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) 方法,代码如下:

// ViewNameMethodReturnValueHandler.java

@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());
    }
}
  • <1> 处,设置视图名到 mavContainer 中。
  • <2> 处,如果是重定向,则标记到 mavContainer 中的 redirectModelScenario 属性中。
  • 注意噢,胖友是否有发现,此时 redirectModelScenariorequestHandled 属性,并未并未像 「5. RequestResponseBodyMethodProcessor」 一样,设置为 true 。这是为什么呢?因为返回结果是视图名的场景下,会使用 ViewResolver 解析出其对应的视图 View 对象,然后执行 View#render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) 方法,进行渲染。

666. 彩蛋

感觉,还有一些需要写的 HandlerMethodReturnValueHandler 实现类,暂时还没想好。

如果胖友有什么 HandlerMethodReturnValueHandler 实现类希望艿艿来写请在星球给我留言。TODO 9999 HandlerMethodReturnValueHandler

参考和推荐如下文章: