Spring boot -- 学习HttpMessageConverter

文章目录

  • 1. Json格式数据获取
  • 2. 为什么返回Json格式的数据
    • 2.1 注解SpringBootAppliaction
      • 2.1.1 SpringBootConfiguration
      • 2.1.2 ComponentScan
      • 2.1.3 EnableAutoConfiguration
        • 2.1.3.1 HttpMessageConvertersAutoConfiguration
        • 2.1.3.2 WebMvcAutoConfiguration
    • 2.2 注解RestController
      • 2.2.1 Controller
      • 2.2.2 ResponseBody
      • 2.3 HttpMessageConverter
      • 2.4 MappingJackson2HttpMessageConverter
      • 2.5 Request header
    • 2.3 总结
  • 3.如何返回xml格式的数据
  • 4. 如何返回自定义格式的数据
  • 5. 总结

1. Json格式数据获取

在Spring boot项目中引入spring-boot-starter-web场景启动器之后,就可以轻松方便的获得json格式的数据返回。
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.17</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>sj-1123</artifactId><version>0.0.1-SNAPSHOT</version><name>sj-1123</name><description>sj-1123</description><properties><java.version>11</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><image><builder>paketobuildpacks/builder-jammy-base:latest</builder></image></configuration></plugin></plugins></build></project>

实体类Person.java

public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return this.name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

Controller.java

@RestController
public class HelloController {@GetMapping("/hello")public ResponseEntity<Person> hello(String name, String age) {var person = new Person(name, 18);return ResponseEntity.ok(person);}
}

启动类

@SpringBootApplication
public class Sj1123Application {public static void main(String[] args) throws Exception {SpringApplication.run(Sj1123Application.class, args);}
}

这样启动后就可以通过访问localhost:8080/hello返回Json格式的结果
在这里插入图片描述
看到这里,会有下面的一些疑问:

  • 为什么返回Json格式的数据?
  • 如何返回xml格式的数据?
  • 如何返回自定义格式的数据?

2. 为什么返回Json格式的数据

对于上面的spring boot项目,最主要的两个注解是SpringBootApplication和RestController

2.1 注解SpringBootAppliaction

这个是springBoot项目当中,最最常见及基础的一个注解。它用来表明这个配置类来生命一个或者多个Bean,并且触发自动配置和Bean扫描。这其实是一个复合注解,相当于同时注解了@SpringBootConfiguration, @EnableAutoConfiguration 和 @ComponentScan。

2.1.1 SpringBootConfiguration

这就是一个配置类注解,表明这是一个Spring Boot应用,可以当作是Configuration注解的一个springBoot应用中的一个替代选择。

2.1.2 ComponentScan

这个是组件扫描注解,用来指定要扫描哪些位置下的类,这样可以让spring 容器来找到这些BeanDefinition并生成Bean。

2.1.3 EnableAutoConfiguration

这个注解是spring Boot项目能够进行自动装配的关键。启用 Spring Application Context 的自动配置,尝试猜测和配置您可能需要的 bean。自动配置类通常基于您的类路径和您定义的 Bean 来应用。
这个注解上有一个注解Import,这个也是用来配置Bean的一种方式,AutoConfigurationImportSelector.class 这个类可以看到将会选择哪些Bean需要自动装配进来。

	@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes = getAttributes(annotationMetadata);List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);configurations = removeDuplicates(configurations);Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}

深入阅读源码,可以发现,spring自动加载位于"META-INF/spring/%s.imports"里定义的自动配置类。org.springframework.boot.autoconfigure.AutoConfiguration.imports 这个文件里共有144个自动配置类,涵盖了常见的Redis,MongoDB, JDBC,es, Cassandra,web等等。
这里有两个需要关注一下:

  • org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration 这个是用来自动配置HttpMessageConverter的。
  • org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration Auto-configuration for Web MVC.
2.1.3.1 HttpMessageConvertersAutoConfiguration

这个配置类里,可以加载两个Bean:

@Bean@ConditionalOnMissingBeanpublic HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));}@Configuration(proxyBeanMethods = false)@ConditionalOnClass(StringHttpMessageConverter.class)protected static class StringHttpMessageConverterConfiguration {@Bean@ConditionalOnMissingBeanpublic StringHttpMessageConverter stringHttpMessageConverter(Environment environment) {Encoding encoding = Binder.get(environment).bindOrCreate("server.servlet.encoding", Encoding.class);StringHttpMessageConverter converter = new StringHttpMessageConverter(encoding.getCharset());converter.setWriteAcceptCharset(false);return converter;}}

