spring.jackson.default-property-inclusion 无效问题分析

x33g5p2x  于9个月前 转载在 Spring  
字(13.9k)|赞(0)|评价(0)|浏览(490)

spring.jackson.default-property-inclusion 不生效问题分析

背景

项目里每个返回体里都有 @JsonInclude(JsonInclude.Include.NON_NULL) 这个注解,也就是不返回 null 字段

想有没有办法全局配置一下,这样就不用每个类都加这个注解了

  1. spring:
  2. jackson:
  3. default-property-inclusion: non_null

看网上说加上这个配置项就可以了,于是加上之后,发现不生效,后来又查到不生效的原因是因为项目里手动注入了 WebMvcConfigurationSupport 这个类

看我们的项目里确实是这样的,配置如下:

  1. @Configuration
  2. @Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class)
  3. public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations {
  4. }

导入了 EnableWebMvcConfiguration 这个类,这个类继承了 DelegatingWebMvcConfiguration , DelegatingWebMvcConfiguration 这个类实现了 WebMvcConfigurationSupport

那就一起来看下不生效的原因以及解决办法

例子

  1. web配置
  2. @Configuration
  3. @Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class)
  4. public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations {
  5. }
  6. controller
  7. @RestController
  8. @RequestMapping("/api/jackson")
  9. public class JacksonTestController {
  10. @GetMapping("/data")
  11. public Object getData() {
  12. return new Result();
  13. }
  14. @Data
  15. public static class Result {
  16. private String string1 = "hello";
  17. private String string2;
  18. }
  19. }
  20. application.yml
  21. spring:
  22. jackson:
  23. default-property-inclusion: non_null
  24. 启动类
  25. @SpringBootApplication
  26. public class MyApplication {
  27. public static void main(String[] args) {
  28. SpringApplication.run(MyApplication.class, args);
  29. }
  30. }
  1. 访问 http://127.0.0.1:8080/api/jackson/data
  2. 返回
  3. {
  4. "string1": "hello",
  5. "string2": null
  6. }

可以看出确实是不生效的

先看一下比较明显的方案:

  1. 方法一
  2. JsonInclude注解
  3. @Data
  4. @JsonInclude(JsonInclude.Include.NON_NULL)
  5. public static class Result {
  6. private String string1 = "hello";
  7. private String string2;
  8. }
  9. 访问返回:
  10. {
  11. "string1": "hello"
  12. }
  13. 方法二
  14. 注释掉Import那一行
  15. @Configuration
  16. //@Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class)
  17. public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations {
  18. }
  19. @Data
  20. //@JsonInclude(JsonInclude.Include.NON_NULL)
  21. public static class Result {
  22. private String string1 = "hello";
  23. private String string2;
  24. }
  25. 访问返回:
  26. {
  27. "string1": "hello"
  28. }

原因

  1. body是怎么返回的?

  1. public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
  2. implements HandlerMethodReturnValueHandler {
  3. protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
  4. ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
  5. throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
  6. }
  7. }
  8. body是返回结果,MappingJackson2HttpMessageConverter会去转换结果,它是从this.messageConverters获取

分别看一下不去掉以及去掉 Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class) 注解后 messageConverters 的结果

不去掉的结果

去掉的结果

可以看到,去掉之后,多了一个MappingJackson2HttpMessageConverter实例,并且这个是根据配置生成的,而且因为它的顺序在前面,所以会用这个来处理

  1. 来看一下 this.messageConverters 是怎么被赋值的

RequestResponseBodyMethodProcessor这个类是通过RequestMappingHandlerAdapter生成的

  1. public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
  2. implements BeanFactoryAware, InitializingBean {
  3. private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
  4. handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); // this
  5. return handlers;
  6. }
  7. public List<HttpMessageConverter<?>> getMessageConverters() {
  8. return this.messageConverters; // this
  9. }
  10. }

RequestMappingHandlerAdapter是由WebMvcConfigurationSupport生成的

  1. public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
  2. @Bean
  3. public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
  4. @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
  5. @Qualifier("mvcConversionService") FormattingConversionService conversionService,
  6. @Qualifier("mvcValidator") Validator validator) {
  7. RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
  8. adapter.setMessageConverters(getMessageConverters());
  9. }
  10. protected final List<HttpMessageConverter<?>> getMessageConverters() {
  11. if (this.messageConverters == null) {
  12. this.messageConverters = new ArrayList<>();
  13. configureMessageConverters(this.messageConverters); // this
  14. if (this.messageConverters.isEmpty()) {
  15. addDefaultHttpMessageConverters(this.messageConverters); // this
  16. }
  17. extendMessageConverters(this.messageConverters);
  18. }
  19. return this.messageConverters;
  20. }
  21. protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
  22. StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
  23. stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
  24. messageConverters.add(new ByteArrayHttpMessageConverter());
  25. messageConverters.add(stringHttpMessageConverter);
  26. messageConverters.add(new ResourceHttpMessageConverter());
  27. messageConverters.add(new ResourceRegionHttpMessageConverter());
  28. if (jackson2Present) {
  29. Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
  30. if (this.applicationContext != null) {
  31. builder.applicationContext(this.applicationContext);
  32. }
  33. messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build())); // this
  34. }
  35. else if (gsonPresent) {
  36. messageConverters.add(new GsonHttpMessageConverter());
  37. }
  38. else if (jsonbPresent) {
  39. messageConverters.add(new JsonbHttpMessageConverter());
  40. }
  41. }
  42. }

