SpringBoot中Jackson序列化处理自定义注解

22 篇文章 0 订阅
3 篇文章 0 订阅

背景

上篇文章 Jackson序列化带有注解的字段的原理浅析 里已经简单介绍了Jackson如何序列化带有注解的字段。

本文尝试自己定义一个注解,让Jackson序列化时对标注了该注解的字段进行脱敏。

实现步骤

自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SensitiveField {
    /**
     * 敏感字段类型
     * @return
     */
    SensitiveFieldTypeEnum type();
}

public enum SensitiveFieldTypeEnum {
    /**
     * 身份证号
     */
    ID_CARD,
    /**
     * 姓名
     */
    NAME,
    /**
     * 手机号
     */
    PHONE,
}

// 定义一个对象,字段标有注解
@Data
public class User {
    @SensitiveField(type = SensitiveFieldTypeEnum.NAME)
    private String name;
    @SensitiveField(type = SensitiveFieldTypeEnum.PHONE)
    private String phone;
    private Integer age;
}

自定义处理注解的序列化器

@Log4j2
public class SensitiveFieldSerializer extends JsonSerializer<String> implements ContextualSerializer {
    /**
     * 脱敏字段类型
     */
    private final ThreadLocal<SensitiveFieldTypeEnum> type = new ThreadLocal<>();

    public SensitiveFieldSerializer() {
    }

    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        // 不同的字段注解的参数type不同,type不同走不同的处理逻辑
        SensitiveFieldTypeEnum sensitiveFieldTypeEnum = type.get();
        if (sensitiveFieldTypeEnum == null) {
            return;
        }

        switch (sensitiveFieldTypeEnum) {
            case ID_CARD:
                s = SensitiveDataUtils.encryptCard(s);
                break;
            case PHONE:
                s = SensitiveDataUtils.encryptPhone((s));
                break;
            case NAME:
                s = SensitiveDataUtils.encryptName(s);
                break;
            default:
                break;
        }
        jsonGenerator.writeString(s);
        // 用完主动remove
        type.remove();
    }

    // 序列化器二次处理时调用该方法
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) {
            // 获取注解的参数
            SensitiveField annotation = beanProperty.getAnnotation(SensitiveField.class);
            SensitiveFieldTypeEnum sensitiveFieldType = annotation.type();
           
            // 将注解参数设置到序列化器的全局变量type里 
            type.set(sensitiveFieldType);
            // 复用该序列器起对象
            return this;
        }
        return serializerProvider.findNullValueSerializer(null);
    }
}
  • SensitiveDataUtils省略,就是一个实现字符串脱敏的工具类,可自行实现。
  • 这里使用了ThreadLocal<SensitiveFieldTypeEnum> type = new ThreadLocal<>();,是因为多线程下该序列化器是复用的,但是传递的type会不一样,所以为了在判断type的时候不混乱,使用了ThreadLocal。

自定义注解内省器

@Log4j2
public class SensitiveFieldAnnotationIntrospector extends NopAnnotationIntrospector {
    @Override
    public Object findSerializer(Annotated am) {
        SensitiveField annotation = am.getAnnotation(SensitiveField.class);
        if (annotation != null) {
            log.info("----当前序列化使用自定义敏感字段序列化器----");
            return SensitiveFieldSerializer.class;
        }
        return super.findSerializer(am);
    }
}

Jackson序列化配置

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder, SensitiveFieldAnnotationIntrospector introspector) {
        // 根据已有的配置创建自定义的ObjectMapper
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
        // 将自定义注解內省器加入到Jackson注解内省器集合里,AnnotationIntrospector是双向链表结构
        AnnotationIntrospector pair = AnnotationIntrospectorPair.pair(annotationIntrospector, introspector);
        objectMapper.setAnnotationIntrospector(pair);
        return objectMapper;
    }

    @Bean
    public SensitiveFieldAnnotationIntrospector sensitiveFieldAnnotationIntrospector() {
        return new SensitiveFieldAnnotationIntrospector();
    }

    @Bean
    public SensitiveFieldSerializer sensitiveFieldSerializer() {
        return new SensitiveFieldSerializer();
    }
}

测试

@GetMapping("/get")
    public User get() {
        User user = new User();
        user.setAge(20);
        user.setName("徐小莫");
        user.setPhone("18211843612");
        return user;
    }

执行结果:

{
  "name": "***",
  "phone": "182****3672",
  "age": 20
}

思考

如何找到处理自定义注解的序列化器

Jackson序列化带有注解的字段的原理浅析 这篇文章已经提到要处理字段上的注解,就要通过注解內省器找到合适的序列化器。

