项目场景:
使用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个注解的序列化
- 通用返回字段扩展-序列化
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);
}
}
- 加密返回字段扩展-序列化
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("");
}
}
}
- 枚举返回字段扩展-序列化
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"
}