SpringMVC —— @ResponseBody原理

目的

@ResponseBody可以添加在控制器类或其下方法中,这样在请求结果对象返回时能够将其解析为JSON格式,这是如何实现的呢?  
文章目的在于梳理@ResponseBody实现的原理  

引子

先看一段摘自官网文档的内容,https://spring.io/guides/gs/rest-service/

The Greeting object must be converted to JSON. Thanks to Spring’s HTTP message converter support, you need not do this conversion manually.
Because Jackson 2 is on the classpath,Spring’s MappingJackson2HttpMessageConverter is automatically chosen to convert the Greeting instance to JSON.

探究

文档中提及了具体实现Json转换类,可以在JacksonHttpMessageConvertersConfiguration这个配置类中找到mappingJackson2HttpMessageConverter的Bean注入。

@Configuration(
    proxyBeanMethods = false
)
class JacksonHttpMessageConvertersConfiguration {
    JacksonHttpMessageConvertersConfiguration() {
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({XmlMapper.class})
    @ConditionalOnBean({Jackson2ObjectMapperBuilder.class})
    protected static class MappingJackson2XmlHttpMessageConverterConfiguration {
        protected MappingJackson2XmlHttpMessageConverterConfiguration() {
        }

        @Bean
        @ConditionalOnMissingBean
        public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter(Jackson2ObjectMapperBuilder builder) {
            return new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build());
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({ObjectMapper.class})
    @ConditionalOnBean({ObjectMapper.class})
    @ConditionalOnProperty(
        name = {"spring.mvc.converters.preferred-json-mapper"},
        havingValue = "jackson",
        matchIfMissing = true
    )
    static class MappingJackson2HttpMessageConverterConfiguration {
        MappingJackson2HttpMessageConverterConfiguration() {
        }

        // 关键:这里注入了mappingJackson2HttpMessageConverter,这个类是AbstractJackson2HttpMessageConverter的子类
        @Bean
        @ConditionalOnMissingBean(
            value = {MappingJackson2HttpMessageConverter.class},
            ignoredType = {"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter"}
        )
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
            return new MappingJackson2HttpMessageConverter(objectMapper);
        }
    }
}

可以看到,上面类中的核心是注入了一个mappingJackson2HttpMessageConverter类,那么接下来就是看一下这个类在哪里被使用了。先来看看这个类的接口实现及继承关系。

在这里插入图片描述

这里我们看到最顶层的接口是HttpMessageConverter,下面的问题就是弄清HttpMessageConverter的调用过程。

image.png

Debug可以发现,数据对象返回后经过了RequestResponseBodyMethodProcessor.handleReturnValue方法,其相关代码如下,可以看到调用了this.writeWithMessageConverters来处理返回的结果。

    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        mavContainer.setRequestHandled(true);
        ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
        // 核心调用
        this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }

上面代码中,最核心的调用方法是writeWithMessageConverters方法,其具体实现如下,该方法的核心调用是converter.write(body, selectedMediaType, outputMessage)

    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        Object body;
        Class valueType;
        Object targetType;
        if (value instanceof CharSequence) {
            body = value.toString();
            valueType = String.class;
            targetType = String.class;
        } else {
            body = value;
            valueType = this.getReturnValueType(value, returnType);
            targetType = GenericTypeResolver.resolveType(this.getGenericType(returnType), returnType.getContainingClass());
        }

        if (this.isResourceType(value, returnType)) {
            outputMessage.getHeaders().set("Accept-Ranges", "bytes");
            if (value != null && inputMessage.getHeaders().getFirst("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 var19) {
                    outputMessage.getHeaders().set("Content-Range", "bytes */" + resource.contentLength());
                    outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
                }
            }
        }

        MediaType selectedMediaType = null;
        MediaType contentType = outputMessage.getHeaders().getContentType();
        boolean isContentTypePreset = contentType != null && contentType.isConcrete();
        if (isContentTypePreset) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Found 'Content-Type:" + contentType + "' in response");
            }

            selectedMediaType = contentType;
        } else {
            HttpServletRequest request = inputMessage.getServletRequest();

            List acceptableTypes;
            try {
                acceptableTypes = this.getAcceptableMediaTypes(request);
            } catch (HttpMediaTypeNotAcceptableException var20) {
                int series = outputMessage.getServletResponse().getStatus() / 100;
                if (body != null && series != 4 && series != 5) {
                    throw var20;
                }

                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Ignoring error response content (if any). " + var20);
                }

                return;
            }

            List<MediaType> producibleTypes = this.getProducibleMediaTypes(request, valueType, (Type)targetType);
            if (body != null && producibleTypes.isEmpty()) {
                throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);
            }

            List<MediaType> mediaTypesToUse = new ArrayList();
            Iterator var15 = acceptableTypes.iterator();

            MediaType mediaType;
            while(var15.hasNext()) {
                mediaType = (MediaType)var15.next();
                Iterator var17 = producibleTypes.iterator();

                while(var17.hasNext()) {
                    MediaType producibleType = (MediaType)var17.next();
                    if (mediaType.isCompatibleWith(producibleType)) {
                        mediaTypesToUse.add(this.getMostSpecificMediaType(mediaType, producibleType));
                    }
                }
            }

            if (mediaTypesToUse.isEmpty()) {
                if (body != null) {
                    throw new HttpMediaTypeNotAcceptableException(producibleTypes);
                }

                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
                }

                return;
            }

            MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
            var15 = mediaTypesToUse.iterator();

            while(var15.hasNext()) {
                mediaType = (MediaType)var15.next();
                if (mediaType.isConcrete()) {
                    selectedMediaType = mediaType;
                    break;
                }

                if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
                    selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                    break;
                }
            }

            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Using '" + selectedMediaType + "', given " + acceptableTypes + " and supported " + producibleTypes);
            }
        }

        HttpMessageConverter converter;
        GenericHttpMessageConverter genericConverter;
        label183: {
            if (selectedMediaType != null) {
                selectedMediaType = selectedMediaType.removeQualityValue();
                Iterator var23 = this.messageConverters.iterator();

                while(var23.hasNext()) {
                    converter = (HttpMessageConverter)var23.next();
                    genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null;
                    if (genericConverter != null) {
                        if (((GenericHttpMessageConverter)converter).canWrite((Type)targetType, valueType, selectedMediaType)) {
                            break label183;
                        }
                    } else if (converter.canWrite(valueType, selectedMediaType)) {
                        break label183;
                    }
                }
            }

            if (body != null) {
                Set<MediaType> producibleMediaTypes = (Set)inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
                if (!isContentTypePreset && CollectionUtils.isEmpty(producibleMediaTypes)) {
                    throw new HttpMediaTypeNotAcceptableException(this.getSupportedMediaTypes(body.getClass()));
                }

                throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
            }

            return;
        }

        body = this.getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, converter.getClass(), inputMessage, outputMessage);
        if (body != null) {
            LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
                return "Writing [" + LogFormatUtils.formatValue(body, !traceOn) + "]";
            });
            this.addContentDispositionHeader(inputMessage, outputMessage);
            if (genericConverter != null) {
                genericConverter.write(body, (Type)targetType, selectedMediaType, outputMessage);
            } else {
                // 核心调用
                converter.write(body, selectedMediaType, outputMessage);
            }
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("Nothing to write: null body");
        }

    }

