springboot配置MappingJackson2HttpMessageConverter最佳实践

Posted by hcy on February 16, 2020

上篇讲了,SpringMvc是如何将返回值转成Json的

默认情况下Springboot是如何配置HandlerAdapter的,如何根据配置文件影响到ObjectMapper的创建的。

下面讲一讲如何做才是最佳实践,如何做能满足需求。

1. 最大限度使用Springboot的自动配置

如果我们想最大限度使用Springboot,且想修改接口返回Json格式等,那么我们可以在配置文件中配置常用的配置。

我们在配置文件里打出spring.jacksonide会给我们提示,我们发现常用的配置都有,比如配置输出时间格式的

1
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

也可以打开看看下面这个类,里面也有对应的配置

1
org.springframework.boot.autoconfigure.jackson.JacksonProperties

2.使用自己创建的ObjectMapper

创建一个ObjectMapperBean,但要标记为@Primary才能覆盖自动配置的。这样的话配置文件里的配置不会生效,我们自己创建的就可以随心所欲的配置了。

并且推荐使用下面的第二种方式创建,点开源码能看到它里面添加了额外的配置。

1
2
3
4
5
6
7
    @Bean
    @Primary
    public ObjectMapper objectMapper (){
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        return objectMapper;
    }
1
2
3
4
5
6
7
8
    @Bean
    @Primary
    public ObjectMapper objectMapper (){
        return Jackson2ObjectMapperBuilder
                .json()
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
                .build();
    }

因为org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration里的自动配置,加入了@ConditionalOnMissingBean注解,所以系统就不会自动配置了。

3. 使用自己创建的MappingJackson2HttpMessageConverter

1
2
3
4
5
6
    @Bean
    public MappingJackson2HttpMessageConverter objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        return new MappingJackson2HttpMessageConverter(objectMapper);
    }

因为org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration类中对MappingJackson2HttpMessageConverter的自动配置添加了条件判断,所以自动配置就不会执行了。

判断如下

1
@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class,

4.如果继承WebMvcConfigurationSupport

这个类里面又很多MVC相关的配置,我个人比较习惯继承这个类,但是继承这个类后,上面三种方法无论是改配置文件,自定义ObjectMapper,还是自己定义MappingJackson2HttpMessageConverter均失效了。

大家可以试一下,那么是什么原因导致失效的呢?,如何解决这个问题?

1.是什么原因导致失效的呢?

我们可以看一下WebMvcAutoConfiguration的源码,发现这个类上有@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)的注解,

也就是说找不到WebMvcConfigurationSupport才进行自动配置,而我们继承了WebMvcConfigurationSupport,并添加@Configuration,则MapperAdaper不会被自动配置,所以自动创建的

MappingJackson2HttpMessageConverterObjectMapper都虽然创建了但也不会起作用了。

2. 如何解决这个问题?

既然我习惯继承WebMvcConfigurationSupport,如果想定制ObjectMapper该怎么办呢,查看WebMvcConfigurationSupport源码,发现他有这样一个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	@Bean
	public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
		RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
		adapter.setContentNegotiationManager(mvcContentNegotiationManager());
		//这里注入了MessageConverter,请进入getMessageConverters()方法
		adapter.setMessageConverters(getMessageConverters());
		adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
		adapter.setCustomArgumentResolvers(getArgumentResolvers());
		adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

		if (jackson2Present) {
			adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
			adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
		}

		AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
		configureAsyncSupport(configurer);
		if (configurer.getTaskExecutor() != null) {
			adapter.setTaskExecutor(configurer.getTaskExecutor());
		}
		if (configurer.getTimeout() != null) {
			adapter.setAsyncRequestTimeout(configurer.getTimeout());
		}
		adapter.setCallableInterceptors(configurer.getCallableInterceptors());
		adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

		return adapter;
	}

从上面源码能看到,没有在WebMvcAutoConfiguration里面配置的RequestMappingHandlerAdapter在这里配置了,但这里并没有使用Spring容器里面的ObjectMapperMappingJackson2HttpMessageConverter,而是新创建的,所以系统自动配置的和我们自己注入到Spring容器的才会失效,因为根本就没有被使用。

我们看一下是如何添加MessageConverter的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
	protected final List<HttpMessageConverter<?>> getMessageConverters() {
		if (this.messageConverters == null) {
			this.messageConverters = new ArrayList<>();
			//这个方法是空实现
			configureMessageConverters(this.messageConverters);
			if (this.messageConverters.isEmpty()) {
			//添加默认的messageConverters
				addDefaultHttpMessageConverters(this.messageConverters);
			}
			//继续处理
			extendMessageConverters(this.messageConverters);
		}
		return this.messageConverters;
	}

上面代码能看出,首先是通过configureMessageConverters(this.messageConverters)方法配置,但该方法是空实现,然后添加默认的MessageConverter,然后extendMessageConverters(this.messageConverters)

所以我们可以在继承类中重写extendMessageConverters方法,在这个类中对默认的MessageConverter进行修改或添加删除等操作,如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //将StringHttpMessageConverter设成UTF8格式
        converters.stream()
                .filter(c -> c instanceof StringHttpMessageConverter)
                .map(c -> (StringHttpMessageConverter) c)
                .forEach(c -> c.setDefaultCharset(StandardCharsets.UTF_8));

        //将MappingJackson2HttpMessageConverter设为自己的ObjectMapper
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        converters.stream()
                .filter(c -> c instanceof MappingJackson2HttpMessageConverter)
                .map(c -> (MappingJackson2HttpMessageConverter) c)
                .forEach(c -> c.setObjectMapper(mapper));
    }

5.总结

如果你不打算继承WebMvcConfigurationSupport,那么自动配置生效,你可以通过配置文件配置Jackson或通过提供自己的类配置Jackson,但注意如果覆盖ObjectMapper要加@Primary注解才行。

如果你不小心或打算继承WebMvcConfigurationSupport,那上面的自动配置依然会执行,但不会被使用,这就导致上面的配置不灵了,可以实现extendMessageConverters方法,在里面我们可以任意处理MessageConverter

6.回答下上篇文章的问题

  1. 配置文件里配置列Jackson但不生效?

可能是覆盖了WebMvcConfigurationSupport,或重写了ObjectMapper或重写了MappingJackson2HttpMessageConverter,这样导致配置文件里的不生效了。

  1. 自己手动创建ObjectMapper来代替Springboot帮我们创建的,但不生效?

你肯定是继承了WebMvcConfigurationSupport

  1. 如何做才是最佳实践?

上面已经列举了四种方式,要注意他们的优先级,且继承WebMvcConfigurationSupport会导致前三种方式创建的对象不被使用,也就是失效了。


转载请注明出处:https://www.huangchaoyu.com/2020/02/16/springboot配置MappingJackson2HttpMessageConverter最佳实践/