基于SpringBoot自定义接口响应消息格式及原理解析

环境:Springboot3.0.5

消息格式转换原理

在默认情况下,RestController接口返回的数据格式是json,如下接口:

@RestController
@RequestMapping("/rmf")
public class ResponseMessageFormatController {


  @GetMapping("/index")
  public Users index() {
    return new Users(1, "张飒", 66, "男") ;
  }
  
}

接口返回

基于SpringBoot自定义接口响应消息格式及原理解析

接口默认返回了json数据格式

通过Postman,我这里是没有在Header中添加Accept。当后台在处理该请求时会认为你能接收任意类型的数据格式也就是MediaType=*/*,紧接着会获取当前所有的HttpMessageConverter支持的MediaType,然后遍历所有的MediaType,找到第一个类型中只要不包含’*’的MediaType就直接返回,默认情况下application/json排在第一个,所以最终确定返回客户端的数据格式将会是application/json,最后再遍历所有的HttpMessageConverter找出支持applicatioin/json类型的HttpMessageConverter,最终确定了MappingJackson2HttpMessageConverter直接进行客户端的输出。

核心源码:

public abstract class AbstractMessageConverterMethodProcessor {
  protected <T> void writeWithMessageConverters(...) {
    List<MediaType> acceptableTypes;
    try {
      // 获取请求header中的Accept,如果请求中没有,则返回 */* 接受任意类型
      acceptableTypes = getAcceptableMediaTypes(request);
    }
    // 获取当前容器中所有的HttpMessageConverter支持的MediaType
    List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
    
    List<MediaType> compatibleMediaTypes = new ArrayList<>();
    // 确定具体输出的应该是那种消息格式,
    determineCompatibleMediaTypes(acceptableTypes, producibleTypes, compatibleMediaTypes);
    // 遍历
    for (MediaType mediaType : compatibleMediaTypes) {
      // 判断当前的MediaType的类型[type] 和 子类型 [subtype] 中分别不是 * 和 *+
      // 就符合条件,找到第一个直接返回,这就确定了将会给客户端返回的消息格式
      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,是否有能支持上面确定的selectedMediaType
      for (HttpMessageConverter<?> converter : this.messageConverters) {
        GenericHttpMessageConverter genericConverter =(converter instanceof GenericHttpMessageConverter ghmc ? ghmc : null);
        if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) {
          body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage);
          // 输出结果
          ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
        }
      }
    }
  }
}

以上是关于消息处理的部分源码分析。

返回XML格式

如果需要返回xml格式的数据,我们只需引入下面依赖即可。

引入依赖:

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

postman进行测试

基于SpringBoot自定义接口响应消息格式及原理解析

正确的返回了xml格式

为什么引入上面的依赖后就可以直接通过设置Accept为application/xml就可以返回xml格式呢?

原理:

// 在这导入的JacksonHttpMessageConvertersConfiguration类中进行了配置
@Import({ JacksonHttpMessageConvertersConfiguration.class })
public class HttpMessageConvertersAutoConfiguration {
}


@Configuration(proxyBeanMethods = false)
class JacksonHttpMessageConvertersConfiguration {
  @Configuration(proxyBeanMethods = false)
  // 当前的类路径下有XmlMapper类,该类就在上面引入的包中
  @ConditionalOnClass(XmlMapper.class)
  @ConditionalOnBean(Jackson2ObjectMapperBuilder.class)
  protected static class MappingJackson2XmlHttpMessageConverterConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter(Jackson2ObjectMapperBuilder builder) {
      return new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build());
    }
  }  
}

当当前的类路径下有XmlMapper就会自动的创建处理XML格式的MappingJackson2XmlHttpMessageConverter

自定义消息格式

如果客户端要求接收的数据格式是yaml格式,这时候就需要自定义HttpMessageConverter

引入依赖:

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

自定义HttpMessageConverter:

@Component
public class YamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {


  public YamlHttpMessageConverter() {
    super(new MediaType("application", "yaml")) ;
  }
  
  @Override
  protected boolean supports(Class<?> clazz) {
    return true ;
  }


  @Override
  protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
      throws IOException, HttpMessageNotReadableException {
    return null ;
  }


  @Override
  protected void writeInternal(Object t, HttpOutputMessage outputMessage)
      throws IOException, HttpMessageNotWritableException {
    try (OutputStream os = outputMessage.getBody()) {
      YAMLFactory factory = new YAMLFactory();
      // 该配置作用就是:去掉开头的三个 '---'
      factory.configure(Feature.WRITE_DOC_START_MARKER, false) ;
      ObjectMapper mapper = new ObjectMapper(factory) ;
      os.write(mapper.writeValueAsString(t).getBytes(StandardCharsets.UTF_8)) ;
    }
  }


}

测试

基于SpringBoot自定义接口响应消息格式及原理解析

正确的返回了yaml格式

文章版权声明

 1 原创文章作者:莫寻,如若转载,请注明出处: https://www.52hwl.com/30154.html

 2 温馨提示:软件侵权请联系469472785#qq.com(三天内删除相关链接)资源失效请留言反馈

 3 下载提示:如遇蓝奏云无法访问,请修改lanzous(把s修改成x)

 免责声明:本站为个人博客,所有软件信息均来自网络 修改版软件,加群广告提示为修改者自留,非本站信息,注意鉴别

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023年7月14日 上午12:00
下一篇 2023年7月15日