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