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

时间:2023-02-05 15:07:06

背景

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