进一步梳理

上面代码流程可能不是很好理解,这里放出时序图来帮助概览接口返回数据时进行convert的过程。

在这里插入图片描述

为了避免调用层级过深导致图过于复杂,时序图只绘制到了HttpMessageConverter接口层级,该接口具体调用哪个类我们来看看下面这张图。

image.png

可以看到在AbstractGenericHttpMessageConverter抽象类的调用方法时,转去调用了writeInternal方法。具体调用的实现是AbstractJackson2HttpMessageConverter的writerInternal方法,其具体实现如下

    protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        MediaType contentType = outputMessage.getHeaders().getContentType();
        JsonEncoding encoding = this.getJsonEncoding(contentType);
        Class<?> clazz = object instanceof MappingJacksonValue ? ((MappingJacksonValue)object).getValue().getClass() : object.getClass();
        ObjectMapper objectMapper = this.selectObjectMapper(clazz, contentType);
        Assert.state(objectMapper != null, "No ObjectMapper for " + clazz.getName());
        OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());

        try {
            JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding);
            Throwable var10 = null;

            try {
                this.writePrefix(generator, object);
                Object value = object;
                Class<?> serializationView = null;
                FilterProvider filters = null;
                JavaType javaType = null;
                if (object instanceof MappingJacksonValue) {
                    MappingJacksonValue container = (MappingJacksonValue)object;
                    value = container.getValue();
                    serializationView = container.getSerializationView();
                    filters = container.getFilters();
                }

                if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
                    javaType = this.getJavaType(type, (Class)null);
                }

                ObjectWriter objectWriter = serializationView != null ? objectMapper.writerWithView(serializationView) : objectMapper.writer();
                if (filters != null) {
                    objectWriter = objectWriter.with(filters);
                }

                if (javaType != null && javaType.isContainerType()) {
                    objectWriter = objectWriter.forType(javaType);
                }

                SerializationConfig config = objectWriter.getConfig();
                if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
                    objectWriter = objectWriter.with(this.ssePrettyPrinter);
                }

                objectWriter.writeValue(generator, value);
                this.writeSuffix(generator, object);
                generator.flush();
            } catch (Throwable var26) {
                var10 = var26;
                throw var26;
            } finally {
                if (generator != null) {
                    if (var10 != null) {
                        try {
                            generator.close();
                        } catch (Throwable var25) {
                            var10.addSuppressed(var25);
                        }
                    } else {
                        generator.close();
                    }
                }

            }

        } catch (InvalidDefinitionException var28) {
            throw new HttpMessageConversionException("Type definition error: " + var28.getType(), var28);
        } catch (JsonProcessingException var29) {
            throw new HttpMessageNotWritableException("Could not write JSON: " + var29.getOriginalMessage(), var29);
        }
    }

MappingJackson2HttpMessageConverter类直接调用父类的writeInternal方法,并未重写,因此实际转换逻辑即为上方代码逻辑。

以上就是@ResponseBody的调用流程及实现原理,根据此调用流程,如果我们想自定义请求返回格式,利用HttpMessageConvert实现是一种可行方案。最后,本文如存在不正确的地方,欢迎并感谢批评指正!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值