Springboot Jackson 动态添加字段属性

项目场景:

使用jackson序列化返回对象时,某些实体类的某些属性,需要基于该属性动态添加其他属性

例如:实体类中有枚举,枚举返回的时对应的code, 为了前端展示,有时候就需要将枚举code代表的name名称也返回。或者,响应前端时,需要对源数据进行加工,生成新的属性。


解决方案:

通过继承 BeanSerializerModifier 重写里面的 changeProperties 方法实现

步骤一:定义注解

import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
//通用返回字段扩展
public @interface DataExtend {
    //前缀
    String prefix() default "";
    //后缀 - (prefix,suffix,name)都为空时默认添加后缀【Extend】
    String suffix() default "";
    //自定义扩展字段名
    String name() default "";
    //扩展字段值
    String function() default "";
    //扩展字段值返回值
    Class type() default String.class;
}


import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
//加密返回字段扩展
public @interface EncryptDataExtend {
    //前缀 - (prefix,suffix,name)都为空时默认添加前缀【encrypt】
    String prefix() default "";
    //后缀
    String suffix() default "";
    //自定义扩展字段名
    String name() default "";
    //业务id
    String businessId();
}


import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
//枚举返回字段扩展
public @interface EnumDataExtend {
    //前缀
    String prefix() default "";
    //后缀 - (prefix,suffix,name)都为空时默认添加后缀 返回字段【retField】(首字母大写)
    String suffix() default "";
    //自定义扩展字段名
    String name() default "";
    //返回字段
    String retField();
}

步骤二:实现上面3个注解的序列化

  1. 通用返回字段扩展-序列化
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;

public class DataExtendSerializer extends JsonSerializer<Object> {
   private String function;

   private Class type;

   public DataExtendSerializer() {
   }

   public DataExtendSerializer(final String function, final Class type) {
       this.function = function;
       this.type = type;
   }

   @SneakyThrows
   @Override
   public void serialize(final Object val, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) {
       Object data = SpELUtil.getExpressionValue(function, JSONUtil.parseObj(jsonGenerator.getCurrentValue()), jsonGenerator.getCurrentValue(), type);
       jsonGenerator.writeObject(data);
   }
}
  1. 加密返回字段扩展-序列化
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;

public class EncryptDataExtendSerializer extends JsonSerializer<Object> {
    private String businessId;


    public EncryptDataExtendSerializer() {
    }

    public EncryptDataExtendSerializer(final String bus) {
        this.businessId = businessId;
    }

    @SneakyThrows
    @Override
    public void serialize(final Object val, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) {
        if (val != null) {
            String value = EncryptDataUtils.encrypt(businessId, val.toString());
            jsonGenerator.writeString(value);
        } else {
            jsonGenerator.writeString("");
        }
    }
}
  1. 枚举返回字段扩展-序列化
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;

public class EnumDataExtendSerializer extends JsonSerializer<Object> {
   private String retField;


   public EnumDataExtendSerializer() {
   }

   public EnumDataExtendSerializer(final String retField) {
       this.retField = retField;
   }

   @SneakyThrows
   @Override
   public void serialize(final Object val, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) {
       Class<?> currentClass = val.getClass();
       Field codeField = ReflectionUtils.findField(currentClass, retField);
       assert codeField != null;
       ReflectionUtils.makeAccessible(codeField);
       Object codeStr = ReflectionUtils.getField(codeField, val);
       jsonGenerator.writeObject(codeStr);
   }
}

步骤三:工具类实现

1. SpEL工具类

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.Expression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Map;

@Component
@Slf4j
public class SpELUtil implements BeanFactoryAware {
   //定义解析的模板
   private static final TemplateParserContext PARSER_CONTEXT = new TemplateParserContext();
   //定义解析器
   private static final SpelExpressionParser PARSER = new SpelExpressionParser();
   //获取到Spring容器的beanFactory对象
   private BeanFactory beanFactory = null;
   private static BeanFactory staticBeanFactory = null;

