背景

项目里每个返回体里都有@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"
}

原因

  1. 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实例,并且这个是根据配置生成的,而且因为它的顺序在前面,所以会用这个来处理

  1. 来看一下 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 不生效问题分析的更多相关文章

  1. springboot 2.0 配置 spring.jackson.date-format 不生效

    展开 问题:application.properties中的如下配置不生效,返回时间戳 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss 原因分析: 拦截器 ...

  2. spring boot定制Jackson ObjectMapper,为什么不生效

    先说结论: 项目中定制了spring 的redisTemplate,而这个template没有使用我自定义的Jackson ObjectMapper.所以不生效. 下面是详细过程: 起因是spring ...

  3. spring中注解式事务不生效的问题

    常用的解决方法可以百度,我针对我的问题描述一下 Mysql中InnoDB引擎才支持事务, MyISAM不支持事务. 当你尝试了各种方法解决spring中注解式事务不生效时, 一定要查看一下数据库中表的 ...

  4. QML的默认属性default property

    qml中,普通的属性,需要添加属性名称,属性内容,如 color: “red” 默认属性则可以直接书写,去掉方括号,在写重用的QML组件式比较有用,例如将一个QmL外部资源封装好,内部具体的item, ...

  5. 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 ...

  6. 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 ...

  7. 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 ...

  8. 涨姿势:Spring Boot 2.x 启动全过程源码分析

    目录 SpringApplication 实例 run 方法运行过程 总结 上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入 ...

  9. Spring Boot 2.x 启动全过程源码分析

    Spring Boot 2.x 启动全过程源码分析 SpringApplication 实例 run 方法运行过程 上面分析了 SpringApplication 实例对象构造方法初始化过程,下面继续 ...

  10. Spring整合Shiro做权限控制模块详细案例分析

    1.引入Shiro的Maven依赖 <!-- Spring 整合Shiro需要的依赖 --> <dependency> <groupId>org.apache.sh ...

随机推荐

  1. or、and表达式

    or 逻辑表达式 result = a or b 如果a为空则执行or后面的b,如果a不为空,则执行or前面的a 即:赋值运算中,如果or前面为真,就不会去执行or后面的,如果or前面为假才会执行or ...

  2. C#中winform DataGridView常用修改点

    1.修改列名 一般情况下,从数据库里面读取的列名是英文或者拼音,但是,有时候显示需要中文,这样就需要修改列名了. dgv.Columns[0].HeaderCell.Value="编号&qu ...

  3. Nginx负载均衡策略的介绍与调优

    工作中经常会用到nginx负载均衡这一块,下面对nginx负载均衡策略做个总结.本人在工作中最常用到的负载均衡策略是轮询策略. 在一般情况下,Web中间件最大的作用就是负责对请求进行分发,也就是我们常 ...

  4. java安全之CC1浅学(2)

    前言 上一篇了解了commons-collections中的Transformer,并且构造了一个简单的payload,接下来就需要将其改造为一个可利用的POC AnnotationInvocatio ...

  5. 对比entrypoint以及cmd

    如何查看 现有 images 的 入口信息 docker image inspect image_id { "entrypoint": "xxx" " ...

  6. 5、有一行电文,译码规律为: a ——> z b——> y c ——> x. 即把第一个字母变成第26个字母, 第i个字母变成第(26-i+1)个字母, 非字母字符不变

    /* 有一行电文,译码规律为: a --> z b--> y c --> x. 即把第一个字母变成第26个字母, 第i个字母变成第(26-i+1)个字母, 非字母字符不变 */ #i ...

  7. centos7 ftp服务搭建记录

    1. 装包与卸载 yum -y install vsftpd yum -y autoremove vsftpd&&rm -rf /etc/vsftpd /etc/pam.d/vsftp ...

  8. Centos7.6分区、格式化、自动挂载磁盘

    个人名片: 对人间的热爱与歌颂,可抵岁月冗长 Github‍:念舒_C.ying CSDN主页️:念舒_C.ying 个人博客 :念舒_C.ying 目录 1. 添加硬盘 2. 执行fdisk -l ...

  9. Swing常用窗体

    Swing常用窗体 Swing 主要具有以下特点: ( 1 )轻量级组件. ( 2 )可插入外观组件. 窗体作为Swing的应用程序中组件的承载体,处于非常重要的地位.Swing中常用的窗体包括JFr ...

  10. 【element】中el-row如何使内容垂直居中

    查阅官方文档,只需要在el-row中设置属性align为middle即可