可以看到先调用configureMessageConverters方法,如果为空就调用addDefaultHttpMessageConverters方法添加默认的。

看一下configureMessageConverters方法

  1. class WebMvcConfigurerComposite implements WebMvcConfigurer {
  2. @Override
  3. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  4. for (WebMvcConfigurer delegate : this.delegates) {
  5. delegate.configureMessageConverters(converters);
  6. }
  7. }
  8. }

不去掉的结果

去掉的结果

可以看到,不去掉的话,这里只有一个我们自己定义的WebMvcConfigurer,而且是不会做任何操作的,所以就会调用addDefaultHttpMessageConverters,生成默认的。
如果去掉的话,会调用WebMvcAutoConfigurationAdapter的configureMessageConverters方法,这个方法的返回结果不会空,就不会调用addDefaultHttpMessageConverters,然后直接返回。

下面看一下这个方法。

  1. @Configuration(proxyBeanMethods = false)
  2. @ConditionalOnWebApplication(type = Type.SERVLET)
  3. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
  4. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
  5. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
  6. @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
  7. ValidationAutoConfiguration.class })
  8. public class WebMvcAutoConfiguration {
  9. @Configuration(proxyBeanMethods = false)
  10. @Import(EnableWebMvcConfiguration.class)
  11. @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
  12. @Order(0)
  13. public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
  14. private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
  15. public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
  16. ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
  17. ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
  18. ObjectProvider<DispatcherServletPath> dispatcherServletPath,
  19. ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
  20. this.messageConvertersProvider = messageConvertersProvider; // this
  21. }
  22. @Override
  23. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  24. this.messageConvertersProvider
  25. .ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters())); // this
  26. }
  27. }

会注入HttpMessageConverters

  1. @Configuration
  2. @ConditionalOnClass(HttpMessageConverter.class)
  3. @AutoConfigureAfter({ GsonAutoConfiguration.class, JacksonAutoConfiguration.class,
  4. JsonbAutoConfiguration.class })
  5. @Import({ JacksonHttpMessageConvertersConfiguration.class,
  6. GsonHttpMessageConvertersConfiguration.class,
  7. JsonbHttpMessageConvertersConfiguration.class })
  8. public class HttpMessageConvertersAutoConfiguration {
  9. public HttpMessageConvertersAutoConfiguration(
  10. ObjectProvider<HttpMessageConverter<?>> convertersProvider) {
  11. this.converters = convertersProvider.orderedStream().collect(Collectors.toList());
  12. }
  13. @Bean
  14. @ConditionalOnMissingBean
  15. public HttpMessageConverters messageConverters() {
  16. return new HttpMessageConverters(this.converters);
  17. }
  18. public HttpMessageConverters(Collection<HttpMessageConverter<?>> additionalConverters) {
  19. this(true, additionalConverters);
  20. }
  21. public HttpMessageConverters(boolean addDefaultConverters,
  22. Collection<HttpMessageConverter<?>> converters) {
  23. List<HttpMessageConverter<?>> combined = getCombinedConverters(converters,
  24. addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
  25. combined = postProcessConverters(combined);
  26. this.converters = Collections.unmodifiableList(combined);
  27. }
  28. private List<HttpMessageConverter<?>> getDefaultConverters() {
  29. List<HttpMessageConverter<?>> converters = new ArrayList<>();
  30. if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation."
  31. + "WebMvcConfigurationSupport", null)) {
  32. converters.addAll(new WebMvcConfigurationSupport() {
  33. public List<HttpMessageConverter<?>> defaultMessageConverters() {
  34. return super.getMessageConverters(); // this
  35. }
  36. }.defaultMessageConverters());
  37. }
  38. else {
  39. converters.addAll(new RestTemplate().getMessageConverters());
  40. }
  41. reorderXmlConvertersToEnd(converters);
  42. return converters;
  43. }
  44. private List<HttpMessageConverter<?>> getCombinedConverters(
  45. Collection<HttpMessageConverter<?>> converters,
  46. List<HttpMessageConverter<?>> defaultConverters) {
  47. List<HttpMessageConverter<?>> combined = new ArrayList<>();
  48. List<HttpMessageConverter<?>> processing = new ArrayList<>(converters);
  49. for (HttpMessageConverter<?> defaultConverter : defaultConverters) {
  50. Iterator<HttpMessageConverter<?>> iterator = processing.iterator();
  51. while (iterator.hasNext()) {
  52. HttpMessageConverter<?> candidate = iterator.next();
  53. if (isReplacement(defaultConverter, candidate)) {
  54. combined.add(candidate);
  55. iterator.remove();
  56. }
  57. }
  58. combined.add(defaultConverter);
  59. if (defaultConverter instanceof AllEncompassingFormHttpMessageConverter) {
  60. configurePartConverters(
  61. (AllEncompassingFormHttpMessageConverter) defaultConverter,
  62. converters);
  63. }
  64. }
  65. combined.addAll(0, processing);
  66. return combined;
  67. }
  68. }

