spring.jackson.default-property-inclusion 不生效问题分析
背景
项目里每个返回体里都有@JsonInclude(JsonInclude.Include.NON_NULL) 这个注解,也就是不返回null字段
想有没有办法全局配置一下,这样就不用每个类都加这个注解了
spring:
jackson:
default-property-inclusion: non_null
看网上说加上这个配置项就可以了,于是加上之后,发现不生效,后来又查到不生效的原因是因为项目里手动注入了WebMvcConfigurationSupport这个类
看我们的项目里确实是这样的,配置如下:
@Configuration
@Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class)
public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations {
}
导入了EnableWebMvcConfiguration这个类,这个类继承了DelegatingWebMvcConfiguration, DelegatingWebMvcConfiguration这个类实现了WebMvcConfigurationSupport
那就一起来看下不生效的原因以及解决办法
例子
web配置
@Configuration
@Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class)
public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations {
}
controller
@RestController
@RequestMapping("/api/jackson")
public class JacksonTestController {
@GetMapping("/data")
public Object getData() {
return new Result();
}
@Data
public static class Result {
private String string1 = "hello";
private String string2;
}
}
application.yml
spring:
jackson:
default-property-inclusion: non_null
启动类
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
访问 http://127.0.0.1:8080/api/jackson/data
返回
{
"string1": "hello",
"string2": null
}
可以看出确实是不生效的
先看一下比较明显的方案:
方法一
加JsonInclude注解
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Result {
private String string1 = "hello";
private String string2;
}
访问返回:
{
"string1": "hello"
}
方法二
注释掉Import那一行
@Configuration
//@Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class)
public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations {
}
@Data
//@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Result {
private String string1 = "hello";
private String string2;
}
访问返回:
{
"string1": "hello"
}
原因
- body是怎么返回的?

public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
implements HandlerMethodReturnValueHandler {
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
}
}
body是返回结果,MappingJackson2HttpMessageConverter会去转换结果,它是从this.messageConverters获取
分别看一下不去掉以及去掉Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class)注解后 messageConverters 的结果
不去掉的结果


去掉的结果


可以看到,去掉之后,多了一个MappingJackson2HttpMessageConverter实例,并且这个是根据配置生成的,而且因为它的顺序在前面,所以会用这个来处理
- 来看一下 this.messageConverters 是怎么被赋值的
RequestResponseBodyMethodProcessor这个类是通过RequestMappingHandlerAdapter生成的
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); // this
return handlers;
}
public List<HttpMessageConverter<?>> getMessageConverters() {
return this.messageConverters; // this
}
}
RequestMappingHandlerAdapter是由WebMvcConfigurationSupport生成的
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setMessageConverters(getMessageConverters());
}
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
configureMessageConverters(this.messageConverters); // this
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters); // this
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
if (jackson2Present) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build())); // this
}
else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
}
else if (jsonbPresent) {
messageConverters.add(new JsonbHttpMessageConverter());
}
}
}
可以看到先调用configureMessageConverters方法,如果为空就调用addDefaultHttpMessageConverters方法添加默认的。
看一下configureMessageConverters方法
class WebMvcConfigurerComposite implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureMessageConverters(converters);
}
}
}
不去掉的结果

去掉的结果

