自定义注解实现数据序列化时进行数据脱敏(基于springboot默认jackjson)、消息转换器HttpMessageConverter

消息转换器 HttpMessageConverter

消息转化器的作用
将请求报文转化为Java对象
将Java对象转化为响应报文
消息转换器接口

public interface HttpMessageConverter<T> {

	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

	List<MediaType> getSupportedMediaTypes();

	default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
		return (canRead(clazz, null) || canWrite(clazz, null) ?
				getSupportedMediaTypes() : Collections.emptyList());
	}

	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

转换器加载流程
消息转换器是在项目启动的时候通过WebMvcConfigurationSupport进行加载,当getMessageConverters()被调用的时候会通过configureMessageConverters()、addDefaultHttpMessageConverters()和extendMessageConverters()三个方法进行初始话消息转换器。生成的消息转换器放在List<HttpMessageConverter<?>> messageConverters集合中

	protected final List<HttpMessageConverter<?>> getMessageConverters() {
        if (this.messageConverters == null) {
            this.messageConverters = new ArrayList();
            // 加载委托给WebMvcConfigurer类型的Bean
            this.configureMessageConverters(this.messageConverters);
            if (this.messageConverters.isEmpty()) {
                // 加载默认的转换器
                this.addDefaultHttpMessageConverters(this.messageConverters);
            }
            // 加载扩展消息转换器
            this.extendMessageConverters(this.messageConverters);
        }
        return this.messageConverters;
    }

自定义消息转换器参考下面
(1)直接注入Bean 的方式替换
(2)实现 WebMvcConfigurer#extendMessageConverters 接口方法

fastjson与jackjson

问题

在springboot中使用fastjson的@jsonField无效
原因:在springboot默认有json(jackjson)解析工具,所以使用fastjson不会生效
解决方案替换默认的解析工具(笔者不推荐,这里根据自己项目决定)

fastjson替换默认的jackjson

第一种方法bean方法

package com.anson.config;
 
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
 
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
 
@Configuration
public class WebConfig {
 
    /**
     * @Author anson
     * @Description 配置消息转换器
     * @Date: 2019-12-8 11:23:33
     * @version: 1.0
     * new HttpMessageConverters(true, converters);
     * 一定要设为true才能替换否则不会替换
     * @return 返回一个消息转换的bean
     */
    @Bean
    public HttpMessageConverters fastJsonMessageConverters() {
        List<HttpMessageConverter<?>> converters = new ArrayList<>();
        //需要定义一个convert转换消息的对象;
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        //添加fastJson的配置信息;
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        //全局时间配置
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
        fastJsonConfig.setCharset(Charset.forName("UTF-8"));
        //处理中文乱码问题
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        //在convert中添加配置信息.
        fastConverter.setSupportedMediaTypes(fastMediaTypes);
        fastConverter.setFastJsonConfig(fastJsonConfig);
 
        converters.add(0, fastConverter);
        return new HttpMessageConverters(converters);
    }
}

第二种方法实现webMvcConfigurer

@Configuration
public class WebConfigure implements WebMvcConfigurer{
 
    /**
     * 配置消息转换器
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
       //需要定义一个convert转换消息的对象;
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        //添加fastJson的配置信息;
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        //全局时间配置
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
        fastJsonConfig.setCharset(Charset.forName("UTF-8"));
        //处理中文乱码问题
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        //在convert中添加配置信息.
        fastConverter.setSupportedMediaTypes(fastMediaTypes);
        fastConverter.setFastJsonConfig(fastJsonConfig);
        converters.add(0,fastConverter);
    }
}

jackjson注解实现原理

参考:https://blog.csdn.net/qq_18515155/article/details/128891441?spm=1001.2014.3001.5501
这里以Jackson中常用的@JsonFormat注解为例,目标就是将Company对象序列化。

public class Company {
    /**
     * 开张时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date openDate;

    public Date getOpenDate() {
        return openDate;
    }

    public void setOpenDate(Date openDate) {
        this.openDate = openDate;
    }
}

public class Test {
    public static void main(String[] args) {
        Company company = new Company();
        company.setOpenDate(new Date());
      
        ObjectMapper objectMapper = new ObjectMapper();

        String s;
        try {
            s = objectMapper.writeValueAsString(company);
            System.out.println(s);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}

执行结果如下:
{"openDate":"2023-02-05"}

处理过程简述:
寻找合适的序列化器
序列化器二次处理(主要用于传递属性元信息)
执行序列化
详细如下:
一·寻找合适的序列化器
image
(1)根据当前数据类型从缓存中获取对应的序列化器
(2)如果获取不到,则创建序列化器
(3)根据Company上的注解获取序列化器(Company上没有注解)
(4)根据当前的注解内省器获取能够处理 步骤3 拿到的注解的序列化器(默认的注解内省器是JacksonAnnotationIntrospector,对于注解@JsonSerialize则返回注解指定的序列化器,对于注解JsonRawValue则返回RawSerializer)
(5)如果获取不到,再根据是否实现了JsonSerializable接口或者标注了 @JsonValue注解获取对应的序列化器(当前Company都没有)
(6)如果没有,就只能创建通用的对象处理器BeanSerialzer
(7)在创建BeanSerialzer之前,需要先去找到对象的属性元信息
(8)为对象的属性(Compnay.openDate)找到合适的序列化器(类似上面的步骤,一步一步找,这里最终没有找对于openDate属性的序列化器)
(9)把属性元信息设置到对象元信息里,然后new一个BeanSerialzer并返回
(10)拿到返回的序列化器以后,如果该序列化器实现了ContextualSerializer接口,则调用它的createContextual方法进行二次处理,并返回序列化器
二.序列化器二次处理(主要用于传递属性元信息)
步骤10即为序列化器二次处理:当找到合适的序列化器以后,还需要根据是否这个序列化器实现了ContextualSerializer接口,该接口声明了方法JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property)

该接口可以拿到属性的元信息,如属性上的注解信息,根据元信息可以做一些二次处理,然后再返回序列化器,再用这个返回的序列化器做序列化处理。

三.执行序列化
image
(1)序列化对象
(2)序列化对象的字段
(3)动态获取能处理字段的序列化器
(4)检索内置的序列化器集合
(5)根据类型从内置的序列化器集合获取对应的序列化器,即根据openDate的类型Date.class获取到DateSerializer
(6)序列化器二次处理
(7)设置需要传递到序列化器中的上下文信息
(8)将openDate上的JsonFormat注解的pattern字段值传递到DateSerializer,用来在序列化openDate时知道应该转为哪种时间格式
(9)调用DateSerializer.serialze进行字段序列化

jackjson实现数据脱敏

脱敏即是对数据的部分信息用脱敏符号(*)处理。
自定义注解:

package com.chinatower.platform.awh.framework;

import com.chinatower.platform.awh.framework.enums.SentitiveFieldEnum;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SensitiveField {
    SentitiveFieldEnum type();
}

枚举类:

package com.chinatower.platform.awh.framework.enums;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
public enum  SentitiveFieldEnum {
    /**
     * 身份证号
     */
    ID_CARD,
    /**
     * 姓名
     */
    NAME,
    /**
     * 手机号
     */
    PHONE,
}