   @Override
   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
       this.beanFactory = beanFactory;
   }

   @PostConstruct
   private void init() {
       staticBeanFactory = beanFactory;
   }

   @SneakyThrows
   public static <T> T getExpressionValue(String typeExpression, Map<String, Object> variables, Object rootData, Class<T> desiredResultType) {
       StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
       if (variables != null) {
           evaluationContext.setVariables(variables);
       }

       if (typeExpression.contains("@")) {
           evaluationContext.setBeanResolver(new BeanFactoryResolver(staticBeanFactory));
       }

       if (typeExpression.contains("$")) {
           typeExpression = resolve(typeExpression);
       }

       Expression expression = null;
       if (typeExpression.startsWith("#{")) {
           expression = PARSER.parseExpression(typeExpression, PARSER_CONTEXT);
       } else {
           expression = PARSER.parseExpression(typeExpression);
       }

       if (rootData != null) {
           return expression.getValue(evaluationContext, rootData, desiredResultType);
       }
       return expression.getValue(evaluationContext, desiredResultType);
   }

   /**
    * 作用是读取yml里面的值
    *
    * @param value 例如:1. #{${ttt.xxx}}会读取yml的ttt.xxx: read配置值,替换为#{read}
    *              2.#{read}直接返回#{read}
    * @return #{read}
    */
   private static String resolve(String value) {
       if (staticBeanFactory != null && staticBeanFactory instanceof ConfigurableBeanFactory) {
           return ((ConfigurableBeanFactory) staticBeanFactory).resolveEmbeddedValue(value);
       }
       return value;
   }
}
1. 数据加密工具类


import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;

@Component
@Slf4j
public class EncryptDataUtils {

   @PostConstruct
   private void init() {

   }

   //此处可以调用service服务获取该业务对应的24位密钥
   private static String getPrivateKey(String businessId){
       String privateKey = StrUtil.fillBefore(String.valueOf(businessId.hashCode()),'0',12) + StrUtil.fillBefore(String.valueOf(StrUtil.reverse(businessId).hashCode()),'0',12);
       return privateKey;
   }

   private static String getKey(String businessId, String decryptText){
       String publicKey = decryptText.substring(0, 8);
       String privateKey = getPrivateKey(businessId);
       return publicKey + privateKey;
   }

   //通过huTool工具包进行加密
   //key为自定义密钥
   public static String encrypt(String businessId,String encryptText){
       //生成随机8位数密钥
       String publicKey = RandomUtil.randomNumbers(8);
       byte[] bytes = (publicKey + getPrivateKey(businessId)).getBytes(StandardCharsets.UTF_8);
       //在密钥生成时必须为128/192/256 bits(位),本案例中使用256位
       //故需进行判断
       //byte,一字节,8位,故需要达到256位,需要32字节
       if (bytes.length!=32){
           //创建32字节的byte数组
           byte[] b = new byte[32];
           if (bytes.length<32){
               //将自定义密钥添加到b数组
               /**
                * 方法:System.arraycopy
                * 参数:
                * src:the source array要插入的数组
                * srcPos:starting position in the source array插入数组的起始位置
                * dest:the destination array被插入的数组
                * destPos:starting position in the destination data被插入数组插入时的起始位置
                * length:the number of array elements to be copied要插入的数组的长度
                */
               System.arraycopy(bytes,0,b,0,bytes.length);
           }
           bytes=b;
       }
       //构建
       SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, bytes);
       //加密为16进制表示
       String encryptHex = aes.encryptHex(encryptText);
       return publicKey + encryptHex;
   }


   //通过huTool工具包进行解密
   //key为自定义密钥
   public static String decrypt(String businessId, String decryptText){
       //生成密钥
       byte[] bytes = getKey(businessId, decryptText).getBytes(StandardCharsets.UTF_8);
       if (bytes.length!=32){
           byte[] b = new byte[32];
           if (bytes.length<32){
               System.arraycopy(bytes,0,b,0,bytes.length);
           }
           bytes=b;
       }
       //构建
       SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, bytes);
       //解密为字符串
       String decryptStr = aes.decryptStr(decryptText.substring(8), CharsetUtil.CHARSET_UTF_8);
       return decryptStr;
   }
}

步骤四:继承BeanSerializerModifier重写changeProperties方法

注意:需将该java文件创建到 com.fasterxml.jackson.databind.ser目录下

package com.fasterxml.jackson.databind.ser;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.SerializationConfig;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;

public class DataExtendSerializerModifier extends BeanSerializerModifier {

