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.rhkb.cn/news/216115.html

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

相关文章

独立完成软件的功能的测试(2)

独立完成软件的功能的测试&#xff08;2&#xff09; &#xff08;12.13&#xff09; 1. 对穷举场景设计测试点&#xff08;等价类划分法&#xff09; 等价类划分法的概念&#xff1a; 说明&#xff1a;数据有共同特征&#xff0c;成功失败分类&#xff1a; 有效&#xff1a…

基于Python+WaveNet+MFCC+Tensorflow智能方言分类—深度学习算法应用(含全部工程源码)(二)

目录 前言引言总体设计系统整体结构图系统流程图 运行环境模块实现1. 数据预处理1&#xff09;数据介绍2&#xff09;数据测试3&#xff09;数据处理 相关其它博客工程源代码下载其它资料下载 前言 博主前段时间发布了一篇有关方言识别和分类模型训练的博客&#xff0c;在读者…

Python和Beautiful Soup爬虫助力提取文本内容

大家好&#xff0c;网络爬虫是一项非常抢手的技能&#xff0c;收集、分析和清洗数据是数据科学项目中最重要的部分。今天介绍如何从链接中爬取高质量文本内容&#xff0c;我们使用迭代&#xff0c;从大约700个链接中进行网络爬取。如果想直接跳转到代码部分&#xff0c;可以在下…

【JUC】二十六、Java对象内存布局和对象头

文章目录 0、前置1、对象的内存布局2、对象头之对象标记Mark Word3、对象头之类元信息4、实例数据5、对齐填充6、对象内存布局之JOL证明7、对象分代年龄8、压缩指针 0、前置 heap&#xff08;堆区&#xff09;&#xff0c;分为新生区new、养老区old、元空间Metaspace&#xff…

C语言—每日选择题—Day46

第一题 1. 下列程序段的输出结果是&#xff08;&#xff09; #include <stdio.h> int main() {int x 1,a 0,b 0;switch(x) {case 0: b;case 1: a;case 2: a;b;}printf("a%d,b%d\n", a, b);return 0; } A&#xff1a;a2,b1 B&#xff1a;a1,b1 C&#xf…

探秘机器学习核心逻辑:梯度下降的迭代过程 (图文详解)

一 需求解函数 f() 和 g()函数分别为求y值和求导数的函数。 目的&#xff1a;求该函数的最小值&#xff1a; 代码&#xff1a; import numpy as np import matplotlib.pyplot as plt f lambda x : (x - 3.5) ** 2 - 4.5 * x 10 g lambda x : 2 * (x - 3.5) - 4.5x np.l…

接口管理——Swagger

Swagger是一个用于设计、构建和文档化API的工具集。它包括一系列工具&#xff0c;如Swagger Editor&#xff08;用于编辑Swagger规范&#xff09;、Swagger UI&#xff08;用于可视化API文档&#xff09;和Swagger Codegen&#xff08;用于根据API定义生成客户端库、server stu…

SpringCloud系列(二)| Nacos的安装与配置

Nacos是阿里巴巴提供的一个开源的可作为注册中心和配置中心的SpringCloud组件。 Nacos/nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称;一个更易于构 建云原生应用的动态服务发现、配置管理和服务管理平台。 简单来说Nacos有两个核心功能&#xff0c…

深度学习中的各类评价指标

深度学习中的各类评价指标 1 Dice Loss2 Precision&#xff08;精度&#xff09;3 Recall&#xff08;召回率&#xff09;4 F-Score5 mAP 1 Dice Loss Dice Loss&#xff0c;也叫Soft Dice Coefficient&#xff0c;是一种用于图像分割任务的损失函数。它基于目标分割图像与模型…

Uniapp项目打包到多个平台...

打包到微信小程序 先设置微信开发者工具的路径 运行到小程序模拟器&#xff0c;会自动打开微信开发者工具&#xff08;需要先在微信开发者工具->设置->安全设置->服务端口切换为打开状态&#xff09; 3. 微信开发者工具上传版本&#xff08;提示覆盖版本就可以了&a…

