一、需求:
编辑时,需要判断每一个字段是否有变化并记录。
如果有变化,需要记录变化的字段信息:包括字段中文名称、字段的英文key、字段变更前的值、字段变更后的值。
ps:字段有可能是枚举,即数据库存储的是英文code,前端显示的是code对应中文(一般枚举这种,接口交互使用英文code,数据库存储也是英文code)。此时变更前、后的值,需要把英文code和中文名称全部记录下来。
即最终要记录的,是:字段的英文key、字段中文名称、字段变更前的数据库值、字段变更前的中文显示值、字段变更后的数据库值、字段变更后的中文显示值。
二、只使用反射的不足:
纯粹使用反射,只能拿到:字段的英文key、字段变更前的数据库值、字段变更后的数据库值。
缺失:字段中文名称、字段变更前的中文显示值、、字段变更后的中文显示值。
三、自定义注解+反射:
3-1、自定义注解:
在要比较的对象的属性上,添加自定义注解,注解可以标注每一个属性的对应中文名称、相应枚举情况
import java.lang.annotation.*;
/**
* 自定义注解
* 使用在属性上
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AttributeDesc {
//属性名称
String notes() default "";
//属性是否属于枚举类型,""不是,如果不为"",比如是"card-type",那说明,此是枚举类,并且对应的枚举的关键字是"card-type",只要从数据字典内,类型是"card-type"的一批数据里去匹配就好
String enumCode() default "";
}
3-2、实体类内使用:
字段的英文key为"stockholderCardType"的中文解释是"股东证件类型",是枚举,枚举对应的数据字典类型是"card-type",由"@AttributeDesc(notes = "股东证件类型", enumCode = "card-type")"标记出。字段数据库值 存储 对象反射得到的值,字段中文显示值存储,通过 对象反射得到的值 和 类型"card-type",去数据字典内查询到的中文值。
字段的英文key为"legalPerson"的中文解释是"法定代表人",不是枚举,由"@AttributeDesc(notes = "法定代表人")"标记出。字段数据库值 和 字段中文显示值,存储成一样的,都是 通过对象反射得到的值。
public class demo{
@AttributeDesc(notes = "股东证件类型", enumCode = "card-type")
private String stockholderCardType;//股东证件类型(数据字典card-type)
@AttributeDesc(notes = "法定代表人")
private String legalPerson;//法定代表人
public String getStockholderCardType() {
return stockholderCardType;
}
public void setStockholderCardType(String stockholderCardType) {
this.stockholderCardType = stockholderCardType;
}
public String getLegalPerson() {
return legalPerson;
}
public void setLegalPerson(String legalPerson) {
this.legalPerson = legalPerson;
}
}
3-3、反射的比较方法:
此处暂时只写了String类型的属性判断
/**
* 比较两个对象内属性值,返回变更详情
* 标记了自定义注解的属性,才进行比较
*
* @param oldDb 旧实体类
* @param newDb 新实体类
* @return 变更详情
*/
public JSONArray compileTwoObjects(Object oldDb, Object newDb) {
JSONArray jsonArray = new JSONArray();
//旧实体转为类
Class<Object> oldClass = (Class<Object>) oldDb.getClass();
//新实体转为类
Class<Object> newClass = (Class<Object>) newDb.getClass();
//获取实体类内属性列表(需要比较的两个对象,应该是类相同,或者对象内字段一致,不然比较无意义)
Field[] oldFiles = oldClass.getDeclaredFields();//获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段
//遍历属性-比较差异
for (Field field : oldFiles) {
//获取属性的get方法名称
String getMethodName = "get"
+ field.getName().substring(0, 1).toUpperCase()
+ field.getName().substring(1);
try {
//通过方法名称获取旧实体类对应方法
Method oldMeth = (Method) oldClass.getMethod(getMethodName);
//通过方法名称获取新实体类对应方法
Method newMeth = (Method) newClass.getMethod(getMethodName);
//获取属性上自定义注解
AttributeDesc meta = field.getAnnotation(AttributeDesc.class);
try {
//属性上有注解,才需要进行比较
if (meta != null) {
//注解-属性名称
String note = meta.notes();
//属性是否属于枚举类型:""不是,不是""的话没说明此字段是枚举类,数据库存储的和要显示的不一致
String enumCode = meta.enumCode();
//对象-旧属性值
Object oldValObj= oldMeth.invoke(oldDb);
//对象-新属性值
Object newValObj = newMeth.invoke(newDb);
//比较
if (oldValObj instanceof String) {
//字符串可以处理Null
String oldVal = (String) oldValObj;
String newVal = (String) newValObj;
if (!oldVal.equals(newVal)) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("变更字段",field.getName());
jsonObject.put("变更字段中文",note);
jsonObject.put("变更前值",oldVal);
if (!StringUtils.isEmpty(enumCode) && !StringUtils.isEmpty(oldVal)) {
SysDictionary sysDictionary = sysDictionaryService.getSysDictionary(enumCode, oldVal);
if (null != sysDictionary) {
jsonObject.put("变更前值名称",sysDictionary.getKeyDesc());
}
} else {
jsonObject.put("变更前值名称",oldVal);
}
jsonObject.put("变更后值",newVal);
if (!StringUtils.isEmpty(enumCode) && !StringUtils.isEmpty(newVal)) {
SysDictionary sysDictionary = sysDictionaryService.getSysDictionary(enumCode, newVal);
if (null != sysDictionary) {
jsonObject.put("变更后值名称",sysDictionary.getKeyDesc());
}
} else {
jsonObject.put("变更后值名称",newVal);
}
jsonArray.add(jsonObject);
}
}
//TODO 其他类型
}
} catch (IllegalAccessException e) {
log.debug("private修饰的成员(变量和方法),可以通过setAccessible(true)暴力获取");
} catch (IllegalArgumentException e) {
log.debug("无法转换类型导致 java.lang.IllegalArgumentException(注意:反射获取或者修改一个变量的值时,编译器不会进行自动装/拆箱,所以int 和Integer需手动修改)");
} catch (InvocationTargetException e) {
log.debug("由Method.invoke(obj, args...)方法抛出.当被调用的方法的内部抛出了异常而没有被捕获时,将由此异常接收");
}
} catch (NoSuchMethodException e) {
log.debug("由getMethod(String name, Class<?>... parameterTypes)方法抛出.没有这个方法可显示调用");
} catch (SecurityException e) {
log.debug("安全异常");
}
}
return jsonArray;
}
四、最终结果:

本文介绍了在编辑场景下,如何使用Java反射和自定义注解来比较新旧对象属性的变化,包括记录字段的中英文名称、前后值,并特别处理了枚举类型的转换。传统反射方法无法获取字段的中文名称和中文显示值,而通过自定义注解,可以在实体类属性上标注中文解释和枚举信息,从而实现完整的信息记录。
1961

被折叠的 条评论
为什么被折叠?



