SpringBoot中Jackson序列化处理自定义注解
创始人
2024-05-30 08:43:53
0

文章目录

  • 背景
  • 实现步骤
    • 自定义注解
    • 自定义处理注解的序列化器
    • 自定义注解内省器
    • Jackson序列化配置
  • 测试
  • 思考
    • 如何找到处理自定义注解的序列化器
    • 如何向序列化器传递自定义注解中的参数
    • 如何不影响已有的Jackson序列化配置

背景

上篇文章 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 implements ContextualSerializer {/*** 脱敏字段类型*/private final ThreadLocal type = new ThreadLocal<>();public SensitiveFieldSerializer() {}@Overridepublic 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);// 用完主动removetype.remove();}// 序列化器二次处理时调用该方法@Overridepublic 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 type = new ThreadLocal<>();,是因为多线程下该序列化器是复用的,但是传递的type会不一样,所以为了在判断type的时候不混乱,使用了ThreadLocal。

自定义注解内省器

@Log4j2
public class SensitiveFieldAnnotationIntrospector extends NopAnnotationIntrospector {@Overridepublic 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 {@Beanpublic ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder, SensitiveFieldAnnotationIntrospector introspector) {// 根据已有的配置创建自定义的ObjectMapperObjectMapper objectMapper = builder.createXmlMapper(false).build();AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();// 将自定义注解內省器加入到Jackson注解内省器集合里,AnnotationIntrospector是双向链表结构AnnotationIntrospector pair = AnnotationIntrospectorPair.pair(annotationIntrospector, introspector);objectMapper.setAnnotationIntrospector(pair);return objectMapper;}@Beanpublic SensitiveFieldAnnotationIntrospector sensitiveFieldAnnotationIntrospector() {return new SensitiveFieldAnnotationIntrospector();}@Beanpublic 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这个自动配置类:

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

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

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

    @Beanpublic 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;}

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...