“百里挑一”AI原生应用亮相,百度智能云千帆AI加速器首个Demo Day来了!

作者简介&#xff1a; 辭七七&#xff0c;目前大二&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

用户管理第2节课 -- idea 2023.2 创建表

一、懂得 1.1编码格式是防止乱码的&#xff0c;utf-8是完全够的&#xff0c;那几个基本没差别 网址&#xff1a; 【IDEA——连接MySQL数据库&#xff0c;创建库和表】_idea中数据库-CSDN博客 这些是MySQL数据库中的一些术语&#xff0c;可以简单解释如下&#xff1a; 1、col…

第三十四周:文献阅读+LSTM学习

目录 摘要 Abstract 文献阅读&#xff1a;综合EMD-LSTM模型在城市排水管网水质预测中的应用 现有问题 提出方法 EMD-LSTM综合模型 研究框架 结论 Long Short-term Memory(长短期记忆) 1. LSTM的结构 2. Multiple-layer LSTM 3.3 LSTM Example 3. GRU LSTM实现PM2…

Java+SSM+MySQL基于微信的在线协同办公小程序(附源码 调试 文档)

基于微信的在线协同办公小程序 一、引言二、系统设计三、技术架构四、管理员功能设计五、员工功能设计六、系统实现七、界面展示八、源码获取 一、引言 随着科技的飞速发展&#xff0c;移动互联网已经深入到我们生活的各个角落。在这个信息时代&#xff0c;微信作为全球最大的…

靠谱的车- 华为OD统一考试(C卷)

靠谱的车- 华为OD统一考试&#xff08;C卷&#xff09; OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 程序员小明打了一辆出租车去上班。出于职业敏感&#xff0c;他注意到这辆出租车的计费表有点问题&#xf…

【知识】如何区分图论中的点分割和边分割

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 以下两个概念在现有中文博客下非常容易混淆&#xff1a; edge-cut(边切割) vertex-partition(点分割)vertex-cut(点切割) edge-partition(边分割) 实际上&#xff0c;初看中文时&#xff0c;真的会搞不清楚。但…

黑豹程序员-EasyExcel实现导出

需求 将业务数据导出到excel中&#xff0c;老牌的可以选择POI&#xff0c;也有个新的选择EasyExcel。 有个小坑&#xff0c;客户要求样式比较美观&#xff0c;数字列要求千位符&#xff0c;保留2位小数。 可以用代码实现但非常繁琐&#xff0c;用模板就特别方便&#xff0c;模…

Pyhon基于YOLOV实现的车辆品牌及型号检测项目源码+模型+项目文档

项目运行运行录屏&#xff1a; Pyhon基于YOLOV实现的车辆品牌及型号检测项目运行录屏 完整代码下载地址&#xff1a;Pyhon基于YOLOV实现的车辆品牌及型号检测项目 项目背景&#xff1a; 车辆检测及型号识别广泛应用于物业&#xff0c;交通等的管理场景中。通过在停车场出入口…

Docker容器:Centos7搭建Docker镜像私服harbor

目录 1、安装docker 1.1、前置条件 1.2、查看当前操作系统的内核版本 1.3、卸载旧版本(可选) 1.4、安装需要的软件包 1.5、设置yum安装源 1.6、查看docker可用版本 1.7、安装docker 1.8、开启docker服务 1.9、安装阿里云镜像加速器 1.10、设置docker开机自启 2、安…

uni-app 设置tabBar的setTabBarBadge购物车/消息等角标

目录 一、效果二、代码实现二、全部代码1.index.vue2.cart.vue 三、真实案例参考最后 一、效果 二、代码实现 只要使用uni.setTabBarBadge和uni.removeTabBarBadge来进行对红点的设置和移除。 主要代码&#xff1a; //设置红点 uni.setTabBarBadge({index: 1, // 底部菜单栏…