场景
在很多情况下,尤其是单表的版本控制的时候,需要记录后台人员的操作日志,通常是用到两个java对象去坐对比,告诉后台人员,改了哪些字段,就好比[名字:李四->张三],名字由原来的李四变成了张三。那么不废话了,直接亮出我反射结合注解写的对象对比工具。
字段对比 自定义注解
import java.lang.annotation.*;
/**
* 字段比较注解
* @author liaoqian
* @since 2023/8/11
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface FiledCompara {
String value() ;
FiledComparaTypeEnum type() default FiledComparaTypeEnum.DEFAULT;
}
字典对比枚举类
import lombok.Getter;
@Getter
public enum FiledComparaTypeEnum {
DEFAULT("default", null),
SEX("sex", new SexFiledComparaType());
private String type;
private IFiledComparaType filedComparaType;
FiledComparaTypeEnum(String type, IFiledComparaType filedComparaType) {
this.type = type;
this.filedComparaType = filedComparaType;
}
public boolean isDefault() {
return DEFAULT.type.equals(type);
}
}
测试对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@FiledCompara("名字")
private String name;
@FiledCompara("密码")
private String password;
@FiledCompara("儿子")
private User son;
@FiledCompara(value = "性别",type = FiledComparaTypeEnum.SEX)
private Integer sex;
public User(String name, String password) {
this.name = name;
this.password = password;
}
public User(String name, Integer sex) {
this.name = name;
this.sex= sex;
}
}
接口
import java.util.Map;
public interface IFiledComparaType {
Map<String,String> map();
}
性别实现类
import java.util.HashMap;
import java.util.Map;
/**
* @author liaoqian
* @since 2023/8/24
*/
public class SexFiledComparaType implements IFiledComparaType {
@Override
public Map<String, String> map() {
Map<String, String> ans = new HashMap<>();
ans.put("1","男");
ans.put("0","女");
return ans;
}
}
对象对比工具类
package com.example.springboottest.util;
import com.example.springboottest.annotation.FiledCompara;
import com.example.springboottest.enums.FiledComparaTypeEnum;
import com.example.springboottest.enums.IFiledComparaType;
import java.lang.reflect.Field;
import java.util.*;
/**
* 对象比较工具
*
* @author liaoqian
* @since 2023/8/11
*/
public class ObjectComparaUtil {
private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new IdentityHashMap(8);
static {
primitiveWrapperTypeMap.put(Boolean.class, Boolean.TYPE);
primitiveWrapperTypeMap.put(Byte.class, Byte.TYPE);
primitiveWrapperTypeMap.put(Character.class, Character.TYPE);
primitiveWrapperTypeMap.put(Double.class, Double.TYPE);
primitiveWrapperTypeMap.put(Float.class, Float.TYPE);
primitiveWrapperTypeMap.put(Integer.class, Integer.TYPE);
primitiveWrapperTypeMap.put(Long.class, Long.TYPE);
primitiveWrapperTypeMap.put(Short.class, Short.TYPE);
}
public static Map<String, String> comparaObj(Object target, Object source) {
return comparaObj(target, source, null);
}
private static Map<String, String> comparaObj(Object target, Object source, Map<String, String> map) {
Map<String, String> ans = new LinkedHashMap<>(1 << 6);
if (source == null || target == null) {
throw new RuntimeException("比较的两个对象不能为空");
}
Class clazz = source.getClass();
if (!clazz.equals(target.getClass())) {
throw new RuntimeException("比较的两个对象类型不同");
}
Field[] fields = clazz.getDeclaredFields();
String format = " %s:%s->%s ";
for (Field field : fields) {
FiledCompara filedCompara = field.getAnnotation(FiledCompara.class);
if (filedCompara != null) {
field.setAccessible(true);
Object sourceFieldValue = null;
Object targetFieldValue = null;
try {
sourceFieldValue = field.get(source);
targetFieldValue = field.get(target);
} catch (Exception e) {
}
boolean notNull = sourceFieldValue != null && targetFieldValue != null;
boolean primitive = isPrimitiveType(field.getType());
if (!notNull) {
continue;
}
String entityKey = field.getName();
String value = filedCompara.value();
FiledComparaTypeEnum type = filedCompara.type();
value = value == null || "".equals(value.trim()) ? entityKey : value;
if (primitive) {
String sourceFieldValueStr = sourceFieldValue.toString();
String targetFieldValueStr = targetFieldValue.toString();
if (!sourceFieldValueStr.equals(targetFieldValueStr)) {
Map<String, String> valueDictMap = getValueDictMap(type);
String entityValue = String.format(format, value, valueDictMap.getOrDefault(sourceFieldValueStr,sourceFieldValueStr),valueDictMap.getOrDefault(targetFieldValueStr,targetFieldValueStr));
ans.put(entityKey, entityValue);
}
} else {
Map<String, String> sonMap = comparaObj(sourceFieldValue, targetFieldValue, ans);
if (!sonMap.isEmpty()) {
for (Map.Entry<String, String> entry : sonMap.entrySet()) {
ans.put(entityKey + "." + entry.getKey(), value + "." + entry.getValue());
}
}
}
}
}
return ans;
}
private static Map<String, String> getValueDictMap(FiledComparaTypeEnum filedComparaType) {
if (filedComparaType.isDefault()) {
return new HashMap<>();
}
IFiledComparaType filedComparaTypeImpl = filedComparaType.getFiledComparaType();
return filedComparaTypeImpl.map();
}
private static boolean isPrimitiveType(Class clazz) {
boolean ans = false;
if (clazz.isPrimitive()) {
return true;
}
boolean containsKey = primitiveWrapperTypeMap.containsKey(clazz);
if (containsKey) {
return true;
}
String simpleName = clazz.getSimpleName();
switch (simpleName) {
case "String":
ans = true;
break;
default:
break;
}
return ans;
}
}
单元对比测试一:简单对象对比
测试代码
@Test
public void tt() {
User sourceUser = new User("李四", "qwertyuiop");
User targetUser = new User("张三", "qwertyui0p");
Map<String, String> map = ObjectComparaUtil.comparaObj(sourceUser, targetUser);
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey()+" --- "+entry.getValue());
}
}
测试结果
name --- 名字:李四->张三
password --- 密码:qwertyuiop->qwertyui0p
单元对比测试二:复杂对象对比
测试代码
@Test
public void tt() {
User sourceUser = new User("张三", "qwertyuiop",new User("张三的儿子","123456",new User("张三的孙子女","666666")));
User targetUser = new User("张三", "qwertyui0p",new User("张三的儿子","12345678",new User("张三的孙子男","666666")));
Map<String, String> map = ObjectComparaUtil.comparaObj(sourceUser, targetUser);
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey()+" --- "+entry.getValue());
}
}
测试结果
password --- 密码:qwertyuiop->qwertyui0p
son.password --- 儿子.密码:123456->12345678
son.son.name --- 儿子.儿子.名字:张三的孙子女->张三的孙子男
使用了字典对比效果
测试代码
@Test
public void tt() {
User targetUser = new User("王小丫",0);
User sourceUser = new User("张三",1);
Map<String, String> map = ObjectComparaUtil.comparaObj(sourceUser, targetUser);
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey()+" --- "+entry.getValue());
}
}
测试结果
name --- 名字:王小丫->张三
sex --- 性别:女->男
亮点细节
- 纯原生代码,无三方依赖
- 支持复杂对象的对比
- 效率高,逻辑清晰
- 可扩展性高
- 这里要配合你对象版本的管理,比如同一个user对象v4和v3的对比要保存在表中
可扩展性
- 对象字段字典上对比有缺陷的地方