SpringBoot 支持 application/xml 及 406 异常分析
Posted 毕小宝
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot 支持 application/xml 及 406 异常分析相关的知识,希望对你有一定的参考价值。
背景
在 Spring 框架中,@ResponseBody
注解响应的对象会利用 HttpMessageConverter
类,根据 produces
属性指定的类型,找到对应的数据转换器完成转换。
最常见也是默认的响应类型是 application/json
,还有很多其他的响应数据类型,都封装在 org.springframework.http.MediaType
中了,本文将介绍 application/xml 类型的响应支持及其引发的思考。
支持 application/xml
定义一个请求方法,设置响应类型为 XML:
@ResponseBody
@RequestMapping(value= "/hello/json")
public MyData hello() {
return new MyData("13020045218","10");
}
@ResponseBody
@RequestMapping(value= "/hello/xml", produces = MediaType.APPLICATION_XML_VALUE)
public MyData hello1() {
return new MyData("13020045218","10");
}
关键配置
依赖引入
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.0</version>
</dependency>
如果不添加上述依赖,而又在 Controller 的方法上指定了 XML 类型,服务端会抛出 406 异常:
异常类型为:
org.springframework.web.HttpMediaTypeNotAcceptableException:
Could not find acceptable representation
加入依赖后,页面返回 XML 数据:
延伸思考两个知识点
框架用久了,难免会忘记它底层的封装过程,像本文这个简单的练习,源于 SpringBoot 中重新设置 MappingJackson2HttpMessageConverter
以解决特殊转换问题,同时针对 XML 类型中的特殊转换,需要重新设置 MappingJackson2XMLHttpMessageConverter
。
思考两个问题:
- 406 是什么原因导致的?
- 这个引用包中是怎么让 XML 转换器自动加入到 Spring 环境中的?
第一个问题容易猜测:请求指定响应数据格式为 application/xml
后,系统在返回时会调用org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue
,这个里面会使用 MessageConverter
,查找 XML 类型的转换器。出现 406 错误,说明没有找到该转换器。
第二个问题,SpringBoot 自动装配 Jackson 转换器是通过 JacksonHttpMessageConvertersConfiguration
类完成的,它会注入 JSON 转换器 MappingJackson2HttpMessageConverter
和 XML 的转换器 ,它的源码如下:
@org.springframework.context.annotation.Configuration
class JacksonHttpMessageConvertersConfiguration {
JacksonHttpMessageConvertersConfiguration() { /* compiled code */ }
@org.springframework.context.annotation.Configuration
@org.springframework.boot.autoconfigure.condition.ConditionalOnClass({com.fasterxml.jackson.dataformat.xml.XmlMapper.class})
@org.springframework.boot.autoconfigure.condition.ConditionalOnBean({org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.class})
protected static class MappingJackson2XmlHttpMessageConverterConfiguration {
protected MappingJackson2XmlHttpMessageConverterConfiguration() { /* compiled code */ }
@org.springframework.context.annotation.Bean
@org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
public org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter(org.springframework.http.converter.json.Jackson2ObjectMapperBuilder builder) { /* compiled code */ }
}
@org.springframework.context.annotation.Configuration
@org.springframework.boot.autoconfigure.condition.ConditionalOnClass({com.fasterxml.jackson.databind.ObjectMapper.class})
@org.springframework.boot.autoconfigure.condition.ConditionalOnBean({com.fasterxml.jackson.databind.ObjectMapper.class})
@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(name = {"spring.http.converters.preferred-json-mapper"}, havingValue = "jackson", matchIfMissing = true)
protected static class MappingJackson2HttpMessageConverterConfiguration {
protected MappingJackson2HttpMessageConverterConfiguration() { /* compiled code */ }
@org.springframework.context.annotation.Bean
@org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean(value = {org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class}, ignoredType = {"org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter"})
public org.springframework.http.converter.json.MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(com.fasterxml.jackson.databind.ObjectMapper objectMapper) { /* compiled code */ }
}
}
注意第一部分, XML 转换器的自动注入条件是:@org.springframework.boot.autoconfigure.condition.ConditionalOnClass({com.fasterxml.jackson.dataformat.xml.XmlMapper.class})
这就是引入依赖自动触发注入的根源了。
这个条件注入又引发了第三个问题,不引入 jar 包,这个配置就存在编译错误,怎么解决这个编译错误的呢?
搜索到一篇《注解@ConditionalOnClass(X.class),X不存在时的探究》,原理类似。
猜测同理, spring-boot 引用了这个包,但是 optional=true
,所以依赖不会传递到我们项目中,如果不引入相关 jar 又想用 @ConditionOnClass
的话,可以使用 name 属性:
@ConditionalOnClass(name = "com.fasterxml.jackson.dataformat.xml.XmlMapper")
启示录
以上就是 Jackson 转换器引发的思考:
- 如何更改
MappingJackson2HttpMessageConverter
默认配置? @ConditionalOnClass
未引入对应 jar 包时,编译错误是怎么回事?- SpringBoot 是如何自动装配 Jackson 的两个转换器的?
看完本文,你明白怎么回事儿了吗?
以上是关于SpringBoot 支持 application/xml 及 406 异常分析的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot 支持 application/xml 及 406 异常分析
SpringBoot application.yml logback.xml,多环境配置,支持 java -jar --spring.profiles.active
SpringBoot application.yml logback.xml,多环境配置,支持 java -jar --spring.profiles.active