PostProcessingBeanDeserializer 使用指南

PostProcessingBeanDeserializer 使用指南

1. 什么是 PostProcessingBeanDeserializer

PostProcessingBeanDeserializer 是一个自定义的 Jackson 反序列化器,用于在 JSON 反序列化完成后对对象进行后置处理(如数据清洗、字段修正、默认值填充等)。

2. 实现方式

方式一:继承 BeanDeserializerBase 并包装原始反序列化器

java 复制代码
package com.xiaomi.car.universal.common.config.jackson;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * 后置处理反序列化器
 * 在反序列化完成后执行自定义处理逻辑
 */
public class PostProcessingBeanDeserializer extends BeanDeserializer {
    
    public PostProcessingBeanDeserializer(BeanDeserializerBase src) {
        super(src);
    }
    
    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        // 1. 先执行原始的反序列化
        Object bean = super.deserialize(p, ctxt);
        
        // 2. 执行后置处理
        if (bean != null) {
            postProcess(bean);
        }
        
        return bean;
    }
    
    /**
     * 后置处理方法 - 子类可以重写此方法实现自定义逻辑
     */
    protected void postProcess(Object bean) {
        // 默认实现:什么都不做
        // 子类可以重写此方法实现具体逻辑
    }
}

方式二:使用 BeanDeserializerModifier 注册

java 复制代码
package com.xiaomi.car.universal.common.config.jackson;

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.stereotype.Component;

/**
 * 自定义反序列化器修改器
 */
@Component
public class CustomBeanDeserializerModifier extends BeanDeserializerModifier {
    
    @Override
    public JsonDeserializer<?> modifyDeserializer(
            DeserializationConfig config,
            BeanDescription beanDesc,
            JsonDeserializer<?> deserializer) {
        
        // 只对特定类型进行包装
        if (deserializer instanceof BeanDeserializerBase) {
            return new PostProcessingBeanDeserializer((BeanDeserializerBase) deserializer) {
                @Override
                protected void postProcess(Object bean) {
                    // 在这里实现具体的后置处理逻辑
                    handlePostProcessing(bean);
                }
            };
        }
        
        return deserializer;
    }
    
    /**
     * 具体的后置处理逻辑
     */
    private void handlePostProcessing(Object bean) {
        // 示例1: 字符串字段统一trim
        if (bean instanceof StringTrimable) {
            trimStringFields(bean);
        }
        
        // 示例2: 填充默认值
        if (bean instanceof DefaultValueFillable) {
            fillDefaultValues(bean);
        }
        
        // 示例3: 数据格式转换
        if (bean instanceof DataTransformable) {
            transformData(bean);
        }
    }
    
    private void trimStringFields(Object bean) {
        // 使用反射或工具类处理字符串字段
        // 例如:Apache Commons BeanUtils 或 Spring BeanWrapper
    }
    
    private void fillDefaultValues(Object bean) {
        // 填充默认值逻辑
    }
    
    private void transformData(Object bean) {
        // 数据转换逻辑
    }
}

3. 配置到 ObjectMapper

方式一:在配置类中注册

java 复制代码
package com.xiaomi.car.universal.common.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.xiaomi.car.universal.common.config.jackson.CustomBeanDeserializerModifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {
    
    @Bean
    public ObjectMapper objectMapper(CustomBeanDeserializerModifier modifier) {
        ObjectMapper mapper = new ObjectMapper();
        
        // 创建模块并注册修改器
        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(modifier);
        
        mapper.registerModule(module);
        
        return mapper;
    }
}

方式二:针对特定类型使用

java 复制代码
package com.xiaomi.car.universal.common.config.jackson;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;

/**
 * 示例:针对特定DTO使用自定义反序列化器
 */
@JsonDeserialize(using = UserDTODeserializer.class)
public class UserDTO {
    private String name;
    private String email;
    
    // getters and setters
}

/**
 * 自定义反序列化器
 */
class UserDTODeserializer extends JsonDeserializer<UserDTO> {
    
    @Override
    public UserDTO deserialize(JsonParser p, DeserializationContext ctxt) 
            throws IOException {
        // 1. 使用默认反序列化
        UserDTO dto = p.getCodec().readValue(p, UserDTO.class);
        
        // 2. 后置处理
        if (dto != null) {
            // 字符串trim
            if (StringUtils.isNotBlank(dto.getName())) {
                dto.setName(dto.getName().trim());
            }
            if (StringUtils.isNotBlank(dto.getEmail())) {
                dto.setEmail(dto.getEmail().trim().toLowerCase());
            }
        }
        
        return dto;
    }
}

4. 实际应用场景示例

场景1:字符串字段统一trim

java 复制代码
public class PostProcessingBeanDeserializer extends BeanDeserializer {
    
