业务场景
某高校教职工职称评审项目,有一个“一表通”信息维护模块,教职工可对自己的各方面信息(比如基本情况、论文著作等)进行纠错,然后每次修改发生变化的内容需要对应的审核部门进行审核通过以后才能真正修改库数据。教职工的信息项多则40~50项,总不能写一堆的if-else对每个字段进行判断,然后再筛选出发生变化的字段,这样太累了!更何况信息项有加有减的时候我还得去维护相应的代码。
最后想到用Java的反射机制并结合自定义属性注解,拿到新旧对象所有的属性集合,包括属性名称(name)、属性中文注释(姓名)以及属性值(张三),然后拿着两个属性集合再进行比对,拿到本次修改发生变化的属性信息。
代码实现
1、创建一个实体类对象 StaffBaseInfo【教职工基本信息】
@Data
@TableName("staff_base_info")
public class StaffBaseInfoDO implements Serializable {
@TableId
private String id;
/**
* 单位号
*/
@PropertyName(name = "单位号")
private String deptNumber;
/**
* 工号
*/
@PropertyName(name = "工号")
private String workNumber;
/**
* 姓名
*/
@PropertyName(name = "姓名")
private String name;
/**
* 性别
*/
@PropertyName(name = "性别", isDict = true)
private String gender;
/**
* 出生日期
*/
@PropertyName(name = "出生日期")
private String birthday;
/**
* 身份证件类型
*/
@PropertyName(name = "身份证件类型", isDict = true)
private String idType;
/**
* 身份证件号码
*/
@PropertyName(name = "身份证件号码")
private String idNumber;
/**
* 民族
*/
@PropertyName(name = "民族", isDict = true)
private String nation;
/**
* 政治面貌
*/
@PropertyName(name = "政治面貌", isDict = true)
private String politicsStatus;
/**
* 参加工作时间
*/
@PropertyName(name = "参加工作时间")
private String workTime;
/**
* 到校(院)工作时间
*/
@PropertyName(name = "到校(院)工作时间")
private String schoolWorkTime;
/**
* 最高学历
*/
@PropertyName(name = "最高学历", isDict = true)
private String highestEducation;
/**
* 最高学位
*/
@PropertyName(name = "最高学位", isDict = true)
private String highestDegree;
........省略N多属性.........
}
其中,自定义了一个注解@PropertyName,它有两个属性,name代表的是属性的中文注释,isDict代表该属性是否为字典项,默认为false;如果设置为true,则代表该属性存储的是字典代码,需要将字典项代码翻译成对应的字典项名称,具体如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PropertyName {
// 字段属性中文注释
String name() default "";
// 字段属性是否为字典项代码
boolean isDict() default false;
}
2、创建 PropertyModelInfo 类,利用反射机制拿到的字段属性信息
@Data
public class PropertyModelInfo {
/**
* 属性名称
*/
private String propertyName;
/**
* 属性注释
*/
private String propertyComment;
/**
* 属性值
*/
private Object value;
/**
* 返回值类型
*/
private Class<?> returnType;
}
3、创建 ModifiedPropertyInfo 类,用于记录修改后发生变化的字段属性信息
@Data
public class ModifiedPropertyInfo implements Serializable {
/**
* 发生变化的属性名称
*/
private String propertyName;
/**
* 发生变化的属性注释
*/
private String propertyComment;
/**
* 修改前的值
*/
private Object oldValue;
/**
* 修改后的值
*/
private Object newValue;
}
4、创建工具类 CompareObjectPropertyUtil,用了比较两个实体对象的属性是否发生变化
public class CompareObjectPropertyUtil {
private static final Logger log = LoggerFactory.getLogger(CompareObjectPropertyUtil.class);
/**
* 比较两个对象不同的属性并记录返回
*
* @param oldObj 旧对象
* @param newObj 新对象
* @param ignoreProperties 可忽略对比的属性
* @return java.util.List<com.sdyy.staff.utils.ModifiedPropertyInfo>
*/
public static <T> List<ModifiedPropertyInfo> getDifferentProperty(T oldObj , T newObj , String... ignoreProperties){
if (oldObj != null && newObj != null) {
// 比较全部字段
if (ignoreProperties == null || ignoreProperties.length > 0) {
if (oldObj.equals(newObj)) {
return Collections.emptyList();
}
}
List<PropertyModelInfo> oldObjectPropertyValue = getObjectPropertyValue(oldObj, ignoreProperties);
if (!CollectionUtils.isEmpty(oldObjectPropertyValue)) {
List<ModifiedPropertyInfo> modifiedPropertyInfos = new ArrayList<>(oldObjectPropertyValue.size());
List<PropertyModelInfo> newObjectPropertyValue = getObjectPropertyValue(newObj, ignoreProperties);
Map<String , Object> objectMap = new HashMap<>(newObjectPropertyValue.size());
// 获取新对象所有属性值
for (PropertyModelInfo propertyModelInfo : newObjectPropertyValue) {
String propertyName = propertyModelInfo.getPropertyName();
Object value = propertyModelInfo.getValue();
objectMap.put(propertyName , value);
}
for (PropertyModelInfo propertyModelInfo : oldObjectPropertyValue) {
String propertyName = propertyModelInfo.getPropertyName();
String propertyComment = propertyModelInfo.getPropertyComment();
Object value = propertyModelInfo.getValue();
if (objectMap.containsKey(propertyName)) {
Object newValue = objectMap.get(propertyName);
ModifiedPropertyInfo modifiedPropertyInfo = new ModifiedPropertyInfo();
if (value != null && newValue != null) {
if (!value.equals(newValue)) {
modifiedPropertyInfo.setPropertyName(propertyName);
modifiedPropertyInfo.setPropertyComment(propertyComment);
modifiedPropertyInfo.setOldValue(value);
modifiedPropertyInfo.setNewValue(newValue);
modifiedPropertyInfos.add(modifiedPropertyInfo);
}
} else if((newValue == null && value != null && !StringUtils.isBlank(value.toString()))
|| (value== null && newValue != null && !StringUtils.isBlank(newValue.toString()))) {
modifiedPropertyInfo.setPropertyName(propertyName);
modifiedPropertyInfo.setPropertyComment(propertyComment);
modifiedPropertyInfo.setOldValue(value);
modifiedPropertyInfo.setNewValue(newValue);
modifiedPropertyInfos.add(modifiedPropertyInfo);
}
}
}
return modifiedPropertyInfos;
}
}
return Collections.emptyList();
}
/**
* 通过反射获取对象的属性名称、getter返回值类型、属性值等信息
*
* @param obj 对象
* @param ignoreProperties 可忽略比对的属性
* @return java.util.List<com.sdyy.staff.utils.PropertyModelInfo>
*/
public static <T> List<PropertyModelInfo> getObjectPropertyValue(T obj , String... ignoreProperties){
if (obj != null) {
Class<?> objClass = obj.getClass();
PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(objClass);
List<PropertyModelInfo> modelInfos = new ArrayList<>(propertyDescriptors.length);
Field[] fields = objClass.getDeclaredFields();
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for(Field field : fields) {
field.setAccessible(true);
String fieldName = field.getName();
if(ignoreList == null || !ignoreList.contains(fieldName)) {
PropertyModelInfo propertyModelInfo = new PropertyModelInfo();
propertyModelInfo.setPropertyName(fieldName);
propertyModelInfo.setReturnType(field.getType());
Object fieldValue = getFieldValueByName(fieldName, obj);
// 通过自定义注解拿到属性注释
if(field.isAnnotationPresent(PropertyName.class)) {
PropertyName annotation = field.getAnnotation(PropertyName.class);
propertyModelInfo.setPropertyComment(annotation.name());
// 如果是字典项,则将code转换成name
// 注意:获取字典项这部分代码需要结合自己的业务实现,我这里是从静态文件加载进来的
if(annotation.isDict()) {
List<DictDTO> dictList = JSONObject.parseArray(JSONObject.toJSONString(DictionaryConstant.dictionaryMap.get(fieldName)), DictDTO.class);
Object finalFieldValue = fieldValue;
if(finalFieldValue != null && !"".equals(finalFieldValue)) {
Optional<DictDTO> first = dictList.stream().filter(dictDTO -> dictDTO.getValue().equals(String.valueOf(finalFieldValue))).findFirst();
if(first.isPresent()) {
fieldValue = first.get().getText();
}
}
}
}
propertyModelInfo.setValue(fieldValue == null ? "":fieldValue);
modelInfos.add(propertyModelInfo);
}
}
return modelInfos;
}
return Collections.emptyList();
}
private static Object getFieldValueByName(String fieldName, Object o) {
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = o.getClass().getMethod(getter, new Class[] {});
Object value = method.invoke(o, new Object[] {});
return value;
} catch (Exception e) {
log.error(e.getMessage(),e);
return null;
}
}
}
5、测试
public static void main(String[] args) {
// 修改前数据
StaffBaseInfoDO oldStaff = new StaffBaseInfoDO();
oldStaff.setName("张三");
oldStaff.setBirthday("1987-01-02");
oldStaff.setBirthPlace("北京市");
// 最高学位为字典项
oldStaff.setHighestDegree("408");
// 修改后数据
StaffBaseInfoDO newStaff = new StaffBaseInfoDO();
newStaff.setName("张三");
newStaff.setBirthday("1987-01-02");
newStaff.setBirthPlace("山东济南");
newStaff.setHighestDegree("308");
List<ModifiedPropertyInfo> differentProperty = CompareObjectPropertyUtil.getDifferentProperty(oldStaff, newStaff);
System.out.println("本次修改共计发生"+differentProperty.size()+"处变化,具体如下所示:");
differentProperty.forEach(diff -> {
System.out.println(diff.getPropertyComment() + ":"+ "修改前为【"+diff.getOldValue() + "】,修改后变为【"+diff.getNewValue()+"】");
});
}
打印结果如下:
本次修改共计发生2处变化,具体如下所示:
出生地:修改前为【北京市】,修改后变为【山东济南】
最高学位:修改前为【工学学士学位】,修改后变为【工学硕士学位】
结语
至此,简单的比较修改前后对象发生变化的属性工具类就完成了,如果实体类属性有新增或减少,只需要维护好实体类本身就可以,不需要改动工具类里面的逻辑。