源码分析 - 数据的变更比对(一)
需求:保存表单信息,记录与上一版本表单的区别,将需要关注的指定字段打上变更标记(颜色),效果如下图所示。我们需要做的,就是在新的对象数据中,将变更的字段及变更的内容标识出来。

前言
因为博主之前没有做过这类数据处理,于是在码云上找到了一个开源的变更比对的工具类Rayen/Coconut,我们来一步步的分析它是如何完成数据变更比对的,并应用到实际的项目使用中去。
一、代码结构

二、业务对象
1.业务实体类
示例中使用到的Business实体类为一个复杂业务嵌套对象,一共包含两层嵌套,一共三级。
// 业务对象示例
{
"id":"1",
"bizNum":"BH2024102501",
"businessDetailList":[{
"id":"10",
"bizId":"1",
"desc":"",
"employeeList":[{
"id":"101",
"detailID":"10",
"birthDay":"1999-10-10"
},{
"id":"11",
"bizId":"1",
"desc":"业务详情2描述.....",
"employeeList":[{
"id":"102",
"detailID":"10",
"birthDay":"2001-01-10"
}]
}]
}
1)业务类-Business

2)业务详情类-BusinessDetail

3)业务详情雇员类-Employee

三、源码分析
1.读取嵌套对象Business的字段及字段值
第一步:我们需要对嵌套对象Business进行解析,对所有字段进行遍历 -> 获取所有的字段值。
那么面对一个多层嵌套的对象,应该怎么去获取这些信息呢?
1)自定义注解
可以发现,在实体类中使用了许多自定义注解。
@DiffIgnore:忽略不比较的属性(字段)
@EntityTag:部件标识,区分各个复合属性
@PropertyField:属性域注解,可自定义属性别名,属性展示顺序
@UnionId:标识对象的唯一性,匹配新旧版本需要对比的对象,支持多个属性联合组成唯一ID
@CollectionFlag 集合/复合属性标识(基本类型、对象、集合)
@PropertyComparator 属性比较器,支持自定义属性比较方法(暂未使用到)
博主认为其中需要重点关注的是:@EntityTag、@UnionId、@CollectionFlag
2)嵌套对象内容读取
字段基础对象 TypePojo
package com.art.coconut.change;
import com.art.coconut.compare.Comparator;
import lombok.Data;
import java.lang.reflect.Type;
/**
* 字段信息类 TypePojo
*/
@Data
public class TypePojo {
// 排序
private float order;
// 字段名
private String propertyName;
// 字段类型
private Type type;
// 获取字段基础类型
private String typeName;
// 字段值
private Object object;
// 比较类
private Comparator comparator;
// 类
private Class<?> typeClass;
// 实体类标识
private String entityTag;
}
流程处理类 ProcessHandle
package com.art.coconut.utils;
import com.art.coconut.annotation.*;
import com.art.coconut.change.TypePojo;
import com.art.coconut.compare.BaseComparator;
import com.art.coconut.enums.ExceptionEnum;
import com.art.coconut.enums.PropertyTypeEnum;
import com.art.coconut.exception.CoconutCommonException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
/**
* 流程处理类
*/
public class ProcessHandle {
/**
* 获取对象唯一id
* 通过反射,根据@UnionId主解获取对象主键
* eg:@UnionId
* private String id;
* @param obj
* @return
* @throws IllegalAccessException
*/
public static String getUniqueId(Object obj) throws IllegalAccessException {
String key = null;
Field[]fields = obj.getClass().getDeclaredFields();
StringBuilder idSb = new StringBuilder();
boolean hasUniqueId = Boolean.FALSE;
for (Field field : fields) {
UnionId id = field.getAnnotation(UnionId.class);
if(id!=null){
hasUniqueId = true;
field.setAccessible(true);
idSb.append(field.get(obj));
idSb.append("@");
}
}
if(hasUniqueId){
key = idSb.substring(0, idSb.lastIndexOf("@"));
}
if(key==null){
throw new CoconutCommonException(ExceptionEnum.NO_ID_EXCEPTION.getCode(), ExceptionEnum.NO_ID_EXCEPTION.getMsg());
}
return key;
}
/**
* 传入对象类,获取对象类注解名称
* eg:@EntityTag("BUSINESS")
* @param tClass
* @return
*/
public static String getEntityTag(Class<?> tClass){
EntityTag tag = tClass.getAnnotation(EntityTag.class);
return Objects.isNull(tag) ? null: tag.value();
}
/**
* 将集合转为Map
* eg:主键 -> Business(id=1, bizNum=100111100, businessDetailList=[BusinessDetail(id=11, bizId=1, desc=A, employeeList=[Employee(id=em_1, detailID=11, birthDay=Mon Dec 16 17:00:13 CST 2024)])])
* @param version
* @return
* @throws IllegalAccessException
*/
public static Map<Object, Object> collectionToMap(Collection<?> version) throws IllegalAccessException {
Map<Object, Object> versionMap = new HashMap<>();
for (Object o : version) {
String key = getUniqueId(o);
versionMap.put(key, o);
}
return versionMap;
}
/**
* 将指定类对象转为统一TypePojo对象Map
* eg:明细ID -> TypePojo(order=0.0, propertyName=明细ID, type=class java.lang.String, typeName=java.util.Map, object=11, comparator=com.art.coconut.compare.BaseComparator@21bd128b, typeClass=class com.art.coconut.example.model.BusinessDetail, entityTag=BUSINESS_DETAIL)
* @param object
* @param refClass
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static Map<String, TypePojo> objectToMap(Object object, Class<?> refClass) throws IllegalAccessException, InstantiationException {
if(Objects.isNull(object)){
return new LinkedHashMap<>();
}
// 获取类的自身的所有字段,不包括父类的字段。
Field[]fields = refClass.getDeclaredFields();
List<TypePojo> typePojoList = new ArrayList<>(fields.length);
// 获取实体类标识
String location = null;
EntityTag entityTag = refClass.getAnnotation(EntityTag.class);
if(entityTag != null){
location = entityTag.value();
}else{
throw new CoconutCommonException(ExceptionEnum.NO_ENTITY_TAG_EXCEPTION.getCode(), ExceptionEnum.NO_ENTITY_TAG_EXCEPTION.getMsg());
}
// 遍历类字段
for (Field field : fields) {
// 排除标记比对忽略标识的字段
DiffIgnore diffIgnore = field.getAnnotation(DiffIgnore.class);
if(diffIgnore==null){
// 初始化typePojo对象
TypePojo typePojo = new TypePojo();
// 设置反射时可访问私有变量
field.setAccessible(true);
// 获取字段基础类型
typePojo.setTypeName(field.getType().getTypeName());
// 获取字段名及排序
PropertyField propertyNameAno = field.getAnnotation(PropertyField.class);
String propertyName = field.getName();
if(propertyNameAno!=null){
propertyName = propertyNameAno.value();
typePojo.setOrder(propertyNameAno.order());
}
// 获取字段属性标识:基本类型/对象/集合
CollectionFlag collectionFlag = field.getAnnotation(CollectionFlag.class);
if(collectionFlag == null){
// 不存在字段属性标识 -> 获取属性比较注解:是否自定义属性比较方法
PropertyComparator comparator = field.getAnnotation(PropertyComparator.class);
if(comparator!=null){
// 自定义属性比较方法
typePojo.setComparator(comparator.value().newInstance());
}else{
// 默认比较方法
typePojo.setComparator(new BaseComparator());
}
// 字段属性标识 默认是基本类型
typePojo.setTypeName(PropertyTypeEnum.BASE_TYPE.getTypeName());
}else {
// 根据注解获取 字段属性标识
typePojo.setTypeName(collectionFlag.value().getTypeName());
}
// 指定类
typePojo.setTypeClass(field.getDeclaringClass());
// 字段名
typePojo.setPropertyName(propertyName);
// 字段类型
typePojo.setType(field.getType());
// 字段值
typePojo.setObject(field.get(object));
// 实体类标识
typePojo.setEntityTag(location);
typePojoList.add(typePojo);
// resMap.put(propertyName, typePojo);
}
}
// Map<String, TypePojo> linkedMap = new LinkedHashMap<>();
// resMap.values().stream().sorted(Comparator.comparing(TypePojo::getOrder)).forEach(t->{
// linkedMap.put(t.getPropertyName(), t);
// });
return typePojoList.stream()
.sorted(Comparator.comparing(TypePojo::getOrder))
.collect(Collectors.toMap(TypePojo::getPropertyName, e->e, throwingMerger(), LinkedHashMap::new));
}
/**
* 重复key抛出异常
* @return
* @param <T>
*/
private static <T> BinaryOperator<T> throwingMerger() {
return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
}
}



浙公网安备 33010602011771号