一个是StringHttpMesageConverter类型的Bean,这个Bean加载的要求是当前类StringHttpMessageConverter.class存在,且Bean还没有,那就加载这个StringHttpMessageConverter的Bean。
另一个是HttpMessageConverters的Bean。

另外,这个配置类还会由Import注解导入另外三个配置类:

  • JacksonHttpMessageConvertersConfiguration.class
  • GsonHttpMessageConvertersConfiguration.class
  • JsonbHttpMessageConvertersConfiguration.class

Jackson的配置类里面,会加载MappingJackson2HttpMessageConverter,这个Converter就是用来转Json的一个重要转换类。由于没有引入Gson和Jsonb相关的包,也不会加载这两个配置类里的相关内容。

2.1.3.2 WebMvcAutoConfiguration

这个配置类,用来配置web mvc相关的内容,但是是要在WebMvcConfigurationSupport.class这个类型的Bean不存在,且Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class 这三个类存在,而且是servlet的web应用下,才会用到这个配置类。
但是WebMvcConfigurationSupport这个类型的Bean是什么时候在哪里指示加载的呢?

This is the main class providing the configuration behind the MVC Java config. It is typically imported by adding @EnableWebMvc to an application @Configuration class. An alternative more advanced option is to extend directly from this class and override methods as necessary, remembering to add @Configuration to the subclass and @Bean to overridden @Bean methods. For more details see the javadoc of @EnableWebMvc.

可以看到是在配置类上启用EnableWebMVC注解。或者是直接扩展这个类,然后加载Bean。

而在WebMvcAutoConfiguration.class里面,由这样一段代码

	/*** Configuration equivalent to {@code @EnableWebMvc}.*/@Configuration(proxyBeanMethods = false)@EnableConfigurationProperties(WebProperties.class)public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {private final Resources resourceProperties;private final WebMvcProperties mvcProperties;private final WebProperties webProperties;private final ListableBeanFactory beanFactory;private final WebMvcRegistrations mvcRegistrations;private ResourceLoader resourceLoader;public EnableWebMvcConfiguration(WebMvcProperties mvcProperties, WebProperties webProperties,ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,ListableBeanFactory beanFactory) {this.resourceProperties = webProperties.getResources();this.mvcProperties = mvcProperties;this.webProperties = webProperties;this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();this.beanFactory = beanFactory;}

这就相当于启用了EnableWebMVC注解,加载了WebMvcConfigurationSupport类型的Bean。这个类型的Bean,配置了非常多的web相关的内容:
在这里插入图片描述
配置messageConverter相关的内容

	/*** Provides access to the shared {@link HttpMessageConverter HttpMessageConverters}* used by the {@link RequestMappingHandlerAdapter} and the* {@link ExceptionHandlerExceptionResolver}.* <p>This method cannot be overridden; use {@link #configureMessageConverters} instead.* Also see {@link #addDefaultHttpMessageConverters} for adding default message converters.*/protected final List<HttpMessageConverter<?>> getMessageConverters() {if (this.messageConverters == null) {this.messageConverters = new ArrayList<>();configureMessageConverters(this.messageConverters);if (this.messageConverters.isEmpty()) {addDefaultHttpMessageConverters(this.messageConverters);}extendMessageConverters(this.messageConverters);}return this.messageConverters;}/*** Override this method to add custom {@link HttpMessageConverter HttpMessageConverters}* to use with the {@link RequestMappingHandlerAdapter} and the* {@link ExceptionHandlerExceptionResolver}.* <p>Adding converters to the list turns off the default converters that would* otherwise be registered by default. Also see {@link #addDefaultHttpMessageConverters}* for adding default message converters.* @param converters a list to add message converters to (initially an empty list)*/protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}/*** Override this method to extend or modify the list of converters after it has* been configured. This may be useful for example to allow default converters* to be registered and then insert a custom converter through this method.* @param converters the list of configured converters to extend* @since 4.1.3*/protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}/*** Adds a set of default HttpMessageConverter instances to the given list.* Subclasses can call this method from {@link #configureMessageConverters}.* @param messageConverters the list to add the default message converters to*/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()));}}

这一共三个方法:

  • 第一个是直接Config custom的MessageConverter,这个就意味着默认的MessageConverter将失效了,只能用自定义的。
  • 第二个是extend MessageConverter, 这个可以添加自己的MessageConverter的同时,保留原有的MessageConverter。
  • 第三个是直接使用默认的MessageConverter。默认的一共有哪些呢?这些由spring容器根据类是否出现在class path中来进行判断。
static {ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);}

