SpringBoot源码系列(7):返回值处理器

前言

在SpringBoot/SpringMVC中,我们在@ResponseBody注解标注的Controller中直接返回对象,即可为请求返回所需类型的返回值(比如json格式)。本文就来谈谈返回值处理器的原理。

原文地址:https://xuedongyun.cn/post/44248/

返回值处理器原理

RequestMappingHandlerAdapter

有关参数解析的原理,可以查看我之前的文章:SpringBoot源码系列(5):参数解析

RequestMappingHandlerAdapter执行了handleInternal方法。代码中,主要设置了请求值解析器和返回值解析器,然后调用了invocableMethod.invokeAndHandle进行处理

// 代码有删改
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        
        // argumentResolvers:请求值解析器,用于确定目标方法每一个参数值是什么
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        // returnValueHandlers:返回值处理器
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        
        // 执行并处理
        invocableMethod.invokeAndHandle(webRequest, mavContainer);

        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
}

ServletInvocableHandlerMethod

RequestMappingHandlerAdapter中,调用了invocableMethod.invokeAndHandle。我们现在查看ServletInvocableHandlerMethodinvokeAndHandle方法

// 代码有删改
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    // 此处已拿到返回对象
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);
	
    // 如果返回为空,直接返回
    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;
    }

    try {
        // 利用返回值处理器,处理返回值
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
}

HandlerMethodReturnValueHandlerComposite

我们来看this.returnValueHandlershandleReturnValue方法

@Override
public void handleReturnValue(@Nullable Object returnValue, 
                              MethodParameter returnType,
                              ModelAndViewContainer mavContainer, 
                              NativeWebRequest webRequest) throws Exception {
	
    // 找合适的返回值处理器
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("...");
    }
    
    // 利用找到的返回值处理器进行处理
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

其中,selectHandler方法选择合适的返回值处理器

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, 
                                                      MethodParameter returnType) {
    // 此处是用来判断是否是异步返回值的,可以先不管
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        
        // 遍历所有返回值处理器,寻找相应处理器
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

this.returnValueHandlers一共有15种

this.returnValueHandlers = {ArrayList} size = 15
	0 = {ModelAndViewMethodReturnValueHandler}
	1 = {ModelMethodProcessor}
	2 = {ViewMethodReturnValueHandler}
	3 = {ResponseBodyEmitterReturnValueHandler}
	4 = {StreamingResponseBodyReturnValueHandler}
	5 = {HttpEntityMethodProcessor}
	6 = {HttpHeadersReturnValueHandler}
	7 = {CallableMethodReturnValueHandler)
	8 = {DeferredResultMethodReturnValueHandler}
	9 = {AsyncTaskMethodReturnValueHandler}
	10 = {ServletModelAttributeMethodProcessor}
	11 = (RequestResponseBodyMethodProcessor}
	12 = {ViewNameMethodReturnValueHandler}
	13 = {MapMethodProcessor)
	14 = {ServletModelAttributeMethodProcessor}

(例子一)ModelAndViewMethodReturnValueHandlersupportsReturnType方法:判断返回值类型是否继承自ModelAndView(也即返回值能否转为ModelAndView类型)

(例子二)RequestResponseBodyMethodProcessorsupportsReturnType方法:判断类或者方法,有没有加@ResponseBody注解

HandlerMethodReturnValueHandler

RequestResponseBodyMethodProcessor为例,查看handler.handleReturnValue方法

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    mavContainer.setRequestHandled(true);
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

   	// 消息转换器,进行写出操作,将返回值转换为json
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); // 步入
}

查看writeWithMessageConverters方法。这里包含“内容协商”的核心原理:浏览器能接收的类型“并上”服务器能提供的类型,使用第一个作为使用的媒体类型。之后第二次遍历messageConverters,寻找支持的Converter

