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" } 

原因

  1. body是怎么返回的?
    spring.jackson.default-property-inclusion 不生效问题分析
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 的结果

不去掉的结果
spring.jackson.default-property-inclusion 不生效问题分析
spring.jackson.default-property-inclusion 不生效问题分析

去掉的结果
spring.jackson.default-property-inclusion 不生效问题分析
spring.jackson.default-property-inclusion 不生效问题分析

可以看到,去掉之后,多了一个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); 		} 	} } 

不去掉的结果
spring.jackson.default-property-inclusion 不生效问题分析

去掉的结果
spring.jackson.default-property-inclusion 不生效问题分析

可以看到,不去掉的话,这里只有一个我们自己定义的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配置?好几个坑,这篇文章必须珍藏

发表评论

评论已关闭。

相关文章