上篇文章 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);}
}
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);}
}
@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序列化带有注解的字段的原理浅析 这篇文章提到序列化时的一个关键操作:序列化器二次处理。
正是通过二次处理,就能够将注解的参数(也可以理解为上下文)传递到序列化器中
实际的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;}