java对比两个对象的字段值(支持嵌套对象)
比较两个对象的字段属性值
equals的场景可以比较两个对象,但是不能知道具体是那个字段不一致。基于这种场景有了今天要说的这个需求。
我这里是需求是去比较新旧数据的差异,所有会去获取注解值,按照自己的需要去嵌入就可以了。
我这里需要获取获取字段的Swagger注解值去存起来,没有需求的直接把相应的代码删除就好。
参考了之前一个大佬写的,因为之前的不支持嵌套对象,就扩展了一下,去递归调用。
原文地址
对比工具类
package com.maple.springbootenv.util;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.maple.springbootenv.entity.Student;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
public class ObjectCompareUtil {
public static void main(String[] args) {
Student student1 = new Student();
Student student2 = new Student();
List<DataComparisonEntity> compareFields = compareFields(student1, student2, new ArrayList<>(), null);
System.out.println(JSON.toJSONString(compareFields));
String formatStr = format(compareFields);
System.out.println("变更字段:" + formatStr);
}
/**
* 比较两个相同对象字段值,返回一个差异对象list
*
* @param obj1 进行属性比较的对象1
* @param obj2 进行属性比较的对象2
* @param ignoreList 选择忽略比较的属性数组
* @return {@link List<DataComparisonEntity>}
*/
public static List<DataComparisonEntity> compareFields(Object obj1, Object obj2, List<DataComparisonEntity> container, List<String> ignoreList) {
// 忽略字段list
if (CollectionUtils.isEmpty(ignoreList)) {
ignoreList = new ArrayList<>();
}
// 必须为同一对象
if (obj1.getClass() != obj2.getClass()) {
throw new RuntimeException("参数1与参数2必须为同一类型对象");
} else if (obj1 instanceof List && obj2 instanceof List && CollectionUtil.isNotEmpty((List<?>) obj1)) {
// 字段为List
for (int i = 0; i < ((List<?>) obj1).size(); i++) {
try {
compareFields(((List<?>) obj1).get(i), ((List<?>) obj2).get(i), container, ignoreList);
} catch (IndexOutOfBoundsException e) {
logInfo("索引越界:" + " 【对象1】:" + obj1 + "; 【对象2】:" + obj2 + "; 【expMessage】:" + e);
}
}
return container;
}
// 获取所有属性描述
Class<?> clazz = obj1.getClass();
PropertyDescriptor[] propertyDescriptors = new PropertyDescriptor[0];
try {
propertyDescriptors = Introspector.getBeanInfo(clazz, Object.class).getPropertyDescriptors();
} catch (IntrospectionException e) {
e.printStackTrace();
}
// 遍历属性描述符
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
// 当前属性在忽略字段中,忽略比较当前字段
String fieldName = propertyDescriptor.getName();
if (ignoreList.contains(fieldName)) {
continue;
}
// 当前字段的get方法
Method readMethod = propertyDescriptor.getReadMethod();
// 调用get方法获取obj1当前字段值
Object fieldValue1 = null;
// 调用get方法获取obj2当前字段值
Object fieldValue2 = null;
try {
fieldValue1 = readMethod.invoke(obj1);
fieldValue2 = readMethod.invoke(obj2);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
// 是否为基本数据类型(包含string)【不是基本数据类型继续比较】
if (notBaseType(fieldValue1) && notBaseType(fieldValue2)) {
compareFields(fieldValue1, fieldValue2, container, ignoreList);
continue;
}
// 字段值不相同
if (!Objects.equals(fieldValue1, fieldValue2)) {
DataComparisonEntity comparisonEntity = DataComparisonEntity.builder()
.linkObject(clazz)
.filedName(fieldName)
.oldValue(fieldValue1)
.newValue(fieldValue2)
.swaggerNote(getSwaggerNote(clazz, fieldName))
.pageFieldName(getPageNote(clazz, fieldName))
.build();
container.add(comparisonEntity);
}
}
return container;
}
/**
* 格式化差异差异对象
*
* @param dataComparisonEntityList List<DataComparisonEntity>
* @return String
*/
public static String format(List<DataComparisonEntity> dataComparisonEntityList) {
final String gap = ":";
final String end = "; ";
final String changeChar = " 变更为-> ";
if (CollectionUtil.isEmpty(dataComparisonEntityList)) {
return null;
}
StringBuilder sb = new StringBuilder();
for (DataComparisonEntity dataComparisonEntity : dataComparisonEntityList) {
String changeDescName = gainFieldDesc(dataComparisonEntity);
sb.append("[");
sb.append(changeDescName);
sb.append("]");
sb.append(gap);
sb.append(dataComparisonEntity.getOldValue());
sb.append(changeChar);
sb.append(dataComparisonEntity.getNewValue());
sb.append(end);
}
sb.setLength(sb.length() - 2);
return new String(sb);
}
private static String gainFieldDesc(DataComparisonEntity entity) {
String swaggerNote = entity.getSwaggerNote();
String pageFieldName = entity.getPageFieldName();
// 有页面注解时直接取页面注解
if (StringUtils.isNotEmpty(pageFieldName)) {
return pageFieldName;
} else if (StringUtils.isNotEmpty(swaggerNote)) {
return swaggerNote;
} else {
return entity.getFiledName();
}
}
/**
* 是否不为基本数据类型(包含String);不是基本数据类型返回true
*
* @param obj object
* @return boolean
*/
private static boolean notBaseType(Object obj) {
if (Objects.isNull(obj)) {
return false;
}
return !(obj instanceof Byte)
&& !(obj instanceof Short)
&& !(obj instanceof Integer)
&& !(obj instanceof Long)
&& !(obj instanceof Float)
&& !(obj instanceof Double)
&& !(obj instanceof Character)
&& !(obj instanceof Boolean)
&& !(obj instanceof String)
&& !(obj instanceof Date);
}
/**
* 处理首字母大写错误的字段名
*
* @param fieldName 字段名
* @return 字段名
*/
private static String handlerFieldName(String fieldName) {
if (StringUtils.isEmpty(fieldName)) {
return fieldName;
}
final char charA = 65;
final char charZ = 90;
char firstChar = fieldName.charAt(0);
char secondChar = fieldName.charAt(1);
// 第一个字符和第二个字符都是大写,存在问题
if (firstChar >= charA && firstChar <= charZ && secondChar >= charA && secondChar <= charZ) {
return fieldName.replace(fieldName.charAt(0), (char) (fieldName.charAt(0) + 32));
}
return fieldName;
}
/**
* 获取注解的value值
*
* @param clazz 添加注解的字段
* @param fieldName 字段名
* @param annotationClass 注解类型
* @return
*/
private static Object getAnnotationValue(Class<?> clazz, String fieldName, Class<? extends Annotation> annotationClass) {
// 处理问题字段
fieldName = handlerFieldName(fieldName);
try {
Field field = clazz.getDeclaredField(fieldName);
if (field.isAnnotationPresent(annotationClass)) {
Annotation annotation = field.getAnnotation(annotationClass);
try {
return annotation.getClass().getDeclaredMethod("value").invoke(annotation);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
logInfo("当前注解没有value属性:" + " 【注解类型】:" + annotationClass + "; 【字段名】:" + fieldName + "; 【expMessage】:" + e);
return null;
}
}
} catch (NoSuchFieldException e) {
// 获取父类字段
Class<?> superClass = clazz.getSuperclass();
if (superClass == Object.class) {
logInfo("获取注解value失败:" + "; 【字段名】:" + fieldName + "; 【expMessage】:" + e);
return null;
}
try {
return getAnnotationValue(superClass, fieldName, annotationClass);
} catch (Exception exception) {
logInfo("获取注解value失败:" + " 【对象类型】:" + clazz + "; 【字段名】:" + fieldName + "; 【expMessage】:" + e);
return null;
}
}
return null;
}
/**
* 获取字段的swagger注解值
*
* @param clazz 字段类型
* @param fieldName 字段名
* @return ApiModelProperty(" value ")
*/
private static String getSwaggerNote(Class<?> clazz, String fieldName) {
return (String) getAnnotationValue(clazz, fieldName, ApiModelProperty.class);
}
/**
* 获取页面注解
*
* @param clazz 字段类型
* @param fieldName 字段名
* @return PageNote(" value ")
*/
private static String getPageNote(Class<?> clazz, String fieldName) {
return (String) getAnnotationValue(clazz, fieldName, PageNote.class);
}
private static void logInfo(String info) {
System.out.println(info);
}
}
数据载体对象
package com.maple.springbootenv.util;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class DataComparisonEntity {
/**
* DTO字段名
*/
private String filedName;
/**
* swagger注解
*/
private String swaggerNote;
/**
* 页面字段名
*/
private String pageFieldName;
/**
* 旧数据
*/
private Object oldValue;
/**
* 新数据
*/
private Object newValue;
/**
* 关联对象
*/
private Class<?> linkObject;
}
注解
package com.maple.springbootenv.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*/
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PageNote {
String value();
}