// 代码有删改
protected <T> void writeWithMessageConverters(@Nullable T value, 
                                              MethodParameter returnType,
                                              ServletServerHttpRequest inputMessage, 		
                                              ServletServerHttpResponse outputMessage)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

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

    // 判断返回值是不是资源类型(是不是流数据)
    if (isResourceType(value, returnType)) {
        // 调用对流数据的处理方法,响应回去
    }
	
    // MediaType(媒体类型),牵扯到内容协商
    // 浏览器发请求的时候,在请求头的Accept中告诉服务器能接收的类型
    MediaType selectedMediaType = null;
    
    // 先看看响应头中,有没有已经写好的响应内容类型
    MediaType contentType = outputMessage.getHeaders().getContentType();
    boolean isContentTypePreset = contentType != null && contentType.isConcrete();
    
    // 如果response已经有了写好的响应类型,就直接用
    if (isContentTypePreset) {
        ...
        selectedMediaType = contentType;
    }
    // 不然,拿到能接收的媒体类型
    else {
        HttpServletRequest request = inputMessage.getServletRequest();
        // 浏览器能接收的类型
        List<MediaType> acceptableTypes = getAcceptableMediaTypes(request); 
        // 服务器能产生的类型(遍历所有MessageConverter,看哪些支持,进而统计媒体类型)
        List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType); 

        // 计算最终能使用的媒体类型
        List<MediaType> mediaTypesToUse = new ArrayList<>();
        for (MediaType requestedType : acceptableTypes) {
            for (MediaType producibleType : producibleTypes) {
                if (requestedType.isCompatibleWith(producibleType)) {
                    mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
                }
            }
        }
        
        MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
		
        // 最终拿到selectedMediaType,比如这里就是"application/json"
        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 (selectedMediaType != null) {
        selectedMediaType = selectedMediaType.removeQualityValue();
        // 遍历所有的HttpMessageConverter(消息转换器),看谁能处理。(可以看看这个接口的方法)
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            GenericHttpMessageConverter genericConverter = 
                (converter instanceof GenericHttpMessageConverter ? 
                 (GenericHttpMessageConverter<?>) converter : null);
            if (genericConverter != null ?
                // GenericHttpMessageConverter由canWrite判断能否使用
                ((GenericHttpMessageConverter) converter).canWrite(targetType, 
                                                                   valueType, 
                                                                   selectedMediaType) :
                // 这里AbstractHttpMessageConverter的canWrite实际上由support和canWrite组成
                converter.canWrite(valueType, selectedMediaType)) {
                body = getAdvice().
                    beforeBodyWrite(body, 
                                    returnType, 
                                    selectedMediaType,
                                    (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                                    inputMessage, outputMessage);
                if (body != null) {
                    Object theBody = body;
                    LogFormatUtils.traceDebug("...");
                    addContentDispositionHeader(inputMessage, outputMessage);
                    if (genericConverter != null) {
                        // 用当前转换器,写出去,步入
                        genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                    }
                }
                return;
            }
        }
    }
}

this.messageConverters有9个,能否使用的判断由supportcanWrite共同决定,下面只考虑了support的部分

this.messageConverters = {ArrayList} size = 9
	0={ByteArrayHttpMessageConverter}			只支持Byte类型
	1={StringHttpMessageConverter}				只支持String类型
	2={StringHttpMessageConverter}				只支持String类型(这两个默认字符集不一样,UTF-8和ISO-8859-1)
	3={ResourceHttpMessageConverter)			只支持返回值为Resource
	4={ResourceRegionHttpMessageConverter}		只支持返回值为ResourceRegion
	5={SourceHttpMessageConverter}				只支持返回值为DOMSource,SAXSource,
															StAXSource,StreamSource,Source
	6={AllEncompassingFormHttpMessageConverter}	只支持返回值为MultiValueMap
	7={MappingJackson2HttpMessageConverter}		直接返回true
	8={MappingJackson2HttpMessageConverter}		直接返回true

MappingJackson2HttpMessageConverter

genericConverter.write方法(此处为MappingJackson2HttpMessageConverter),利用底层的jackson包,ObjectMapper转换的