工具类:

package com.chinatower.platform.awh.framework.config;

import cn.hutool.core.util.StrUtil;
import org.apache.commons.lang3.StringUtils;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
public class SensitiveDataUtils {
    public static String encryptCard(String card){
        String left = StringUtils.left(card, 1);
        return StringUtils.rightPad(left,card.length(),"*");
    }
    public static String encryptPhone(String card){
        String left = StringUtils.left(card, 2);
        return StringUtils.rightPad(left,card.length(),"*");
    }
    public static String encryptName(String card){
        String left = StringUtils.left(card, 3);
        return StringUtils.rightPad(left,card.length(),"*");
    }
}

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

package com.chinatower.platform.awh.framework.config;

import com.chinatower.platform.awh.framework.SensitiveField;
import com.chinatower.platform.awh.framework.enums.SentitiveFieldEnum;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;

import java.io.IOException;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
public class SensitiveFieldSerializer extends JsonSerializer<String> implements ContextualSerializer {
    private  SentitiveFieldEnum eum ;
    public SensitiveFieldSerializer(){}
    public SensitiveFieldSerializer(SentitiveFieldEnum enu){this.eum = enu;}


    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {

        SentitiveFieldEnum sentitiveFieldEnum = eum;
        if (sentitiveFieldEnum == null){
            return;
        }
        switch (sentitiveFieldEnum){
            case ID_CARD:
                s=SensitiveDataUtils.encryptCard(s);
                break;
            case NAME:
                s=SensitiveDataUtils.encryptName(s);
                break;
            case PHONE:
                s=SensitiveDataUtils.encryptPhone(s);
                break;
            default:break;

        }
        jsonGenerator.writeObject(s);
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        //获取注解
        System.out.println(1);
        if (beanProperty != null){
            SensitiveField annotation = beanProperty.getAnnotation(SensitiveField.class);
            SentitiveFieldEnum type = annotation.type();
            return new SensitiveFieldSerializer(type);
        }
        //返回处理枚举
        System.out.println(2);
        return serializerProvider.findNullValueSerializer(null);
    }
}

自定义注解内省器

package com.chinatower.platform.awh.framework;

import com.chinatower.platform.awh.framework.config.SensitiveFieldSerializer;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
public class SensitiveFieldAnnotationIntrospector extends NopAnnotationIntrospector {
    @Override
    public Object findSerializer(Annotated am) {
        SensitiveField annotation = am.getAnnotation(SensitiveField.class);
        if (annotation !=null){
            return SensitiveFieldSerializer.class;
        }
        return super.findSerializer(am);
    }
}

Jackson序列化配置

package com.chinatower.platform.awh.framework.config;

import com.chinatower.platform.awh.framework.SensitiveFieldAnnotationIntrospector;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

/**
 * @Description :
 * @Date : 2023/5/23
 * @Author :
 */
@Configuration
public class JacksonConfig {


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

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

    @Bean
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder, SensitiveFieldAnnotationIntrospector introspector){
        ObjectMapper mapper = builder.createXmlMapper(false).build();
        AnnotationIntrospector annotationIntrospector = mapper.getSerializationConfig().getAnnotationIntrospector();
        AnnotationIntrospector pair = AnnotationIntrospector.pair(annotationIntrospector, introspector);
        mapper.setAnnotationIntrospector(pair);
        return mapper;
    }
}

springboot的bean是单例模式

测试

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

代码分析:
(1)如何找到处理自定义注解的序列化器
就要通过注解內省器找到合适的序列化器。
所以我们自定义了一个注解內省器,并把它加入到了当前Jackson注解內省器集合里:

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

(2)**如何不影响已有的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
        @ConditionalOnMissingBean
       // 这个注解代表没有自定义的ObjectMqpper bean对象时才会在这里创建
        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;
    }

posted @ 2023-05-24 11:32  spiderMan1-1  阅读(1379)  评论(0编辑  收藏  举报