如何为Spring Boot Controller配置不同ObjectMapper实现差异化响应序列化
Spring Boot 按Controller粒度差异化配置响应序列化ObjectMapper实现
现有配置现状
项目当前已在Spring容器中注册了@Primary修饰的全局单例ObjectMapper,作为所有REST Controller的默认序列化组件,简化配置如下:
@Bean @Primary public ObjectMapper objectMapper() { return new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); }
受Spring MVC默认加载逻辑影响,所有接口的响应序列化都会复用这同一个单例实例,无法直接匹配不同Controller的输出规则差异。
预期配置目标
需要实现Controller维度的序列化规则绑定,具体匹配关系如下:
- Controller A、B、C 使用专属ObjectMapper X:序列化时自动忽略所有值为null的字段
- Controller D 使用专属ObjectMapper Y:序列化时自动忽略所有空值字段(覆盖null、空字符串、空集合等场景)
- 其余未做特殊配置的Controller 沿用全局默认的ObjectMapper Z:序列化时完整返回包含null值的全部字段
实现约束
- 无法修改项目内自动生成的POJO类,也不打算调整代码生成库逻辑、修改对应Mustache模板
- 不采用自定义MediaType的内容协商方案实现序列化逻辑的差异化
符合约束的落地实现方案
整个实现完全基于Spring Web提供的原生扩展点完成,不侵入业务代码、不修改POJO结构,具体分为三步:
1. 拆分注册不同配置的ObjectMapper实例
保留原全局@Primary的ObjectMapper作为默认逻辑,再基于全局实例拷贝生成两个专属配置的实例,避免通用配置丢失:
// 全局默认ObjectMapper Z:保留原配置,序列化时不忽略null值 @Bean @Primary public ObjectMapper objectMapperZ() { return new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } // 专属ObjectMapper X:序列化时忽略null值 @Bean public ObjectMapper objectMapperX() { // 从全局实例拷贝生成,保留所有通用配置 return objectMapperZ().copy() .setSerializationInclusion(JsonInclude.Include.NON_NULL); } // 专属ObjectMapper Y:序列化时忽略空值(null、空字符串、空集合等) @Bean public ObjectMapper objectMapperY() { return objectMapperZ().copy() .setSerializationInclusion(JsonInclude.Include.NON_EMPTY); }
注意:所有自定义ObjectMapper都必须通过全局实例的
copy()方法生成,不能直接new新实例,否则会丢失全局配置中的反序列化规则、Jackson模块注册等通用逻辑。
2. 定义Controller匹配标识
创建仅作用于类级别的自定义注解,用来标记需要绑定专属序列化规则的Controller:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface JsonObjectMapper { // 填写要使用的ObjectMapper对应的Bean名称 String value(); }
后续直接在需要特殊规则的Controller类上标注即可,无需修改其他逻辑:
- Controller A、B、C 类上添加
@JsonObjectMapper("objectMapperX") - Controller D 类上添加
@JsonObjectMapper("objectMapperY") - 其他Controller不做任何标注,自动走全局默认的ObjectMapper Z逻辑
3. 实现序列化规则的动态切换
利用Spring MVC提供的ResponseBodyAdvice扩展点,在响应写出阶段根据Controller上的注解动态绑定对应的ObjectMapper,完全不侵入原有业务代码:
@ControllerAdvice public class DynamicJsonSerializeAdvice implements ResponseBodyAdvice<Object> { private final Map<String, ObjectMapper> allObjectMappers; // 自动注入容器中所有ObjectMapper实例,按Bean名称匹配 public DynamicJsonSerializeAdvice(Map<String, ObjectMapper> allObjectMappers) { this.allObjectMappers = allObjectMappers; } @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { // 仅对标注了自定义注解的Controller生效 return returnType.getContainingClass().isAnnotationPresent(JsonObjectMapper.class); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 读取Controller上的注解配置,匹配对应的目标ObjectMapper JsonObjectMapper config = returnType.getContainingClass().getAnnotation(JsonObjectMapper.class); ObjectMapper targetMapper = allObjectMappers.get(config.value()); if (targetMapper == null) { // 找不到对应配置时直接走默认序列化逻辑 return body; } // 包装响应体,绑定指定的ObjectMapper完成后续序列化流程 MappingJacksonValue wrapper = new MappingJacksonValue(body); wrapper.setObjectMapper(targetMapper); return wrapper; } }
内容的提问来源于stack exchange,提问作者rmrt
相关产品推荐
相关产品推荐

