背景
项目里每个返回体里都有@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配置?好几个坑,这篇文章必须珍藏