这里可以看到默认加载的MessageConverter有:

  • ByteArrayHttpMessageConverter
  • StringHttpMessageConverter
  • ResourceHttpMessageConverter
  • ResourceRegionHttpMessageConverter
  • AllEncompassingFormHttpMessageConverter
  • MappingJackson2HttpMessageConverter
  • SourceHttpMessageConverter
    所有默认的MessageConverter已经就位了,那什么时候使用呢?这个时候就要看另一个注解RestController了。

2.2 注解RestController

这也是一个复合注解,由两个注解组成:
@Controller
@ResponseBody

2.2.1 Controller

这个是一个特别熟悉的注解,就是表示这是一个控制器controller,它和RequestMapping注解一起,处理请求。

2.2.2 ResponseBody

这个注解就是表明返回值直接绑定到response body中。当使用@ResponseBody注解时,Spring MVC会根据请求的Content-Type头和处理方法的返回类型来选择合适的HttpMessageConverter进行数据转换。HttpMessageConverter负责将Java对象转换为响应的数据格式,并将其写入HTTP响应中。

2.3 HttpMessageConverter

HttpMessageConverter负责将对象转化为对应的数据格式,那是如何做到的呢?

public interface HttpMessageConverter<T> {boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);List<MediaType> getSupportedMediaTypes();default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {return (canRead(clazz, null) || canWrite(clazz, null) ?getSupportedMediaTypes() : Collections.emptyList());}T read(Class<? extends T> clazz, HttpInputMessage inputMessage)throws IOException, HttpMessageNotReadableException;void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException;				}

五个方法,一个是看支持哪些MediaType,剩下4个事两组,一组是读,一组是写。在读写之前要check是都可以读、写。
简单的类图关系如下:
在这里插入图片描述
这里是一个标准的策略模式的实现。在处理类AbstractMessageConverterMethodProcessor.class中,依次使用MessageConverter来进行判断是否可以write,可以就将结果写入到响应体中。

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)) {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());}}}MediaType selectedMediaType = null;MediaType contentType = outputMessage.getHeaders().getContentType();boolean isContentTypePreset = contentType != null && contentType.isConcrete();if (isContentTypePreset) {if (logger.isDebugEnabled()) {logger.debug("Found 'Content-Type:" + contentType + "' in response");}selectedMediaType = contentType;}else {HttpServletRequest request = inputMessage.getServletRequest();List<MediaType> acceptableTypes;try {acceptableTypes = getAcceptableMediaTypes(request);}catch (HttpMediaTypeNotAcceptableException ex) {int series = outputMessage.getServletResponse().getStatus() / 100;if (body == null || series == 4 || series == 5) {if (logger.isDebugEnabled()) {logger.debug("Ignoring error response content (if any). " + ex);}return;}throw ex;}List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}List<MediaType> mediaTypesToUse = new ArrayList<>();for (MediaType requestedType : acceptableTypes) {for (MediaType producibleType : producibleTypes) {if (requestedType.isCompatibleWith(producibleType)) {mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));}}}if (mediaTypesToUse.isEmpty()) {if (logger.isDebugEnabled()) {logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);}if (body != null) {throw new HttpMediaTypeNotAcceptableException(producibleTypes);}return;}MediaType.sortBySpecificityAndQuality(mediaTypesToUse);//这里选择出要输出的MediaType类型,这个是由发起请求的Request header里面Accepte 决定的。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 (logger.isDebugEnabled()) {logger.debug("Using '" + selectedMediaType + "', given " +acceptableTypes + " and supported " + producibleTypes);}}//这里遍历spring加载的所有MessageConverter,依次遍历,如果是可以write,就调用这个MessageConverter的write方法,把结果写入response body中。写成什么样的格式,由当前这个MessageConverter的write方法决定。if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();for (HttpMessageConverter<?> converter : this.messageConverters) {GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter<?>) converter : 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);if (body != null) {Object theBody = body;LogFormatUtils.traceDebug(logger, traceOn ->"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");addContentDispositionHeader(inputMessage, outputMessage);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");}}return;}}}if (body != null) {Set<MediaType> producibleMediaTypes =(Set<MediaType>) inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");}throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));}}

2.4 MappingJackson2HttpMessageConverter

这个是spring 官方默认的Json格式MessageConverter。这是Jackson的工具,由ObjectMapper来写出writeValue。

2.5 Request header

我们在使用postman发送请求时,没有注意设置的请求头是什么,但是里面是有一些默认值的:
在这里插入图片描述
甚至我们使用curl指令的时候,也可以不写这些信息

curl --location 'localhost:10083/hello?name=zhangsan'

