Java对象通用比对工具

目录

背景

思路

实现


背景

  前段时间的任务中,遇到了需要识别两个对象不同属性的场景,如果使用传统的一个个属性比对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;//=====额外信息,可以不关注====//整体对比耗时private long totalCostMill;//方法过滤耗时protected long methodFilterCostMill;protected void genCost(long start) {this.totalCostMill = System.currentTimeMillis() - start;}@Getterpublic static class CompareInfo {protected Object sourceVal;protected Object otherVal;protected String path;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;}@Overridepublic String toString() {return String.format("path:%s,source:[%s],other:[%s]", path, sourceVal, otherVal);}}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;}

主流程方法,流程如下

  1. 校验
    1. 路径长度校验
    2. 类校验
  2. 递归终点,对于基本类型,直接走比对方法,并生成路径
  3. 对于复合类型,递归进行比对逻辑
  4. 对于对象类型,采用反射获取具体值,并通过方法过滤进行比较
    1. 首先判断是否有public的无参数getter方法
    2. 有的话采用调用该方法获取两边的值,设置上下文信息
    3. 过滤器过滤无需比对的字段
    4. 如果任一方为空,构造结果
    5. 对于集合类型的属性,需要进一步获取里面的元素进行递归处理
    6. 对于对象类型或基本类型,重复上述步骤

