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需要自动装配进来。
@Override
public 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
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(StringHttpMessageConverter.class)
protected static class StringHttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean
public 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
-
- 配置好服务端的MessageConverter,让它将JAVA对象转成xml格式。
Jackson已经可以支持返回xml格式数据了,只需要引入相应依赖即可。
- 配置好服务端的MessageConverter,让它将JAVA对象转成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);
}
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
protected 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 {
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new YamlMessageConverter());
}
};
}
}
这个时候,再启动应用,就可以返回yaml格式的数据了
5. 总结
HttpMessageConverter可以让读取参数、返回请求转换成指定格式。在服务端可以配置多中格式的MessageConverter。
另外,指定格式的方式不仅仅只有通过Request Header这个方式,也可以通过参数 type=yaml这种格式。