    @Override
    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
        List<BeanPropertyWriter> li = new ArrayList<>();
        for (BeanPropertyWriter writer : beanProperties) {
            DataExtend dataExtend = writer.getAnnotation(DataExtend.class);
            if (dataExtend != null) {
                String simpleName = writer.getName();
                if (StringUtils.isNotBlank(dataExtend.name())) {
                    simpleName = dataExtend.name();
                } else {
                    if (StringUtils.isNotBlank(dataExtend.prefix())) {
                        simpleName = dataExtend.prefix() + StrUtil.upperFirst(simpleName);
                    }
                    if (StringUtils.isNotBlank(dataExtend.suffix())) {
                        simpleName += dataExtend.suffix();
                    }
                }
                if (writer.getName().equals(simpleName)) {
                    simpleName += "Extend";
                }
                BeanPropertyWriter newExtend = new BeanPropertyWriter(writer, PropertyName.construct(simpleName));
                if (newExtend.hasSerializer()) {
                    newExtend._serializer = null;
                }
                //更改序列化器
                newExtend.assignSerializer(new DataExtendSerializer(dataExtend.function(), dataExtend.type()));
                li.add(newExtend);
            }


            EnumDataExtend enumDataExtend = writer.getAnnotation(EnumDataExtend.class);
            if (enumDataExtend != null) {
                if (Enum.class.isAssignableFrom(writer.getType().getRawClass())) {
                    String simpleName = writer.getName();
                    if (StringUtils.isNotBlank(enumDataExtend.name())) {
                        simpleName = enumDataExtend.name();
                    } else {
                        if (StringUtils.isNotBlank(enumDataExtend.prefix())) {
                            simpleName = enumDataExtend.prefix() + StrUtil.upperFirst(simpleName);
                        }
                        if (StringUtils.isNotBlank(enumDataExtend.suffix())) {
                            simpleName += enumDataExtend.suffix();
                        }
                    }
                    if (writer.getName().equals(simpleName)) {
                        simpleName += StrUtil.upperFirst(enumDataExtend.retField());
                    }
                    BeanPropertyWriter newExtend = new BeanPropertyWriter(writer, PropertyName.construct(simpleName));
                    if (newExtend.hasSerializer()) {
                        newExtend._serializer = null;
                    }
                    //更改序列化器
                    newExtend.assignSerializer(new EnumDataExtendSerializer(enumDataExtend.retField()));
                    li.add(newExtend);
                }
            }

            EncryptDataExtend encryptDataExtend = writer.getAnnotation(EncryptDataExtend.class);
            if (encryptDataExtend != null) {
                String simpleName = writer.getName();
                if (StringUtils.isNotBlank(encryptDataExtend.name())) {
                    simpleName = encryptDataExtend.name();
                } else {
                    if (StringUtils.isNotBlank(encryptDataExtend.prefix())) {
                        simpleName = encryptDataExtend.prefix() + StrUtil.upperFirst(simpleName);
                    }
                    if (StringUtils.isNotBlank(encryptDataExtend.suffix())) {
                        simpleName += encryptDataExtend.suffix();
                    }
                }
                if (writer.getName().equals(simpleName)) {
                    simpleName = "encrypt" + StrUtil.upperFirst(simpleName);
                }
                BeanPropertyWriter newExtend = new BeanPropertyWriter(writer, PropertyName.construct(simpleName));
                if (newExtend.hasSerializer()) {
                    newExtend._serializer = null;
                }
                //更改序列化器
                newExtend.assignSerializer(new EncryptDataExtendSerializer(encryptDataExtend.businessId()));
                li.add(newExtend);
            }
        }
        beanProperties.addAll(li);
        return beanProperties;
    }

}

步骤五:将修改ObjectMapper的SerializerFactory

针对自定义MappingJackson2HttpMessageConverter的情况

	@Bean
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverterConfiguration() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper om = new ObjectMapper();
		//设置使用DataExtendSerializerModifier的SerializerFactory
        om.setSerializerFactory(om.getSerializerFactory().withSerializerModifier(new DataExtendSerializerModifier()));
        converter.setObjectMapper(om);
        return converter;
    }

未配置的情况可以参考如下配置:

	@Bean
    @Primary
    ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper mapper = builder.createXmlMapper(false).build();
        mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new DataExtendSerializerModifier()));
        return mapper;
    }

使用示例:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TestEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 任务批次名称简称
     */
    @DataExtend(function = "#idCard+'DataExtend'")
    @EncryptDataExtend(businessId = "111111")
    private String idCard;

    @EnumDataExtend(retField = "name")
    @ApiModelProperty(value = "测试Enum")
    private TestEnum testEnum;
}

枚举如下:
在这里插入图片描述

返回对象:

{
  "idCard": "321322**********31",
  "testEnum": "2",
  "idCardExtend": "321322**********31DataExtend",
  "encryptIdCard": "92564224dae26b5f0c14b108d31*************83fdcae97480d0e4",
  "testEnumName": "vb"
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值