怎么优雅地做到N个接口根据请求参数决定返回值是否包含某个字段呢?
我们知道Spring MVC返回JSON数据的时候,序列化使用的是Jackson。而且,Jackson提供了自定义的字段Filter。因此,我们只需要修改序列化JSON的ObjectMapper。
总结就是:
- 自定义Filter
- 修改ObjectMapper
- 为返回结果的类添加@JsonFilter
自定义Filter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| public static class MyFilter extends SimpleBeanPropertyFilter {
@Override
protected boolean include(PropertyWriter writer) {
return include0(writer);
}
@Override
protected boolean include(BeanPropertyWriter writer) {
return include0(writer);
}
private boolean include0(PropertyWriter writer) {
// 过滤逻辑,需要过滤的字段返回false
// 可使用自定义注解标识字段
// 例如获取request的参数进行过滤
// request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return true;
}
}
|
修改ObjectMapper
自定义ObjectMapper未生效
我的第一想法就是寻找SpringBoot自定义ObjectMapper的方法。一般SpringBoot的自动配置都会提供某某Customizer
来自定义。
找到JacksonAutoConfiguration,查看源码可得知,只需要自定义一个Jackson2ObjectMapperBuilderCustomizer的bean即可。
1
2
3
4
5
6
7
8
| @Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
final SimpleFilterProvider simpleFilterProvider = new SimpleFilterProvider();
simpleFilterProvider.addFilter("myFilterId", new MyFilter());
builder.filters(simpleFilterProvider);
};
}
|
然而,现实是残酷的,定义完这个之后,压根没生效。而且debug跟了好多代码,发现ObjectMapper确实改变了。那为啥不生效呢?
那说明没有使用我们认为的ObjectMapper,原因就是我自定义了WebMvcConfigurationSupport。
看WebMvcAutoConfiguration的源码,发现类上有如下注解:
1
| @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
|
所以,当我们自定义WebMvcConfigurationSupport时,那么mvc的自动配置压根就没生效。
而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
29
30
| @Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(contentNegotiationManager);
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
AsyncSupportConfigurer configurer = getAsyncSupportConfigurer();
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;
}
|
1
2
3
4
5
6
7
8
9
10
11
| protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
|
这里会生成8个默认的HttpMessageConverter。跟踪代码可以看到生成MappingJackson2HttpMessageConverter使用的Jackson2ObjectMapperBuilder是新生成的,不是我们使用Jackson2ObjectMapperBuilderCustomizer自定义的。
自定义MappingJackson2HttpMessageConverter
覆盖WebMvcConfigurationSupport的extendMessageConverters方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
| @Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
super.extendMessageConverters(converters);
for (final HttpMessageConverter<?> httpMessageConverter : converters) {
if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) {
final MappingJackson2HttpMessageConverter converter = (MappingJackson2HttpMessageConverter) httpMessageConverter;
final SimpleFilterProvider simpleFilterProvider = new SimpleFilterProvider();
simpleFilterProvider.addFilter("myFilterId", new MyFilter());
converter.getObjectMapper().setFilterProvider(simpleFilterProvider);
break;
}
}
}
|
这里需要注意的是,我选择修改MappingJackson2HttpMessageConverter而不是替换。
一开始,我替换为SpringBoot自动配置的MappingJackson2HttpMessageConverter。发现Date字段由原来的long类型的时间戳,变为格式化的字符串。原因是JacksonAutoConfiguration有如下配置:
1
2
3
4
5
6
| static {
Map<Object, Boolean> featureDefaults = new HashMap<>();
featureDefaults.put(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
featureDefaults.put(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
FEATURE_DEFAULTS = Collections.unmodifiableMap(featureDefaults);
}
|
为返回结果的类添加@JsonFilter
1
| @JsonFilter("myFilterId")
|
@JsonFilter需要加在序列化的类上面,且value值为前面定义的filterId值,此处为myFilterId
。