可以看到,不去掉的话,这里只有一个我们自己定义的WebMvcConfigurer,而且是不会做任何操作的,所以就会调用addDefaultHttpMessageConverters,生成默认的。
如果去掉的话,会调用WebMvcAutoConfigurationAdapter的configureMessageConverters方法,这个方法的返回结果不会空,就不会调用addDefaultHttpMessageConverters,然后直接返回。
下面看一下这个方法。
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.messageConvertersProvider = messageConvertersProvider; // this
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider
.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters())); // this
}
}
会注入HttpMessageConverters
@Configuration
@ConditionalOnClass(HttpMessageConverter.class)
@AutoConfigureAfter({ GsonAutoConfiguration.class, JacksonAutoConfiguration.class,
JsonbAutoConfiguration.class })
@Import({ JacksonHttpMessageConvertersConfiguration.class,
GsonHttpMessageConvertersConfiguration.class,
JsonbHttpMessageConvertersConfiguration.class })
public class HttpMessageConvertersAutoConfiguration {
public HttpMessageConvertersAutoConfiguration(
ObjectProvider<HttpMessageConverter<?>> convertersProvider) {
this.converters = convertersProvider.orderedStream().collect(Collectors.toList());
}
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters() {
return new HttpMessageConverters(this.converters);
}
public HttpMessageConverters(Collection<HttpMessageConverter<?>> additionalConverters) {
this(true, additionalConverters);
}
public HttpMessageConverters(boolean addDefaultConverters,
Collection<HttpMessageConverter<?>> converters) {
List<HttpMessageConverter<?>> combined = getCombinedConverters(converters,
addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
combined = postProcessConverters(combined);
this.converters = Collections.unmodifiableList(combined);
}
private List<HttpMessageConverter<?>> getDefaultConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation."
+ "WebMvcConfigurationSupport", null)) {
converters.addAll(new WebMvcConfigurationSupport() {
public List<HttpMessageConverter<?>> defaultMessageConverters() {
return super.getMessageConverters(); // this
}
}.defaultMessageConverters());
}
else {
converters.addAll(new RestTemplate().getMessageConverters());
}
reorderXmlConvertersToEnd(converters);
return converters;
}
private List<HttpMessageConverter<?>> getCombinedConverters(
Collection<HttpMessageConverter<?>> converters,
List<HttpMessageConverter<?>> defaultConverters) {
List<HttpMessageConverter<?>> combined = new ArrayList<>();
List<HttpMessageConverter<?>> processing = new ArrayList<>(converters);
for (HttpMessageConverter<?> defaultConverter : defaultConverters) {
Iterator<HttpMessageConverter<?>> iterator = processing.iterator();
while (iterator.hasNext()) {
HttpMessageConverter<?> candidate = iterator.next();
if (isReplacement(defaultConverter, candidate)) {
combined.add(candidate);
iterator.remove();
}
}
combined.add(defaultConverter);
if (defaultConverter instanceof AllEncompassingFormHttpMessageConverter) {
configurePartConverters(
(AllEncompassingFormHttpMessageConverter) defaultConverter,
converters);
}
}
combined.addAll(0, processing);
return combined;
}
}
可以看到converters由两部分组成,一部分是注入的HttpMessageConverter,另一部分是WebMvcConfigurationSupport.defaultMessageConverters方法,然后进行合并,当然如果有相同的类,前面的顺序在前。
会注入MappingJackson2HttpMessageConverter
@Configuration
class JacksonHttpMessageConvertersConfiguration {
@Configuration
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(
name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
havingValue = "jackson", matchIfMissing = true)
protected static class MappingJackson2HttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class,
ignoredType = {
"org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter",
"org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" })
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(
ObjectMapper objectMapper) {
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
}
会注入ObjectMapper相关的实例
@Configuration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
// 注入jacksonObjectMapper
@Configuration
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
}
// 注入jacksonObjectMapperBuilder
@Configuration
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperBuilderConfiguration {
private final ApplicationContext applicationContext;
JacksonObjectMapperBuilderConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
@ConditionalOnMissingBean
public Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.applicationContext(this.applicationContext);
customize(builder, customizers);
return builder;
}
private void customize(Jackson2ObjectMapperBuilder builder,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
customizer.customize(builder);
}
}
}
// 注入standardJacksonObjectMapperBuilderCustomizer
@Configuration
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
@EnableConfigurationProperties(JacksonProperties.class)
static class Jackson2ObjectMapperBuilderCustomizerConfiguration {
@Bean
StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer(
ApplicationContext applicationContext, JacksonProperties jacksonProperties) {
return new StandardJackson2ObjectMapperBuilderCustomizer(applicationContext, jacksonProperties);
}
static final class StandardJackson2ObjectMapperBuilderCustomizer
implements Jackson2ObjectMapperBuilderCustomizer, Ordered {
private final JacksonProperties jacksonProperties;
@Override
public void customize(Jackson2ObjectMapperBuilder builder) {
if (this.jacksonProperties.getDefaultPropertyInclusion() != null) {
builder.serializationInclusion(this.jacksonProperties.getDefaultPropertyInclusion());
}
}
}
}
}
// 会注入JacksonProperties
@ConfigurationProperties(prefix = "spring.jackson")
public class JacksonProperties {
private JsonInclude.Include defaultPropertyInclusion;
}
这个过程就生成了一个新的根据配置文件配置的MappingJackson2HttpMessageConverter,并把它添加到messageConverters中
总结
最后还有一种不去掉import注解就可以解决的方法,就是采用和WebMvcAutoConfigurationAdapter一样的方法,如下:
@Configuration
@Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class)
public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations {
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
public WebConfig(ObjectProvider<HttpMessageConverters> messageConvertersProvider) {
this.messageConvertersProvider = messageConvertersProvider;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider.ifAvailable((customConverters) -> converters
.addAll(customConverters.getConverters()));
}
}
参考
Spring Boot 中自定义 SpringMVC 配置,到底继承谁?
自定义SpringBoot默认MVC配置?好几个坑,这篇文章必须珍藏
spring.jackson.default-property-inclusion 不生效问题分析的更多相关文章
- springboot 2.0 配置 spring.jackson.date-format 不生效
展开 问题:application.properties中的如下配置不生效,返回时间戳 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss 原因分析: 拦截器 ...
- spring boot定制Jackson ObjectMapper,为什么不生效
先说结论: 项目中定制了spring 的redisTemplate,而这个template没有使用我自定义的Jackson ObjectMapper.所以不生效. 下面是详细过程: 起因是spring ...
- spring中注解式事务不生效的问题
常用的解决方法可以百度,我针对我的问题描述一下 Mysql中InnoDB引擎才支持事务, MyISAM不支持事务. 当你尝试了各种方法解决spring中注解式事务不生效时, 一定要查看一下数据库中表的 ...
- QML的默认属性default property
qml中,普通的属性,需要添加属性名称,属性内容,如 color: “red” 默认属性则可以直接书写,去掉方括号,在写重用的QML组件式比较有用,例如将一个QmL外部资源封装好,内部具体的item, ...
- How to: Initialize Business Objects with Default Property Values in XPO 如何:在 XPO 中用默认属性值初始化业务对象
When designing business classes, a common task is to ensure that a newly created business object is ...
- How to: Initialize Business Objects with Default Property Values in Entity Framework 如何:在EF中用默认属性值初始化业务对象
When designing business classes, a common task is to ensure that a newly created business object is ...
- Spring.之.报错:Caused by: java.lang.IllegalArgumentException: No Spring Session store is configured: set the 'spring.session.store-type' property
Spring.之.报错 No Spring Session store is configured springboot在启动的时候报如下错误: Error starting ApplicationC ...
- 涨姿势:Spring Boot 2.x 启动全过程源码分析
目录 SpringApplication 实例 run 方法运行过程 总结 上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入 ...
- Spring Boot 2.x 启动全过程源码分析
Spring Boot 2.x 启动全过程源码分析 SpringApplication 实例 run 方法运行过程 上面分析了 SpringApplication 实例对象构造方法初始化过程,下面继续 ...
- Spring整合Shiro做权限控制模块详细案例分析
1.引入Shiro的Maven依赖 <!-- Spring 整合Shiro需要的依赖 --> <dependency> <groupId>org.apache.sh ...
随机推荐
- SpringBoot简单快速入门操作
项目类分为: dao层 server层 controller层 Mapper → Server→ controller mapper层(必须要用interface创建) 创建后,首先要在方法前加@Ma ...
- k8s 中的 ingress 使用细节
k8s中的ingress 什么是ingress Ingress 如何使用 ingress 使用细节 参考 k8s中的ingress 什么是ingress k8s 中使用 Service 为相同业务的 ...
- 关于图计算&图学习的基础知识概览:前置知识点学习(Paddle Graph Learning (PGL))
关于图计算&图学习的基础知识概览:前置知识点学习(Paddle Graph Learning (PGL)) 欢迎fork本项目原始链接:关于图计算&图学习的基础知识概览:前置知识点学习 ...
- Flask框架:运用Ajax轮询动态绘图
Ajax是异步JavaScript和XML可用于前后端交互,在之前<Flask 框架:运用Ajax实现数据交互>简单实现了前后端交互,本章将通过Ajax轮询获取后端的数据,前台使用echa ...
- C语言指针重点
指针 指针与一维数组 万能公式 p[i] = *(p+i) = (i+p) = i[p] &p[i] == &((p+i))== p+i 指针与二维数组 二维数组万能公式: ((p+i ...
- sqlserver数据库还原
这里是从A服务器备份的数据库文件,还原到本地B电脑的数据库.因数据库版本相同,故而未曾出现其他版本问题 1.有个bak的备份文件 2.在B电脑新建一个数据库test. 3.设置test数据库的访问限制 ...
- VS使用web deploy发布到远程服务器
如果是先安装 web deploy后安装iis的功能,需要在iis功能安装好后,修复下web deploy(直接运行web deploy的安装程序有修复)(本人也死在这里) 1.iis开启管理服务,和 ...
- K8S kubesphere安装mysql
原先我们使用Docker按照mysql时候的命令 docker run -p 3306:3306 --name mysql-01 \ -v /mydata/mysql/log:/var/log/mys ...
- 如何利用C++使Windows蓝屏
如何利用C++使Windows蓝屏 虽说windows非常强大,但是使它蓝屏也非常简单: 如果你想让Windows蓝屏,你一定会在运行框里输入: cmd /c for /f %I in ('wmic ...
- Java Web中requset,session,application 的作用域及区别
三者概述 requset概述: request是表示一个请求,只要发出一个请求就会创建一个request 用处:常用于服务器间同一请求不同页面之间的参数传递,常应用于表单的控件值传递. session ...