目的

@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@ConditionalOnMissingBeanpublic 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的调用过程。

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接口层级,该接口具体调用哪个类我们来看看下面这张图。

可以看到在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实现是一种可行方案。最后,本文如存在不正确的地方,欢迎并感谢批评指正!

SpringMVC —— @ResponseBody原理相关推荐

  1. SpringMVC @RequestBody和@ResponseBody原理解析

    SpringMVC @RequestBody和@ResponseBody原理解析 前言 @RequestBody作用是将http请求解析为对应的对象.例如: http请求的参数(application ...

  2. SpringMVC工作原理详解

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 先来看一下什么是 MVC 模式 MVC 是一种设计模式. MVC 的原理图如下: SpringMV ...

  3. SpringMVC工作原理之一:DispatcherServlet

    一.DispatcherServlet 处理流程 在整个 Spring MVC 框架中,DispatcherServlet 处于核心位置,它负责协调和组织不同组件完成请求处理并返回响应工作.在看 Di ...

  4. 【SpringMVC】面试常见问题:总结 SpringMVC 运行原理

    请简述SpringMVC 运行原理: 如果在 web.xml 中设置 DispatcherServlet 的 <url-pattern> 为/时, 当用户发起请求, 请求一个控制器, 首先 ...

  5. SpringMVC工作原理 1

    大家好,我是IT修真院深圳分院第十一期学员,一枚正直纯洁善良的JAVA程序员. 今天给大家分享一下,修真院官网JAVA任务二的一个知识点:SpringMVC工作原理 1.背景介绍 一:背景介绍 Jav ...

  6. spring源码分析第四天------springmvc核心原理及源码分析

    spring源码分析第四天------springmvc核心原理及源码分析 1.基础知识普及 2. SpringMVC请求流程 3.SpringMVC代码流程 4.springMVC源码分析 4.1 ...

  7. SpringMVC→简介、MVC、SpringMVC工作原理、Maven搭建第一个SpringMVC、请求参数接收、重定向、文件上传、AJAX异步访问、请求参数接收绑定JSON、@注解及传参

    MVC SpringMVC工作原理 Maven搭建第一个SpringMVC 目录结构 web.xml *-servlet.xml Controller请求处理类 跳转页面 Maven运行服务器项目 浏 ...

  8. SpringMVC工作原理及源码解析

    SpringMVC工作原理及源码解析 一:SpringMVC原理图 二:SpringMVC的主要组件 1.前端控制器DispatcherServlet: 2.处理器映射器HandlerMapping: ...

  9. java中MVC原理详解,SpringMVC运行原理,MVC的基本原理

    SpringMVC运行原理,MVC的基本原理 按照上边的执行流程图,我们可以看出一个SpringMVC整体的一个执行轮廓,下面我们具体来分析下 首先服务器接收到一个请求,匹配并调用了我们的前端控制器( ...

最新文章

  1. Cooike的一些用法
  2. mysql哪些优化手段_mysql explain 及常见优化手段
  3. 易语言 设置屏幕刷新率 源码_一块好的手机屏幕应具备什么条件?现在了解还不晚...
  4. javascript的垃圾回收机制指的是什么?
  5. 挖洞技巧:APP手势密码绕过思路总结
  6. oracle 不等于某类,Oracle如何查询不等于某数值
  7. 几个删除重复记录的SQL语句
  8. 计算机设备投标标书范本,OA办公自动化系统投标文件(标书范本)
  9. python词组语义相似度_文本匹配,语义相似度,匹配相似短语/单词python语义wordNet模糊匹配...
  10. 用Golang写一个搜索引擎(0x03)
  11. mysql查找数据库中是否已经存在某张表
  12. OpenCV学习笔记——多种Smooth平滑处理
  13. 如何解决PHP上传中文出错,如何解决php上传中文乱码的问题
  14. 类似平行宇宙的灵异事件,三个常见的解释
  15. ucGUI3.9版本快速移植构建
  16. echarts实现半圆饼图
  17. 如何注册我的世界服务器账号密码,我的世界电脑服务器怎么注册登录密码
  18. Oracle10g ora12170,ORA-3136、TNS-12535 12170 12606
  19. 校园无线网登陆成功,但打开浏览器不能上网怎么办?标签上显示注销页怎么办?
  20. 【Impala】根据当前日期取去年、今年、上月、日期差

热门文章

  1. 如何定位Java源文件_webgisframe.java 源代码在线查看 - 实现网络GPS定位车辆的位置 资源下载 虫虫电子下载站...
  2. Suzy找到实习了吗 | 字符串结束啦 今天学习kmp 题还没做!!!记得回来补!!!
  3. SpringBoot2.6.5+Swagger3配置
  4. 什么是百度信息流广告?
  5. java指针乱跳_鼠标卡顿,指针乱跳,鼠标失灵的解决办法。 涨知识了
  6. [Swift]LeetCode16. 最接近的三数之和 | 3Sum Closest
  7. ios6.0 siri语音识别
  8. 安装教程之Visual C++6.0的安装
  9. java解析JT808协议
  10. python概念股_宫廷御牛:半导体板块再次爆发,最新最全半导体概念股名单汇总(值得收藏)...