    public PostProcessingBeanDeserializer(BeanDeserializerBase src) {
        super(src);
    }
    
    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) 
            throws IOException {
        Object bean = super.deserialize(p, ctxt);
        
        if (bean != null) {
            trimStringFields(bean);
        }
        
        return bean;
    }
    
    private void trimStringFields(Object bean) {
        Class<?> clazz = bean.getClass();
        Field[] fields = clazz.getDeclaredFields();
        
        for (Field field : fields) {
            if (field.getType() == String.class) {
                field.setAccessible(true);
                try {
                    String value = (String) field.get(bean);
                    if (value != null) {
                        field.set(bean, value.trim());
                    }
                } catch (IllegalAccessException e) {
                    // 忽略
                }
            }
        }
    }
}

场景2:空字符串转null

java 复制代码
@Override
protected void postProcess(Object bean) {
    Class<?> clazz = bean.getClass();
    Field[] fields = clazz.getDeclaredFields();
    
    for (Field field : fields) {
        if (field.getType() == String.class) {
            field.setAccessible(true);
            try {
                String value = (String) field.get(bean);
                if (value != null && value.isEmpty()) {
                    field.set(bean, null);
                }
            } catch (IllegalAccessException e) {
                // 忽略
            }
        }
    }
}

场景3:日期格式兼容处理

java 复制代码
@Override
protected void postProcess(Object bean) {
    Class<?> clazz = bean.getClass();
    Field[] fields = clazz.getDeclaredFields();
    
    for (Field field : fields) {
        if (field.getType() == Date.class || field.getType() == LocalDateTime.class) {
            // 处理日期字段的兼容性
            // 例如:将旧格式转换为新格式
        }
    }
}

5. 与 @Validated 的区别

特性 PostProcessingBeanDeserializer @Validated
触发时机 JSON反序列化完成后立即执行 Spring MVC参数绑定后,方法执行前
主要作用 数据清洗、格式转换、默认值填充 数据校验、规则验证
是否修改数据 ✅ 可以修改对象数据 ❌ 只校验,不修改
作用范围 全局(所有使用该ObjectMapper的地方) 局部(只对加了注解的参数生效)
错误处理 可以静默处理或抛出异常 统一抛出 MethodArgumentNotValidException

6. 最佳实践

✅ 推荐使用场景

  1. 数据清洗:统一trim、空字符串转null
  2. 格式转换:旧版本JSON格式兼容
  3. 默认值填充:根据业务规则填充默认值
  4. 字段映射:将旧字段名映射到新字段名

❌ 不推荐使用场景

  1. 业务校验 :应该使用 @Validated + Bean Validation
  2. 复杂业务逻辑:应该在Service层处理
  3. 数据库操作:不应该在反序列化器中执行

7. 完整示例

java 复制代码
@Configuration
public class JacksonConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        
        SimpleModule module = new SimpleModule();
        module.setDeserializerModifier(new BeanDeserializerModifier() {
            @Override
            public JsonDeserializer<?> modifyDeserializer(
                    DeserializationConfig config,
                    BeanDescription beanDesc,
                    JsonDeserializer<?> deserializer) {
                
                if (deserializer instanceof BeanDeserializerBase) {
                    return new PostProcessingBeanDeserializer(
                        (BeanDeserializerBase) deserializer) {
                        @Override
                        protected void postProcess(Object bean) {
                            // 统一处理:字符串trim
                            trimStringFields(bean);
                            // 统一处理:空字符串转null
                            emptyStringToNull(bean);
                        }
                    };
                }
                return deserializer;
            }
        });
        
        mapper.registerModule(module);
        return mapper;
    }
}

8. 注意事项

  1. 性能考虑:后置处理会增加反序列化时间,避免复杂逻辑
  2. 异常处理:后置处理中的异常要妥善处理,避免影响反序列化
  3. 线程安全:确保后置处理逻辑是线程安全的
  4. 调试困难:后置处理在底层执行,调试相对困难,建议添加日志
相关推荐
郑州光合科技余经理39 分钟前
基于PHP:海外版同城O2O系统多语言源码解决方案
java·开发语言·git·spring cloud·uni-app·php·uniapp
IT_Octopus1 小时前
Java GZip 压缩实践 +实践思考 +进一步压榨性能和存储方案思考:Protobuf+ GZip
java·spring boot
毕设源码-郭学长1 小时前
【开题答辩全过程】以 高校教材大管家系统为例,包含答辩的问题和答案
java·spring boot
Gavin在路上2 小时前
DDD之用事件风暴重构“电商订单履约”(11)
java·前端·重构
AnAnCode2 小时前
ECS 架构 (Entity Component System) - 数据导向编程快速入门
java·架构·游戏服务器
qq_12498707532 小时前
基于SpringBoot+vue的小黄蜂外卖平台(源码+论文+部署+安装)
java·开发语言·vue.js·spring boot·后端·mysql·毕业设计
小二·2 小时前
Spring框架入门:TX 声明式事务详解
java·数据库·spring
i02082 小时前
Java 17 + Spring Boot 3.2.5 使用 Redis 实现“生产者–消费者”任务队列
java·spring boot·redis
烤麻辣烫2 小时前
黑马程序员苍穹外卖后端概览
xml·java·数据库·spring·intellij-idea