这是因为Accept的值为*/*和缺省是一样的,这个结果被默认为返回appliaction/json格式数据。

2.3 总结

至此,可以说算是明白为什么返回Json格式数据了:
我们给服务器说,接收*/*格式数据的response。
默认的HttpMessageConverter把JAVA对象转成了Json格式写入response body中。

3.如何返回xml格式的数据

这个时候,应该已经很明确了:

  • 1.你要告诉服务端,你需要xml格式的数据:设置Request header的Accept值为application/xml
    1. 配置好服务端的MessageConverter,让它将JAVA对象转成xml格式。
      Jackson已经可以支持返回xml格式数据了,只需要引入相应依赖即可。

在pom.xml文件中引入xml格式依赖

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

然后再entity类加上注解

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;@JacksonXmlRootElement
public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return this.name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

这个时候再发送请求:
在这里插入图片描述
可以看到,已经可以返回XML格式数据了。

4. 如何返回自定义格式的数据

返回自定义格式的数据,就是要和服务端约定好,是什么样的格式,然后有converter写出来即可。
从上面的类图可以看到,我们可以直接实现HttpMessageConverter接口,也可以继承类图里的抽象类也可以。
这里提供一个继承AbstractHttpMessageConverter的例子,实现返回yaml格式:

package com.example.sj1123.config;import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;public class YamlMessageConverter extends AbstractHttpMessageConverter<Object> {private ObjectMapper objectMapper = null;public YamlMessageConverter() {super(new MediaType("application", "yaml", StandardCharsets.UTF_8));YAMLFactory factory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);this.objectMapper = new ObjectMapper(factory);}@Overrideprotected boolean supports(Class<?> clazz) {return true;}@Overrideprotected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {return null;}@Overrideprotected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {try (OutputStream outputStream = outputMessage.getBody()) {this.objectMapper.writeValue(outputStream, o);}}
}

然后再配置这个MessageConverter作为Bean

@Configuration
public class AppConfig {@Beanpublic WebMvcConfigurer webMvcConfigurer() {return new WebMvcConfigurer() {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new YamlMessageConverter());}};}
}

这个时候,再启动应用,就可以返回yaml格式的数据了
在这里插入图片描述

5. 总结

HttpMessageConverter可以让读取参数、返回请求转换成指定格式。在服务端可以配置多中格式的MessageConverter。

另外,指定格式的方式不仅仅只有通过Request Header这个方式,也可以通过参数 type=yaml这种格式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/201677.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

石油化工园区:安全管理工作中的挑战与措施

石油化工园区&#xff1a;安全管理工作中的挑战与措施 石油化工园区是一种设备设施多且结构复杂的工业园区&#xff0c;涉及到易燃易爆或有毒介质&#xff0c;同时园区内有大量的工作人员和工作车辆等动态参与要素。由于园区的特殊性质&#xff0c;安全管理工作显然具备较高的挑…

农用烘干机市场分析:我国市场规模为190亿元

农用烘干机是运用在农业烘干领域的传统干燥设备之一&#xff0c;主要是为了农副产品直接使用或满足进一步加工的需要。农用烘干机具有操作弹性大、适应性强、处理能力大、设备运转可靠等优点&#xff0c;能大幅度提高农副产品烘干效率。现阶段&#xff0c;我国农用烘干机的市场…

AI助力智慧农业,基于YOLOv6最新版本模型开发构建不同参数量级农田场景下庄稼作物、杂草智能检测识别系统

智慧农业随着数字化信息化浪潮的演变有了新的定义&#xff0c;在前面的系列博文中&#xff0c;我们从一些现实世界里面的所见所想所感进行了很多对应的实践&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《自建数据集&#xff0c;基于YOLOv7开发构建农田场景下杂草…

element中el-table表头通过header-row-style设置样式

文章目录 一、知识点二、设置全部表头2.1、方式一2.2、方式二 三、设置某个表头四、最后 一、知识点 有些时候需要给element-ui表头设置不同样式&#xff0c;比如居中、背景色、字体大小等等&#xff0c;这时就可以用到本文要说的属性header-row-style。官网说明如下所示&…

通用plantuml 时序图(Sequence Diagram)模板头

通用plantuml文件 startuml participant Admin order 0 #87CEFA // 参与者、顺序、颜色 participant Student order 1 #87CEFA participant Teacher order 2 #87CEFA participant TestPlayer order 3 #87CEFA participant Class order 4 #87CEFA participant Subject order …

关于支持向量机(SVM)的QSAR的结果的分析

图中的灰色部分代表了预测pIC50值与实测pIC50值之间的95%置信区间&#xff08;confidence interval&#xff09;。这个区间提供了一个预测误差的范围&#xff0c;意味着在统计上&#xff0c;我们有95%的把握认为真实值会落在这个区间内。具体来说&#xff0c;这个置信区间围绕着…