可以看到converters由两部分组成,一部分是注入的HttpMessageConverter,另一部分是WebMvcConfigurationSupport.defaultMessageConverters方法,然后进行合并,当然如果有相同的类,前面的顺序在前。

会注入MappingJackson2HttpMessageConverter

  1. @Configuration
  2. class JacksonHttpMessageConvertersConfiguration {
  3. @Configuration
  4. @ConditionalOnClass(ObjectMapper.class)
  5. @ConditionalOnBean(ObjectMapper.class)
  6. @ConditionalOnProperty(
  7. name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
  8. havingValue = "jackson", matchIfMissing = true)
  9. protected static class MappingJackson2HttpMessageConverterConfiguration {
  10. @Bean
  11. @ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class,
  12. ignoredType = {
  13. "org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter",
  14. "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" })
  15. public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(
  16. ObjectMapper objectMapper) {
  17. return new MappingJackson2HttpMessageConverter(objectMapper);
  18. }
  19. }
  20. }

会注入ObjectMapper相关的实例

  1. @Configuration
  2. @ConditionalOnClass(ObjectMapper.class)
  3. public class JacksonAutoConfiguration {
  4. // 注入jacksonObjectMapper
  5. @Configuration
  6. @ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
  7. static class JacksonObjectMapperConfiguration {
  8. @Bean
  9. @Primary
  10. @ConditionalOnMissingBean
  11. public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
  12. return builder.createXmlMapper(false).build();
  13. }
  14. }
  15. // 注入jacksonObjectMapperBuilder
  16. @Configuration
  17. @ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
  18. static class JacksonObjectMapperBuilderConfiguration {
  19. private final ApplicationContext applicationContext;
  20. JacksonObjectMapperBuilderConfiguration(ApplicationContext applicationContext) {
  21. this.applicationContext = applicationContext;
  22. }
  23. @Bean
  24. @ConditionalOnMissingBean
  25. public Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(
  26. List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
  27. Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
  28. builder.applicationContext(this.applicationContext);
  29. customize(builder, customizers);
  30. return builder;
  31. }
  32. private void customize(Jackson2ObjectMapperBuilder builder,
  33. List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
  34. for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
  35. customizer.customize(builder);
  36. }
  37. }
  38. }
  39. // 注入standardJacksonObjectMapperBuilderCustomizer
  40. @Configuration
  41. @ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
  42. @EnableConfigurationProperties(JacksonProperties.class)
  43. static class Jackson2ObjectMapperBuilderCustomizerConfiguration {
  44. @Bean
  45. StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer(
  46. ApplicationContext applicationContext, JacksonProperties jacksonProperties) {
  47. return new StandardJackson2ObjectMapperBuilderCustomizer(applicationContext, jacksonProperties);
  48. }
  49. static final class StandardJackson2ObjectMapperBuilderCustomizer
  50. implements Jackson2ObjectMapperBuilderCustomizer, Ordered {
  51. private final JacksonProperties jacksonProperties;
  52. @Override
  53. public void customize(Jackson2ObjectMapperBuilder builder) {
  54. if (this.jacksonProperties.getDefaultPropertyInclusion() != null) {
  55. builder.serializationInclusion(this.jacksonProperties.getDefaultPropertyInclusion());
  56. }
  57. }
  58. }
  59. }
  60. }
  61. // 会注入JacksonProperties
  62. @ConfigurationProperties(prefix = "spring.jackson")
  63. public class JacksonProperties {
  64. private JsonInclude.Include defaultPropertyInclusion;
  65. }

这个过程就生成了一个新的根据配置文件配置的MappingJackson2HttpMessageConverter,并把它添加到messageConverters中

总结

最后还有一种不去掉import注解就可以解决的方法,就是采用和WebMvcAutoConfigurationAdapter一样的方法,如下:

  1. @Configuration
  2. @Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class)
  3. public class WebConfig implements WebMvcConfigurer, WebMvcRegistrations {
  4. private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
  5. public WebConfig(ObjectProvider<HttpMessageConverters> messageConvertersProvider) {
  6. this.messageConvertersProvider = messageConvertersProvider;
  7. }
  8. @Override
  9. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  10. this.messageConvertersProvider.ifAvailable((customConverters) -> converters
  11. .addAll(customConverters.getConverters()));
  12. }
  13. }

参考

Spring Boot 中自定义 SpringMVC 配置,到底继承谁?
自定义SpringBoot默认MVC配置?好几个坑,这篇文章必须珍藏

相关文章