主流程代码如下

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() {@Overridepublic 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;//方法keyprivate 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;}@Overridepublic 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>() {@Overridepublic 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());}}}}@Overridepublic String toString() {return "CompareJoiner{" +"isAllSame=" + allSame +", costMills=" + costMills +'}';}}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() {@Overridepublic 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);}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/41941.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

node的下载、安装、配置和使用(node.js下载安装和配置、npm命令汇总、cnpm的使用)

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。 愿将腰下剑,直为斩楼兰。 ——《塞下曲》 文章目录 一、node.js的下载、安装和配置1. node.js下…

集智书童 | 英伟达和斯坦福基于 Transformer 的异常检测最新研究!

本文来源公众号“集智书童”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;英伟达和斯坦福基于 Transformer 的异常检测最新研究&#xff01; 在作者推动各种视觉任务性能边界的同时&#xff0c;模型的大小也在相应增长。为了跟上…

电商视角如何理解动态IP与静态IP

在电子商务的蓬勃发展中&#xff0c;网络基础设施的稳定性和安全性是至关重要的。其中&#xff0c;IP地址作为网络设备间通信的基础&#xff0c;扮演着举足轻重的角色。从电商的视角出发&#xff0c;我们可以将动态IP和静态IP比作电商平台上不同类型的店铺安排&#xff0c;以此…

华为ENSP防火墙+路由器+交换机的常规配置

(防火墙区域DHCP基于接口DHCP中继服务器区域有线区域无线区域&#xff09;配置 一、适用场景&#xff1a; 1、普通企业级网络无冗余网络环境&#xff0c;防火墙作为边界安全设备&#xff0c;分trust&#xff08;内部网络信任区域&#xff09;、untrust&#xff08;外部网络非信…

vulnhub靶场之Jarbas

1 信息收集 1.1 主机发现 arp-scan -l 发现主机IP地址为&#xff1a;192.168.1.16 1.2 端口发现 nmap -sS -sV -A -T5 -p- 192.168.1.16 存在端口22&#xff0c;80&#xff0c;3306&#xff0c;8080 1.3 目录扫描 dirsearch -u 192.168.1.16 2 端口访问 2.1 80端口 2.2…

LRU缓存算法设计

LRU 缓存算法的核⼼数据结构就是哈希链表&#xff0c;双向链表和哈希表的结合体。这个数据结构⻓这样&#xff1a; 创建的需要有两个方法&#xff0c;一个是get方法&#xff0c;一个是put方法。 一些问题&#xff1a;为什么需要使用双向链表呢&#xff1f;因为删除链表的本身&…

[单master节点k8s部署]20.监控系统构建(五)Alertmanager

prometheus将监控到的异常事件发送给Alertmanager&#xff0c;然后Alertmanager将报警信息发送到邮箱等设备。可以从下图看出&#xff0c;push alerts是由Prometheus发起的。 安装Alertmanager config文件 [rootmaster prometheus]# cat alertmanager-cm.yaml kind: ConfigMa…

硕士文凭再耀眼,也没有第一学历刺眼?

在当今社会,教育被视为个人发展和社会进步的重要基石。随着高等教育的普及和竞争的加剧,学历成为了衡量个人能力、决定职业前景的重要标尺。然而,在这一过程中,“第一学历”的概念逐渐凸显,其影响力甚至在某些情况下超越了后续的硕士、博士等更高学历。这一现象引发了广泛…

软件测试与开发流程

软件测试简介 软件测试是对软件进行检测和评估&#xff0c;以确定其是否满足所需结果的过程和方法。它是在规定的条件下对程序进行操作&#xff0c;发现程序错误&#xff0c;从而衡量软件质量&#xff0c;并对其是否满足设计要求进行评估的过程。 与计算机系统操作有关的计算机…

使用Python绘制堆积柱形图

使用Python绘制堆积柱形图 堆积柱形图效果代码 堆积柱形图 堆积柱形图&#xff08;Stacked Bar Chart&#xff09;是一种数据可视化图表&#xff0c;用于显示不同类别的数值在某一变量上的累积情况。每一个柱状条显示多个子类别的数值&#xff0c;子类别的数值在柱状条上堆积在…

基于Redis和阻塞队列的 异步秒杀业务

异步前 之前的秒杀业务的查询优惠券、查询订单、减库存、创建订单都要查询数据库&#xff0c;而且有分布式锁&#xff0c;使得整个业务耗时长&#xff0c;对此采用异步操作处理&#xff0c;异步操作类似于餐厅点餐&#xff0c;服务员负责点菜产生订单、厨师负责根据订单后厨做…

IDEA越用越卡?教你轻松解决IDEA内存占用过高问题

大家好&#xff0c;我是瑶山&#xff0c;最近IDEA越用越卡了&#xff0c;刚刚内存卡爆&#xff0c;带着整个电脑也卡的飞起&#xff0c;只能重启了电脑。 虽然重启后又恢复到了流畅&#xff0c;但是问题还是如鲠在喉&#xff0c;痛定思痛&#xff0c;还是决定处理下&#xff01…

基于SpringBoot+Vue的招生管理系统(带1w+文档)

基于SpringBootVue的招生管理系统(带1w文档&#xff09; 通过招生管理系统的研究可以更好地理解系统开发的意义&#xff0c;而且也有利于发展更多的智能系统&#xff0c;解决了人才的供给和需求的平衡问题&#xff0c;招生管理系统的开发建设&#xff0c;由于其开发周期短&…

在 PostgreSQL 中,如何处理大规模的文本数据以提高查询性能?

文章目录 一、引言二、理解 PostgreSQL 中的文本数据类型三、数据建模策略四、索引选择与优化五、查询优化技巧六、示例场景与性能对比七、分区表八、数据压缩九、定期维护十、总结 在 PostgreSQL 中处理大规模文本数据以提高查询性能 一、引言 在当今的数据驱动的世界中&…

555定时器

硬件大杂烩 1. 555定时器内部结构 各引脚定义作用 引脚1: GND (地)&#xff0c;功能&#xff1a;接地&#xff0c;作为低电平(0V)。 引脚2: TRIG (触发)&#xff0c;功能&#xff1a;当此引脚电压降至1/3VCC (或由控制端决定的阈值电压)时&#xff0c;输出端给出高电平。 引…

【CUDA】 由GPGPU控制核心架构考虑CUDA编程中线程块的分配

GPGPU架构特点 由于典型的GPGPU只有小的流缓存&#xff0c;因此一个存储器和纹理读取请求通常需要经历全局存储器的访问延迟加上互连和缓冲延迟&#xff0c;可能高达数百个时钟周期。与CPU通过巨大的工作集缓存而降低延迟不同&#xff0c;GPU硬件多线程提供了数以千计的并行独…

YOLOv8改进 添加轻量级注意力机制ELAttention

一、ELA论文 论文地址:2403.01123 (arxiv.org) 二、Efficient Local Attention结构 ELA (Efficient Local Attention) 被用于处理自然语言处理任务中的序列数据。它旨在提高传统注意力机制的效率,并减少其计算和存储成本。 在传统的注意力机制中,计算每个输入位置与所有其…

MYSQL 四、mysql进阶 6(索引的创建与设计原则)

一、索引的声明和使用 1.1 索引的分类 MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。 从 功能逻辑 上说&#xff0c;索引主要有 4 种&#xff0c;分别是普通索引、唯一索引、主键索引、全文索引。 按照 物理实现方式 &#xff0c;索引可…

Apache Seata配置管理原理解析

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Apache Seata配置管理原理解析 说到Seata中的配置管理&#xff0c;大家可能会想到Seata中适配…

47.HOOK引擎优化支持CALL与JMP位置做HOOK

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 上一个内容&#xff1a;46.修复HOOK对代码造成的破坏 以 46.修复HOOK对代码造成的破坏 它的代码为基础进行修改 优化的是让引擎支持从短跳JMP&#xff08;E9&…