背景
前段时间的任务中,遇到了需要识别两个对象不同属性的场景,如果使用传统的一个个属性比对equals方法,会存在大量的重复工作,而且为对象新增了属性后,比对方法也需要同步修改,不方便维护,于是便打算构造一个通用的比对方法
思路
一个java对象,他的属性是多样的,会有集合类型(如Collection,Map,Array),基本类型(如String,int,double,long等认为是最基本对象的比较),或者对象类型,本质上是一个树状的结构
怎么去遍历对象树
常用的方法就是递归,那怎么定义递归的终点?基于上面三种类型,定为基本类型的比对为递归的终点,对于集合类型,那就再对每一个元素进行比较,对于对象类型,遍历属性进行比较
为了达成通用目的,这里使用java反射实现,基于约定和性能考虑,只通过无参的getter方法获取属性进行比较,另外如何记录当前属性所在的路径?这里参考spel表达式
实现
首先定义递归过程记录的上下文信息
这里记录了根对象,当前处理的对象,以及反射获取的对象信息
protected static class CompareContext {
//对比结果
private List<CompareResult.CompareInfo> resCollect;
//当前遍历的对象信息,用于定位
private ArrayDeque<String> pathMessageStack;
protected Object rootSourceObj;
protected Object rootOtherObj;
protected Object sourceObj;
protected Object otherObj;
protected Object sourceInvokeVal;
protected Object otherInvokeVal;
//过滤耗费时间
private long filterCost;
private String checkCycle() {
Set<String> set = new LinkedHashSet<>();
//出现重复退出
while (set.add(pathMessageStack.removeLast())) {
}
String[] elements = new String[set.size()];
Iterator<String> iterator = set.iterator();
int index = 0;
while (iterator.hasNext()) {
elements[set.size() - 1 - index++] = iterator.next();
}
return getPath(elements);
}
protected String getPath(String[] elements) {
Object obj = this.rootSourceObj == null ? this.rootOtherObj : this.rootSourceObj;
String simpleName = obj.getClass().getSimpleName();
StringBuilder builder = new StringBuilder(simpleName);
if (elements == null) {
elements = this.pathMessageStack.toArray(new String[0]);
}
for (int i = elements.length - 1; i >= 0; i--) {
String cur = elements[i];
String value = cur.substring(2);
if (cur.startsWith(FIELD_SIGN)) {
builder.append(".").append(value);
} else {
builder.append("[").append(value).append("]");
}
}
return builder.toString();
}
}
基于这个上下文,定义过滤接口,因为有些属性可能不用比对,如果命中过滤接口,则跳过
@FunctionalInterface
public interface MethodFilter {
boolean isSkipCompare(Method method, String methodKey, CompareUtil.CompareContext compareContext);
}
比对结果类
@Getter
@ToString
public class CompareResult {
protected boolean same;
protected Object source;
protected Object other;
protected List<CompareInfo> diffList;
/**
* 为true的情况包括无异常发生&发生但都相等的情况
*/
private boolean exceptionSame = true;
//=====额外信息,可以不关注====
//整体对比耗时
private long totalCostMill;
//方法过滤耗时
protected long methodFilterCostMill;
protected void genCost(long start) {
this.totalCostMill = System.currentTimeMillis() - start;
}
/**
* 合并结果
*
* @param otherResult
*/
public void merge(CompareResult otherResult) {
if(otherResult!=null){
List<CompareInfo> otherResultDiffList = otherResult.getDiffList();
Set<String> pathSet = diffList.stream().map(CompareInfo::getPath).collect(Collectors.toSet());
for (CompareInfo compareInfo : otherResultDiffList) {
if (!pathSet.contains(compareInfo.getPath())) {
compareInfo.merge=true;
diffList.add(compareInfo);
}
}
this.same &= otherResult.same;
this.exceptionSame &= otherResult.exceptionSame;
}
}
/**
* 设置父路径
*
* @param parentPath
*/
public void setParentPath(String parentPath) {
for (CompareInfo compareInfo : diffList) {
String path = compareInfo.getPath();
int i = Math.min(path.indexOf("."), path.indexOf("["));
compareInfo.path = parentPath + (i == -1 ? "" : path.substring(i));
}
}
@Getter
public static class CompareInfo {
protected Object sourceVal;
protected Object otherVal;
protected String path;
protected boolean merge;
protected boolean isShow() {
if (sourceVal == null || otherVal == null) {
return true;
}
if (CompareUtil.getObjType(sourceVal) != Object.class) {
return true;
}
if (sourceVal instanceof Collection) {
return ((Collection<?>) sourceVal).size() != ((Collection<?>) otherVal).size();
}
if (sourceVal instanceof Map) {
return ((Map<?, ?>) sourceVal).size() != ((Map<?, ?>) otherVal).size();
}
if (sourceVal.getClass().isArray()) {
return ((Object[]) sourceVal).length != ((Object[]) otherVal).length;
}
return false;
}
@Override
public String toString() {
return String.format("path:%s,source:[%s],other:[%s]", path, sourceVal, otherVal);
}
}
public void setExceptionSame(boolean exceptionSame) {
this.exceptionSame = exceptionSame;
}
public String getBaseObjDiffInfo() {
return getBaseObjDiffInfo("\n");
}
/**
* 过滤出为空的父对象或基本对象,原生的diffList会包含父对象,不方便查看
*
* @param seperator
* @return
*/
public String getBaseObjDiffInfo(String seperator) {
if (!same) {
StringBuilder builder = new StringBuilder();
diffList.stream().filter(CompareInfo::isShow).forEach(v -> builder.append(v).append(seperator));
return builder.toString();
}
return "";
}
}
随后定义入口方法
public static <T> CompareResult compareObjByGetter(T source, T other, MethodFilter... methodFilters) {
List<CompareResult.CompareInfo> diffList = new ArrayList<>();
CompareContext compareContext = new CompareContext();
compareContext.resCollect = diffList;
compareContext.pathMessageStack = new ArrayDeque<>(MAX_DEEP_SIZE);
compareContext.rootSourceObj = source;
compareContext.rootOtherObj = other;
long start = System.currentTimeMillis();
boolean res = compareObjByGetter(source, other, compareContext, methodFilters);
CompareResult compareResult = new CompareResult();
compareResult.genCost(start);
compareResult.diffList = diffList;
compareResult.same = res;
compareResult.other = other;
compareResult.source = source;
compareResult.methodFilterCostMill = compareContext.filterCost;
return compareResult;
}
主流程方法,流程如下
- 校验
- 路径长度校验
- 类校验
- 递归终点,对于基本类型,直接走比对方法,并生成路径
- 对于复合类型,递归进行比对逻辑
- 对于对象类型,采用反射获取具体值,并通过方法过滤进行比较
- 首先判断是否有public的无参数getter方法
- 有的话采用调用该方法获取两边的值,设置上下文信息
- 过滤器过滤无需比对的字段
- 如果任一方为空,构造结果
- 对于集合类型的属性,需要进一步获取里面的元素进行递归处理
- 对于对象类型或基本类型,重复上述步骤
主流程代码如下
private static <T> boolean compareObjByGetter(T source, T other, CompareContext compareContext, MethodFilter... methodFilters) {
//对比路径太深或出现循环引用不支持
if (compareContext.pathMessageStack.size() > MAX_DEEP_SIZE) {
String path = compareContext.checkCycle();
if (!StringUtils.isEmpty(path)) {
//路径仅供参考,不一定准确
throw new IllegalStateException(String.format("reference cycle happen,please check your object,path:%s", path));
}
throw new IllegalStateException(String.format("compare path over max size:%s,please check your object", MAX_DEEP_SIZE));
}
if(source==other){
return true;
}
if (source == null || other == null) {
generateResult(source, other, compareContext);
return false;
}
if (!source.getClass().equals(other.getClass())) {
throw new IllegalArgumentException(String.format("not the same object,class source:%s,class other:%s,path:%s", source.getClass(), other.getClass(),compareContext.getPath(null)));
}
//基本类型不再对比getter方法
if (getObjType(source) != Object.class) {
boolean isSame = compareBaseObj(source, other);
if (!isSame) {
generateResult(source, other, compareContext);
}
return isSame;
}
//复合类型
if (isCollectionOrMapOrArray(source)) {
return dealWithCMA(source, other, compareContext, methodFilters);
}
//对象类型,遍历对应的方法
final boolean[] val = new boolean[]{true};
ReflectionUtils.doWithMethods(source.getClass(), new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
String name = method.getName();
if (method.getModifiers() == Modifier.PUBLIC && (name.startsWith("get") || name.startsWith("is"))) {
//有入参的getter不处理
if (method.getParameterTypes().length != 0) {
return;
}
String methodKey = method.getDeclaringClass().getName() + "#" + method.getName();
Object sourceInvokeVal = null;
Object otherInvokeVal = null;
try {
sourceInvokeVal = method.invoke(source);
otherInvokeVal = method.invoke(other);
} catch (Exception e) {
throw new RuntimeException(e);
}
compareContext.otherObj = other;
compareContext.otherInvokeVal = otherInvokeVal;
compareContext.sourceObj = source;
compareContext.sourceInvokeVal = sourceInvokeVal;
//过滤,methodFilter是||关系
long start = System.currentTimeMillis();
try {
for (MethodFilter methodFilter : methodFilters) {
if (methodFilter.isSkipCompare(method, methodKey, compareContext)) {
return;
}
}
} finally {
compareContext.filterCost += System.currentTimeMillis() - start;
}
if (sourceInvokeVal == null && otherInvokeVal == null) {
return;
}
compareContext.pathMessageStack.push(String.format("%s%s", FIELD_SIGN, ColumnSelector.getFieldName(method.getName(), method.getDeclaringClass().getName())));
if (sourceInvokeVal == null || otherInvokeVal == null) {
generateResult(sourceInvokeVal, otherInvokeVal, compareContext);
val[0] = false;
compareContext.pathMessageStack.pop();
return;
}
if (isCollectionOrMapOrArray(sourceInvokeVal)) {
val[0] &= dealWithCMA(sourceInvokeVal, otherInvokeVal, compareContext, methodFilters);
} else {
//对象类型 or 基本类型
val[0] &= compareObjByGetter(sourceInvokeVal, otherInvokeVal, compareContext, methodFilters);
}
compareContext.pathMessageStack.pop();
}
}
});
return val[0];
}
对于集合类型的处理如下
主要根据各自的特性取出元素,再给到上面的主流程去执行
private static boolean dealWithCMA(Object sourceObj, Object otherObj, CompareContext compareContext, MethodFilter... methodFilters) {
if(sourceObj==otherObj){
return true;
}
boolean isDiff = true;
if (sourceObj instanceof Collection) {
Collection<?> sourceCollection = ((Collection<?>) sourceObj);
Collection<?> otherCollection = ((Collection<?>) otherObj);
//要求顺序,这里不做排序
if (sourceCollection.size() != otherCollection.size()) {
isDiff = false;
} else {
Iterator<?> sourceI = sourceCollection.iterator();
Iterator<?> otherI = otherCollection.iterator();
int index = 0;
while (sourceI.hasNext()) {
Object sourceElement = sourceI.next();
Object otherElement = otherI.next();
//下一层不匹配的值
compareContext.pathMessageStack.push(String.format("%s%s", COLLECTION_SIGN, index++));
isDiff &= compareObjByGetter(sourceElement, otherElement, compareContext, methodFilters);
compareContext.pathMessageStack.pop();
}
}
}
if (sourceObj.getClass().isArray()) {
Object[] sourceArray = (Object[]) sourceObj;
Object[] otherArray = (Object[]) otherObj;
if (sourceArray.length != otherArray.length) {
isDiff = false;
} else {
for (int i = 0; i < sourceArray.length; i++) {
Object sourceElement = sourceArray[i];
Object otherElement = otherArray[i];
compareContext.pathMessageStack.push(String.format("%s%s", ARRAY_SIGN, i));
isDiff &= compareObjByGetter(sourceElement, otherElement, compareContext, methodFilters);
compareContext.pathMessageStack.pop();
}
}
}
if (sourceObj instanceof Map) {
Map<?, ?> sourceMap = (Map) sourceObj;
Map<?, ?> otherMap = (Map) otherObj;
if (sourceMap.size() != otherMap.size()) {
isDiff = false;
} else {
HashSet<?> otherKeySet = new HashSet<>(otherMap.keySet());
for (Map.Entry<?, ?> entry : sourceMap.entrySet()) {
Object sourceKey = entry.getKey();
Object otherVal = otherMap.get(sourceKey);
otherKeySet.remove(sourceKey);
compareContext.pathMessageStack.push(String.format("%s%s", KEY_SIGN, sourceKey));
isDiff &= compareObjByGetter(entry.getValue(), otherVal, compareContext, methodFilters);
compareContext.pathMessageStack.pop();
}
if (!otherKeySet.isEmpty()) {
for (Object otherKey : otherKeySet) {
compareContext.pathMessageStack.push(String.format("%s%s", KEY_SIGN, otherKey));
isDiff &= compareObjByGetter(null, otherMap.get(otherKey), compareContext, methodFilters);
compareContext.pathMessageStack.pop();
}
}
}
}
if (!isDiff) {
generateResult(sourceObj, otherObj, compareContext);
}
return isDiff;
}
对于基本对象判断,如果返回object类型需要进一步解析判断,后续如果有其他基本对象,比如业务自定义的基本对象也可以往里面加,或者再进行优化,通过本地线程的方式动态指定基本对象类型
/**
* 后续比对有其他基本类型可以往里加
* @param obj
* @return
*/
protected static Class<?> getObjType(Object obj) {
if (obj instanceof Integer) {
return Integer.class;
} else if (obj instanceof String) {
return String.class;
} else if (obj instanceof BigDecimal) {
return BigDecimal.class;
} else if (obj instanceof Long) {
return Long.class;
} else if (obj instanceof Enum) {
return Enum.class;
} else if (obj instanceof Double) {
return Double.class;
} else if (obj instanceof Boolean) {
return Boolean.class;
}
return Object.class;
}
对于基本类型的比较方法
private static <T> boolean compareBaseObj(T obj, T other) {
//同一个对象直接通过
if(obj==other){
return true;
}
//数字类型
if (obj instanceof Number && obj instanceof Comparable) {
return ((Comparable<T>) obj).compareTo(other) == 0;
}
//其他类型
return Objects.equals(obj, other);
}
Java对象比对功能实现大概如此,来看看效果
定义了比对对象
public static class ComposeObj {
private String id;
private Object[] arrays;
private List<Object> list;
private Map<String, Object> map;
private int idLength;
public ComposeObj(String id) {
this.id = id;
this.idLength = id.length();
}
}
测试代码
ComposeObj sourceObj = new ComposeObj("source");
ComposeObj arrayObj = new ComposeObj("A1");
sourceObj.arrays = new Object[]{arrayObj, new ComposeObj("A22")};
sourceObj.list = Arrays.asList(new ComposeObj("C1"), new ComposeObj("C11"));
Map<String, Object> map = new HashMap<>();
map.put("test", "test");
map.put("test2", "test2");
sourceObj.map = map;
ComposeObj otherObj = new ComposeObj("other");
ComposeObj arrayObj2 = new ComposeObj("A2");
otherObj.arrays = new Object[]{arrayObj2, new ComposeObj("A22")};
otherObj.list = Arrays.asList(new ComposeObj("C2"), new ComposeObj("C11"));
Map<String, Object> map2 = new HashMap<>();
map2.put("test", "test2");
map2.put("test2", "test22");
otherObj.map = map2;
测试方法
CompareResult compareResult = CompareUtil.compareObjByGetter(sourceObj, otherObj);
assert checkPath(compareResult);
assert compareResult.getDiffList().size() == 9;
log.info(compareResult.getBaseObjDiffInfo());
结果
默认的支持的表达式过滤器如下
/**
* 通过简单的表达式
* "*abc|xxx" ->标识以abc结尾或xxx的属性忽略,不做比对,*代表通配符
*/
public class RegexFieldFilter implements MethodFilter {
private static final Cache<String, Pattern> REGEX_MAP = CacheBuilder.newBuilder()
.softValues().maximumSize(2048).build();
private final List<String> rules;
private static final String WILDCARD = "*";
//是否缓存结果,如果调用次数较多(比如属性为list,且实际场景数量较多)可以用启用
private boolean cacheResult = false;
//方法key
private Cache<String, Boolean> resultMap;
private final Set<String> equalsField = new HashSet<>();
private final Map<String, String> prefixFieldMap = new HashMap<>();
private final Map<String, String> suffixFieldMap = new HashMap<>();
private RegexFieldFilter(String regex, boolean cacheResult) {
this.cacheResult = cacheResult;
rules = Splitter.on("|").splitToList(regex);
for (String rule : rules) {
//普通比对
if (!rule.contains(WILDCARD)) {
equalsField.add(rule);
}
//首尾通配符特殊逻辑
if (onlyOneWildcard(rule)) {
if (rule.startsWith(WILDCARD)) {
suffixFieldMap.put(rule, rule.substring(1));
}
if (rule.endsWith(WILDCARD)) {
prefixFieldMap.put(rule, rule.substring(0, rule.length() - 1));
}
}
}
if (cacheResult) {
resultMap = CacheBuilder.newBuilder().softValues().maximumSize(1024).build();
}
}
public static RegexFieldFilter of(String regex, boolean cacheResult) {
return new RegexFieldFilter(regex, cacheResult);
}
public static RegexFieldFilter of(String regex) {
return of(regex, false);
}
private boolean canSkip(String rule, String fieldName) {
if (rule.contains(WILDCARD)) {
if (suffixFieldMap.containsKey(rule)) {
return fieldName.endsWith(suffixFieldMap.get(rule));
}
if (prefixFieldMap.containsKey(rule)) {
return fieldName.startsWith(prefixFieldMap.get(rule));
}
//在中间或多个通配符
String replace = StringUtils.replace(rule, WILDCARD, ".*");
Pattern pattern = REGEX_MAP.asMap().computeIfAbsent(replace, Pattern::compile);
return pattern.matcher(fieldName).matches();
}
return equalsField.contains(fieldName);
}
private boolean onlyOneWildcard(String rule) {
if (!rule.contains(WILDCARD)) {
return false;
}
return rule.indexOf(WILDCARD, rule.indexOf(WILDCARD) + 1) == -1;
}
@Override
public boolean isSkipCompare(Method method, String methodKey, CompareUtil.CompareContext context) {
return cacheResult ? resultMap.asMap().computeIfAbsent(methodKey, s -> judgeSkip(method)) : judgeSkip(method);
}
private boolean judgeSkip(Method method) {
if (!CollectionUtils.isEmpty(rules)) {
String name = method.getName();
String fieldName = CompareUtil.getFieldName(name, method.getDeclaringClass().getCanonicalName());
for (String rule : rules) {
if (canSkip(rule, fieldName)) {
return true;
}
}
}
return false;
}
}
测试代码如下,定义这个过滤会把属性名为arrays,带有'l',或者ma开头的属性都过滤掉,不参与比对逻辑
boolean cacheResult = false;
RegexFieldFilter of = RegexFieldFilter.of("arrays|*l*|ma*", cacheResult);
CompareResult compareResult = CompareUtil.compareObjByGetter(source, other, of);
assert checkPath(compareResult);
assert compareResult.getDiffList().size() == 15;
comopareUtil完整代码
/**
* <p>入口 {@link #compareObjByGetter(Object, Object, MethodFilter...)}</p>
* <p>具体对比不通过的内容由 {@link CompareResult#diffList}提供</p>
* <p>提供对比耗时统计,如果性能较差或多个比对结果可以使用异步比对 {@link #compareObjByGetterAsync(Object, Object, MethodFilter...)}</p>
* <p>其中{@link CompareResult.CompareInfo#path}为 <a href="https://docs.spring.io/spring-framework/reference/core/expressions.html">spel表达式</a></p>
* <p>{@link CompareJoiner} 提供多个比对&打印日志(默认不打印-概率为0)的功能</p>
* @see MethodFilter getter方法过滤
*/
public final class CompareUtil {
private CompareUtil() {
}
private static final String COLLECTION_SIGN = "C$";
private static final String ARRAY_SIGN = "A$";
private static final String FIELD_SIGN = "F$";
private static final String KEY_SIGN = "K$";
private static final Integer MAX_DEEP_SIZE = 200;
/**
* @param source 目前只支持同对象,不同对象目前可以转为同一个对象再比对
* @param other
* @param methodFilters {@link MethodFilter}
* @param <T>
* @return
*/
public static <T> CompareResult compareObjByGetter(T source, T other, MethodFilter... methodFilters) {
List<CompareResult.CompareInfo> diffList = new ArrayList<>();
CompareContext compareContext = new CompareContext();
compareContext.resCollect = diffList;
compareContext.pathMessageStack = new ArrayDeque<>(MAX_DEEP_SIZE);
compareContext.rootSourceObj = source;
compareContext.rootOtherObj = other;
long start = System.currentTimeMillis();
boolean res = compareObjByGetter(source, other, compareContext, methodFilters);
CompareResult compareResult = new CompareResult();
compareResult.genCost(start);
compareResult.diffList = diffList;
compareResult.same = res;
compareResult.other = other;
compareResult.source = source;
compareResult.methodFilterCostMill = compareContext.filterCost;
return compareResult;
}
public static <T> CompletableFuture<CompareResult> compareObjByGetterAsync(T source, T other, MethodFilter... methodFilters) {
return CompletableFuture.supplyAsync(() -> compareObjByGetter(source, other, methodFilters));
}
public static <T> CompletableFuture<CompareResult> compareObjByGetterAsync(T source, T other, Executor executor, MethodFilter... methodFilters) {
return CompletableFuture.supplyAsync(() -> compareObjByGetter(source, other, methodFilters), executor);
}
public static CompareJoiner newJoiner(){
return newJoiner(0);
}
public static CompareJoiner newJoiner(double logRate) {
return new CompareJoiner(logRate);
}
public static class CompareJoiner {
private static final Logger LOGGER = LoggerFactory.getLogger(CompareJoiner.class);
private Map<String, Supplier<CompareResult>> suppliers = new HashMap<>();
private boolean allSame = true;
private Map<String, CompareResult> resultMap = new ConcurrentHashMap<>();
private long costMills;
private double logRate;
private int index = 0;
private CompareJoiner(double logRate) {
this.logRate=logRate;
}
public CompareJoiner join(String compareId, Object source, Object other, MethodFilter... methodFilter) {
suppliers.put(compareId, () -> compareObjByGetter(source, other, methodFilter));
return this;
}
public CompareJoiner join(Object source, Object other, MethodFilter... methodFilters) {
return join(String.valueOf(index++), source, other, methodFilters);
}
public List<CompareResult> getDiffResult() {
List<CompareResult> results = new ArrayList<>();
for (Map.Entry<String, CompareResult> entry : resultMap.entrySet()) {
boolean same = entry.getValue().isSame();
if (!same) {
results.add(entry.getValue());
}
}
return results;
}
public boolean isAllSame() {
return allSame;
}
public CompareResult getResultById(String id) {
return resultMap.get(id);
}
public CompareJoiner executeParallel() {
return executeParallel(500, TimeUnit.MILLISECONDS);
}
/**
* 并行执行
*
* @param time
* @param timeUnit
*/
public CompareJoiner executeParallel(long time, TimeUnit timeUnit) {
long start = System.currentTimeMillis();
CountDownLatch count = new CountDownLatch(suppliers.values().size());
for (Map.Entry<String, Supplier<CompareResult>> entry : suppliers.entrySet()) {
Supplier<CompareResult> value = entry.getValue();
String key = entry.getKey();
if (!resultMap.containsKey(key)) {
CompletableFuture.supplyAsync(value).whenComplete(new BiConsumer<CompareResult, Throwable>() {
@Override
public void accept(CompareResult compareResult, Throwable throwable) {
resultMap.put(key, compareResult);
count.countDown();
}
});
} else {
count.countDown();
}
}
try {
count.await(time, timeUnit);
resultMap.values().forEach(e -> allSame &= e.same);
} catch (InterruptedException e) {
LOGGER.warn("compare parallel execute time out {} mills,use serial execute",timeUnit.convert(time,TimeUnit.MILLISECONDS));
execute();
}
this.costMills =System.currentTimeMillis()-start;
logDiff();
return this;
}
/**
* 串型执行
*/
public CompareJoiner execute() {
long start = System.currentTimeMillis();
for (Map.Entry<String, Supplier<CompareResult>> stringSupplierEntry : suppliers.entrySet()) {
String key = stringSupplierEntry.getKey();
resultMap.computeIfAbsent(key, s -> {
CompareResult compareResult = stringSupplierEntry.getValue().get();
allSame &= compareResult.same;
return compareResult;
});
}
this.costMills =System.currentTimeMillis()-start;
logDiff();
return this;
}
public CompareJoiner reset(){
this.resultMap.clear();
this.suppliers.clear();
this.allSame =true;
this.costMills =0;
this.index = 0;
return this;
}
private void logDiff(){
if(logRate<=0){return;}
if(logRate>=1||logRate>=ThreadLocalRandom.current().nextDouble(1)){
for (Map.Entry<String, CompareResult> entry : resultMap.entrySet()) {
String key = entry.getKey();
if(!entry.getValue().same){
LOGGER.warn("compare joiner found difference,joiner key:[{}],different values [{}]",key,entry.getValue().getBaseObjDiffInfo());
}
}
}
}
@Override
public String toString() {
return "CompareJoiner{" +
"isAllSame=" + allSame +
", costMills=" + costMills +
'}';
}
}
/**
* 工具类方法
*
* @param source
* @param other
* @param <T>
* @return
*/
public static <T> T beanCopy(Object source, Supplier<T> other,String... ignoreProperties) {
if (Objects.isNull(source)) {
return null;
}
T t = other.get();
BeanUtils.copyProperties(source, t,ignoreProperties);
return t;
}
public static <T extends Serializable> T deepCopy(T source){
return (T) SerializationUtils.deserialize(SerializationUtils.serialize(source));
}
protected static class CompareContext {
//对比结果
private List<CompareResult.CompareInfo> resCollect;
//当前遍历的对象信息,用于定位
private ArrayDeque<String> pathMessageStack;
protected Object rootSourceObj;
protected Object rootOtherObj;
protected Object sourceObj;
protected Object otherObj;
protected Object sourceInvokeVal;
protected Object otherInvokeVal;
//过滤耗费时间
private long filterCost;
private String checkCycle() {
Set<String> set = new LinkedHashSet<>();
//出现重复退出
while (set.add(pathMessageStack.removeLast())) {
}
String[] elements = new String[set.size()];
Iterator<String> iterator = set.iterator();
int index = 0;
while (iterator.hasNext()) {
elements[set.size() - 1 - index++] = iterator.next();
}
return getPath(elements);
}
protected String getPath(String[] elements) {
Object obj = this.rootSourceObj == null ? this.rootOtherObj : this.rootSourceObj;
String simpleName = obj.getClass().getSimpleName();
StringBuilder builder = new StringBuilder(simpleName);
if (elements == null) {
elements = this.pathMessageStack.toArray(new String[0]);
}
for (int i = elements.length - 1; i >= 0; i--) {
String cur = elements[i];
String value = cur.substring(2);
if (cur.startsWith(FIELD_SIGN)) {
builder.append(".").append(value);
} else {
builder.append("[").append(value).append("]");
}
}
return builder.toString();
}
}
/**
* 对比主入口
*
* @param source
* @param other
* @param compareContext
* @param methodFilters
* @param <T>
* @return
*/
private static <T> boolean compareObjByGetter(T source, T other, CompareContext compareContext, MethodFilter... methodFilters) {
//对比路径太深或出现循环引用不支持
if (compareContext.pathMessageStack.size() > MAX_DEEP_SIZE) {
String path = compareContext.checkCycle();
if (!StringUtils.isEmpty(path)) {
//路径仅供参考,不一定准确
throw new IllegalStateException(String.format("reference cycle happen,please check your object,path:%s", path));
}
throw new IllegalStateException(String.format("compare path over max size:%s,please check your object", MAX_DEEP_SIZE));
}
if(source==other){
return true;
}
if (source == null || other == null) {
generateResult(source, other, compareContext);
return false;
}
if (!source.getClass().equals(other.getClass())) {
throw new IllegalArgumentException(String.format("not the same object,class source:%s,class other:%s,path:%s", source.getClass(), other.getClass(),compareContext.getPath(null)));
}
//基本类型不再对比getter方法
if (getObjType(source) != Object.class) {
boolean isSame = compareBaseObj(source, other);
if (!isSame) {
generateResult(source, other, compareContext);
}
return isSame;
}
//复合类型
if (isCollectionOrMapOrArray(source)) {
return dealWithCMA(source, other, compareContext, methodFilters);
}
//对象类型,遍历对应的方法
final boolean[] val = new boolean[]{true};
ReflectionUtils.doWithMethods(source.getClass(), new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
String name = method.getName();
if (method.getModifiers() == Modifier.PUBLIC && (name.startsWith("get") || name.startsWith("is"))) {
//有入参的getter不处理
if (method.getParameterTypes().length != 0) {
return;
}
String methodKey = method.getDeclaringClass().getName() + "#" + method.getName();
Object sourceInvokeVal = null;
Object otherInvokeVal = null;
try {
sourceInvokeVal = method.invoke(source);
otherInvokeVal = method.invoke(other);
} catch (Exception e) {
throw new RuntimeException(e);
}
compareContext.otherObj = other;
compareContext.otherInvokeVal = otherInvokeVal;
compareContext.sourceObj = source;
compareContext.sourceInvokeVal = sourceInvokeVal;
//过滤,methodFilter是||关系
long start = System.currentTimeMillis();
try {
for (MethodFilter methodFilter : methodFilters) {
if (methodFilter.isSkipCompare(method, methodKey, compareContext)) {
return;
}
}
} finally {
compareContext.filterCost += System.currentTimeMillis() - start;
}
if (sourceInvokeVal == null && otherInvokeVal == null) {
return;
}
compareContext.pathMessageStack.push(String.format("%s%s", FIELD_SIGN,getFieldName(method.getName(), method.getDeclaringClass().getName())));
if (sourceInvokeVal == null || otherInvokeVal == null) {
generateResult(sourceInvokeVal, otherInvokeVal, compareContext);
val[0] = false;
compareContext.pathMessageStack.pop();
return;
}
if (isCollectionOrMapOrArray(sourceInvokeVal)) {
val[0] &= dealWithCMA(sourceInvokeVal, otherInvokeVal, compareContext, methodFilters);
} else {
//对象类型 or 基本类型
val[0] &= compareObjByGetter(sourceInvokeVal, otherInvokeVal, compareContext, methodFilters);
}
compareContext.pathMessageStack.pop();
}
}
});
return val[0];
}
/**
* collection & map & array 处理
*
* @param sourceObj
* @param otherObj
* @param compareContext
* @param methodFilters
* @return
*/
private static boolean dealWithCMA(Object sourceObj, Object otherObj, CompareContext compareContext, MethodFilter... methodFilters) {
if(sourceObj==otherObj){
return true;
}
boolean isDiff = true;
if (sourceObj instanceof Collection) {
Collection<?> sourceCollection = ((Collection<?>) sourceObj);
Collection<?> otherCollection = ((Collection<?>) otherObj);
//要求顺序,这里不做排序
if (sourceCollection.size() != otherCollection.size()) {
isDiff = false;
} else {
Iterator<?> sourceI = sourceCollection.iterator();
Iterator<?> otherI = otherCollection.iterator();
int index = 0;
while (sourceI.hasNext()) {
Object sourceElement = sourceI.next();
Object otherElement = otherI.next();
//下一层不匹配的值
compareContext.pathMessageStack.push(String.format("%s%s", COLLECTION_SIGN, index++));
isDiff &= compareObjByGetter(sourceElement, otherElement, compareContext, methodFilters);
compareContext.pathMessageStack.pop();
}
}
}
if (sourceObj.getClass().isArray()) {
Object[] sourceArray = (Object[]) sourceObj;
Object[] otherArray = (Object[]) otherObj;
if (sourceArray.length != otherArray.length) {
isDiff = false;
} else {
for (int i = 0; i < sourceArray.length; i++) {
Object sourceElement = sourceArray[i];
Object otherElement = otherArray[i];
compareContext.pathMessageStack.push(String.format("%s%s", ARRAY_SIGN, i));
isDiff &= compareObjByGetter(sourceElement, otherElement, compareContext, methodFilters);
compareContext.pathMessageStack.pop();
}
}
}
if (sourceObj instanceof Map) {
Map<?, ?> sourceMap = (Map) sourceObj;
Map<?, ?> otherMap = (Map) otherObj;
if (sourceMap.size() != otherMap.size()) {
isDiff = false;
} else {
HashSet<?> otherKeySet = new HashSet<>(otherMap.keySet());
for (Map.Entry<?, ?> entry : sourceMap.entrySet()) {
Object sourceKey = entry.getKey();
Object otherVal = otherMap.get(sourceKey);
otherKeySet.remove(sourceKey);
compareContext.pathMessageStack.push(String.format("%s%s", KEY_SIGN, sourceKey));
isDiff &= compareObjByGetter(entry.getValue(), otherVal, compareContext, methodFilters);
compareContext.pathMessageStack.pop();
}
if (!otherKeySet.isEmpty()) {
for (Object otherKey : otherKeySet) {
compareContext.pathMessageStack.push(String.format("%s%s", KEY_SIGN, otherKey));
isDiff &= compareObjByGetter(null, otherMap.get(otherKey), compareContext, methodFilters);
compareContext.pathMessageStack.pop();
}
}
}
}
if (!isDiff) {
generateResult(sourceObj, otherObj, compareContext);
}
return isDiff;
}
/**
* 生成spel路径,用于核对&查看
*
* @param sourceObj
* @param otherObj
* @param compareContext
*/
private static void generateResult(Object sourceObj, Object otherObj, CompareContext compareContext) {
CompareResult.CompareInfo compareInfo = new CompareResult.CompareInfo();
compareInfo.sourceVal = sourceObj;
compareInfo.otherVal = otherObj;
compareInfo.path = compareContext.getPath(null);
compareContext.resCollect.add(compareInfo);
}
private static <T> boolean compareBaseObj(T obj, T other) {
//同一个对象直接通过
if(obj==other){
return true;
}
//数字类型
if (obj instanceof Number && obj instanceof Comparable) {
return ((Comparable<T>) obj).compareTo(other) == 0;
}
//其他类型
return Objects.equals(obj, other);
}
/**
* 后续比对有其他基本类型可以往里加
* @param obj
* @return
*/
protected static Class<?> getObjType(Object obj) {
if (obj instanceof Integer) {
return Integer.class;
} else if (obj instanceof String) {
return String.class;
} else if (obj instanceof BigDecimal) {
return BigDecimal.class;
} else if (obj instanceof Long) {
return Long.class;
} else if (obj instanceof Enum) {
return Enum.class;
} else if (obj instanceof Double) {
return Double.class;
} else if (obj instanceof Boolean) {
return Boolean.class;
}
return Object.class;
}
private static boolean isCollectionOrMapOrArray(Object obj) {
return obj instanceof Collection || obj instanceof Map || obj.getClass().isArray();
}
public static String getFieldName(String getMethodName,String className) {
String get = "get";
String is = "is";
if (getMethodName.startsWith(get)) {
getMethodName = getMethodName.substring(get.length());
} else if (getMethodName.startsWith(is)) {
getMethodName = getMethodName.substring(is.length());
} else {
throw new IllegalStateException(String.format("no found getter in class %s", className));
}
// 小写第一个字母
return Character.isLowerCase(getMethodName.charAt(0)) ? getMethodName : Character.toLowerCase(getMethodName.charAt(0)) + getMethodName.substring(1);
}
}