23、pytest通过skip跳过测试用例

官方实例 # content of test_skip.py import pytest import syspytest.mark.skip(reason"no way of currently testing this") def test_the_unknown():passdef valid_config():return Falsedef test_function():if not valid_config():pytest.skip("unsupport…

技术博客:Vue中各种混淆用法汇总

技术博客&#xff1a;Vue中各种混淆用法汇总 摘要 本文主要介绍了在Vue中使用的一些常见混淆用法&#xff0c;包括new Vue()、export default {}、createApp()、Vue.component、Vue3注册全局组件、Vue.use()等&#xff0c;以及如何使用混淆器对代码进行加固&#xff0c;保护应…

Hadoop学习笔记(HDP)-Part.19 安装Kafka

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

分类信息发布小程序效果如何

信息发布系统连接信息供需双方&#xff0c;打造信息聚合平台&#xff0c;用户可获取和发布需求信息、参与互动交流&#xff0c;适用于同城、社区交流、客户互动、业务员/经纪人发布信息场景。 制作分类信息小程序后&#xff0c;商家后台设置信息项&#xff0c;发布者填写内容发…

腾讯云轻量应用服务器挂载对象存储详细说明

腾讯云轻量对象存储LighthouseCOS是腾讯云专为中小企业开发者打造的轻量级数据存储服务&#xff0c;适用于云端网站、小程序、课堂演示、云盘/图床等场景下的数据存储和处理任务。腾讯云百科txybk.com详细介绍腾讯云轻量对象存储使用、开通和收费价格说明&#xff1a; 轻量对象…

配置CentOS服务器以支持PHP

CentOS是一款优秀的开源服务器操作系统&#xff0c;为各种网络服务提供了强大的支持。为了使CentOS服务器能够支持PHP&#xff0c;我们需要进行一些必要的配置。下面将介绍配置CentOS服务器以支持PHP的关键步骤。 安装PHP 首先&#xff0c;需要安装PHP解释器。在CentOS上&…

【python】使用pipenv创建虚拟环境进行打包

文章目录 一、pipenv 介绍二、快速上手使用pipenv2.1 安装pipenv2.2 创建虚拟环境2.3 激活环境2.4 虚拟环境中安装项目依赖包2.5 检查项目在虚拟环境中是否能正常运行2.6 打包项目2.7 删除虚拟环境 起因: 本地安装的模块太多,使用pyinstaller打包,会把许多无关模块打包进去&…

【头歌系统数据库实验】实验5 SQL的多表查询-1

目录 第1关&#xff1a;等值连接&#xff1a;求S表和J表城市相同的等值连接(列顺序还是按照S、J表) 第2关&#xff1a;查询供应情况&#xff0c;并显示供应商、零件和工程三者的名称 第3关&#xff1a;找出上海厂商供应的所有零件号码 第4关&#xff1a;找出使用上海产的零…

关于inline函数声明和定义为什么不可以分离

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 前言 inline函数在使用时声明和定义不可以分离&#xff0c;接下来我们将会解释为什么是这样的。 我们从程序的编译链接说起。 首先我们先介绍一下程序的翻译环境&#xff1a; 编译分以下几个步骤&#xff0c;预处理&#x…

Hadoop学习笔记(HDP)-Part.08 部署Ambari集群

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

R语言【rgbif】——最全最详细的函数解读(occ_search)

occ_search最全最详细的参数解读 occ_search有什么作用&#xff1f;occ_search有哪些参数&#xff1f;occ_search的参数使用&#xff1f;aa. 通过名称完成筛选的参数1. taxonKey&#xff08;用于检索的类群的标识符&#xff09;2. scientificName&#xff08;用于检索的类群的科…

U盘文件突然不见了怎么恢复?文件恢复(3个方法)!

“好奇怪呀&#xff01;一般我都会把比较重要的文件保存在u盘中&#xff0c;但是今天使用u盘的时候却发现u盘里的文件突然不见了&#xff0c;这是为什么呢&#xff1f;我怎么才能恢复这些数据呢&#xff1f;” 在我们的日常生活和工作中&#xff0c;U盘作为一种便携式的存储设备…

ChatGPT哪些行业需要学习?

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

CSS进阶知识点速览2

1 前情回顾 关于选择器进阶、背景色、元素显示模式和css特性的前半部分集中在下面的笔记中&#xff1a; css进阶知识点速览 2 CSS特性 2.1 优先级 特性&#xff1a;不同选择器具有不同的优先级&#xff0c;优先级高的选择器样式会覆盖优先级低选择器样式 优先级公式&#x…