所以我们自定义了一个注解內省器,并把它加入到了当前Jackson注解內省器集合里:

        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
        // 将自定义注解內省器加入到Jackson注解内省器集合里,AnnotationIntrospector是双向链表结构
        AnnotationIntrospector pair = AnnotationIntrospectorPair.pair(annotationIntrospector, introspector);
        objectMapper.setAnnotationIntrospector(pair);

如何向序列化器传递自定义注解中的参数

Jackson序列化带有注解的字段的原理浅析 这篇文章提到序列化时的一个关键操作:序列化器二次处理

正是通过二次处理,就能够将注解的参数(也可以理解为上下文)传递到序列化器中

如何不影响已有的Jackson序列化配置

实际的SpringBoot项目中,对于Controller的返回数据,经常会在配置文件或者配置类进行一些Jackson的自定义配置。

如果针对当前的自定义注解序列化器新建一个ObjectMapper

        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        SensitiveFieldSerializer sensitiveFieldSerializer = new SensitiveFieldSerializer();
        simpleModule.addSerializer(String.class, sensitiveFieldSerializer);
        objectMapper.registerModule(simpleModule);
        AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
        AnnotationIntrospector newIntro = AnnotationIntrospectorPair.pair(annotationIntrospector,new SensitiveFieldAnnotationIntrospector());
        objectMapper.setAnnotationIntrospector(newIntro);

那这个ObjectMapper不会包含原来的那些配置,因为他是一个新的对象。

要保证原来的配置生效,就需要对SpringBoot自动配置的ObjectMapper动手脚。

通过分析JacksonAutoConfiguration.class这个自动配置类:

        @Bean
        JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer(ApplicationContext applicationContext, JacksonProperties jacksonProperties) {
            return new JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.StandardJackson2ObjectMapperBuilderCustomizer(applicationContext, jacksonProperties);
        }
        
        
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext, List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
            Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
            builder.applicationContext(applicationContext);
            this.customize(builder, customizers);
            return builder;
        }
        @Bean
        @Primary
        // 这个注解代表没有自定义的ObjectMqpper bean对象时才会在这里创建
        @ConditionalOnMissingBean
        ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
            return builder.createXmlMapper(false).build();
        }

发现SpringBoot会将配置信息封装成Jackson2ObjectMapperBuilder,进而去创建ObjectMapper对象。

所以需要通过注入已有的Jackson2ObjectMapperBuilder对象来创建我们自己的ObjectMapper,再将自定义注解内省器加入到它的內省器集合,使我们的自定义注解序列化器生效:

    @Bean
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder, SensitiveFieldAnnotationIntrospector introspector) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
        // 将自定义注解內省器加入到Jackson注解内省器集合里,AnnotationIntrospector是双向链表结构
        AnnotationIntrospector pair = AnnotationIntrospectorPair.pair(annotationIntrospector, introspector);
        objectMapper.setAnnotationIntrospector(pair);
        return objectMapper;
    }
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Spring Boot,可以通过自定义Jackson来实现自定义序列化和反序列化的需求。以下是一个简单的示例,演示了如何自定义Jackson。 首先,我们需要创建一个Jackson的ObjectMapper Bean,并指定我们自定义序列化和反序列化。示例,我们自定义了一个格式化日期的序列化和反序列化。 ```java @Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addSerializer(Date.class, new CustomDateSerializer()); module.addDeserializer(Date.class, new CustomDateDeserializer()); objectMapper.registerModule(module); return objectMapper; } } ``` 然后,我们需要实现自定义序列化和反序列化。示例,我们自定义了一个格式化日期的序列化和反序列化。 ```java public class CustomDateSerializer extends JsonSerializer<Date> { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(dateFormat.format(value)); } } public class CustomDateDeserializer extends JsonDeserializer<Date> { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { String dateValue = p.getText(); try { return dateFormat.parse(dateValue); } catch (ParseException e) { throw new RuntimeException(e); } } } ``` 现在,我们已经完成了自定义Jackson的配置。在使用时,我们可以直接使用@Autowired注解注入ObjectMapper Bean,或者使用@JsonComponent注解来标识我们的自定义序列化和反序列化。 ```java @RestController public class UserController { @Autowired private ObjectMapper objectMapper; @PostMapping("/user") public User addUser(@RequestBody User user) throws JsonProcessingException { String json = objectMapper.writeValueAsString(user); System.out.println(json); return user; } } @JsonComponent public class DateJsonComponent { public static class Serializer extends JsonSerializer<Date> { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(dateFormat.format(value)); } } public static class Deserializer extends JsonDeserializer<Date> { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { String dateValue = p.getText(); try { return dateFormat.parse(dateValue); } catch (ParseException e) { throw new RuntimeException(e); } } } } ``` 以上就是自定义Jackson的简单示例。通过自定义Jackson,我们可以轻松实现自定义序列化和反序列化需求。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值