@Override
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
                        HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

    final HttpHeaders headers = outputMessage.getHeaders();
    // 向头中写入数据,此处为Content-Type=application/json
    addDefaultHeaders(headers, t, contentType);

    ...
    
    // 可以步入看看,jackson将对象转json,放入response中的一个outputBuffer中
    writeInternal(t, type, outputMessage);
    // 将Buffer中的内容写出
    outputMessage.getBody().flush();
}

自定义MessageConverter

假设我们想自定义一个application/my-type协议,请求将返回的User以name-age的格式返回

我们首先要写一个类,实现HttpMessageConverter,重写canWritewritegetSupportedMediaTypes方法

public class MyConverter implements HttpMessageConverter<User> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(User.class);
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/my-type");
    }

    @Override
    public User read(Class<? extends User> clazz, 
                     HttpInputMessage inputMessage) 
        	throws HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(User user, 
                      MediaType contentType, 
                      HttpOutputMessage outputMessage) 
        	throws HttpMessageNotWritableException, IOException {
        String data = user.getUserName() + "-" + user.getUserAge();
        OutputStream body = outputMessage.getBody();
        body.write(data.getBytes());
    }
}

利用WebMvcConfigurer中的extendMessageConverters方法,我们可以向容器中添加自定义的converter

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        MyConverter myConverter = new MyConverter();
        converters.add(myConverter);
    }
}

最后,在我们前端的请求中,我们只需要在请求头指定:Accept=application,即可拿到我们想要的格式

总结和补充

  1. 标注了@ResponseBody注解
  2. 调用RequestResponseBodyMethodProcessor这个返回值处理器
  3. 调用各种MessageConverter进行数据的转换
  4. MessageConverter可以支持各种媒体类型数据的操作(读,写)
  5. 内容协商找到合适的MessageConverter

那原本已有的MessageConverter是如何被添加的呢?我们来简单看一看源码:

WebMvcAutoConfiguration中添加了所有的默认MessageConverter

@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        this.messageConvertersProvider.ifAvailable(
            (customConverters) -> converters.addAll(customConverters.getConverters())
        );
    }
}

继续查看,简单列出一下执行过的函数:

customConverters.getConverters()->getDefaultConverters()->defaultMessageConverters()->getMessageConverters()->addDefaultHttpMessageConverters()

这里自动为我们添加了很多converter

protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    messageConverters.add(new ByteArrayHttpMessageConverter());
    messageConverters.add(new StringHttpMessageConverter());
    messageConverters.add(new ResourceHttpMessageConverter());
    messageConverters.add(new ResourceRegionHttpMessageConverter());
    if (!shouldIgnoreXml) {
        try {
            messageConverters.add(new SourceHttpMessageConverter<>());
        }
        catch (Error err) {
            // Ignore when no TransformerFactory implementation is available
        }
    }
    messageConverters.add(new AllEncompassingFormHttpMessageConverter());

    if (romePresent) {
        messageConverters.add(new AtomFeedHttpMessageConverter());
        messageConverters.add(new RssChannelHttpMessageConverter());
    }

    if (!shouldIgnoreXml) {
        if (jackson2XmlPresent) {
            Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }
            messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
        }
        else if (jaxb2Present) {
            messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }
    }

    if (kotlinSerializationJsonPresent) {
        messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
    }
    if (jackson2Present) {
        Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
        if (this.applicationContext != null) {
            builder.applicationContext(this.applicationContext);
        }
        messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
    }
    else if (gsonPresent) {
        messageConverters.add(new GsonHttpMessageConverter());
    }
    else if (jsonbPresent) {
        messageConverters.add(new JsonbHttpMessageConverter());
    }

    if (jackson2SmilePresent) {
        Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
        if (this.applicationContext != null) {
            builder.applicationContext(this.applicationContext);
        }
        messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
    }
    if (jackson2CborPresent) {
        Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
        if (this.applicationContext != null) {
            builder.applicationContext(this.applicationContext);
        }
        messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
    }
}

比如其中jackson2Present就是如此判断。这也就是为什么我们导入jackson-xml,就能使用的原因

jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值