Java对象比对工具

news2024/10/5 13:38:59

背景

  前段时间的任务中,遇到了需要识别两个对象不同属性的场景,如果使用传统的一个个属性比对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;
    }

主流程方法,流程如下

  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() {
            @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);
    }

}

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

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

相关文章

76 4G模组 境外拨号入网注意

1 引言 最近朋友把国内的设备拿到新加坡了&#xff0c;然后发现原本国内可以使用的设备无法在异国他乡联网&#xff0c;所以就叫我来看看&#xff0c;发现是附网返回状态、入网APN发生了改变导致的。另外&#xff0c;如果在境外使用国产4G模组拨号入网&#xff0c;也需要关注4G…

Nginx实战:nginx性能压测(ab)

在nginx的生产实践中,不管是服务上线,还是性能优化,都会遇到需要对nginx的性能压测,本文介绍一个简单的压测工具:ab命令 ab(Apache Bench)是一个常用的HTTP压力测试工具,可以用来测试Nginx的性能和压力。ab命令可以指定并发请求数、请求数、请求类型等参数,并输出测试…

MySQL第三次作业--DML语句(INSERT)

目录 一、在数据库中创建一个表student&#xff0c;用于存储学生信息 二、向student表中添加一条新记录&#xff0c;记录中id字段的值为1&#xff0c;name字段的值为"monkey"&#xff0c;grade字段的值为98.5 三、向student表中添加多条新记录&#xff1a; 2,&qu…

G1.【C语言】EasyX初步了解

1.介绍 EasyX 是针对 C/C 的图形库&#xff0c;可以帮助使用C/C语言的程序员快速上手图形和游戏编程。 2.安装 EasyX Graphics Library for CEasyX Graphics Library 是针对 Visual C 的绘图库&#xff0c;支持 VC6.0 ~ VC2019&#xff0c;简单易用&#xff0c;学习成本极低…

使用WinSCP工具连接Windows电脑与Ubuntu虚拟机实现文件共享传输

一。环境配置 1.首先你的Windows电脑上安装了VMware虚拟机&#xff0c;虚拟机装有Ubuntu系统&#xff1b; 2.在你的windows电脑安装了WinSCP工具&#xff1b; 3.打开WinSCP工具默认是这样 二。设置WinSCP连接 打开WinSCP&#xff0c;点击新标签页&#xff0c;进入到如下图的…

编码与加密

编码与加密在爬虫中经常涉及&#xff0c;常见的编码有base64, unicode, urlencode&#xff0c;常见的加密有MD5, SHA1, HMAC, DES, AES, RSA。 下面逐一介绍&#xff1a; 一&#xff0c;编码 1.1 常规编码 常规编码约定了字符集中字符与一定长度二进制的映射关系&#xff0…

leetcode刷题(51-60)

算法是码农的基本功&#xff0c;也是各个大厂必考察的重点&#xff0c;让我们一起坚持写题吧。 遇事不决&#xff0c;可问春风&#xff0c;春风不语&#xff0c;即是本心。 我们在我们能力范围内&#xff0c;做好我们该做的事&#xff0c;然后相信一切都事最好的安排就可以啦…

BES 平台 SDK之ANC 参数调整

前言: 最近项目开发进入到DV 阶段,客户临时提了一个需求,希望在ota升级的时候,保留ANC 参数下的total_gain 值,ota只更新滤波器相关参数。total_gain 继续使用产线校准好的值。 一:ANC 参数 1.首先需要找到代码对应ANC 加载的函数: best1502x_ibrt_anc_…

TeXstudio对已加载宏包的命令标记为暗红色未知命令

宏包已正常加载&#xff0c;编译也正常&#xff0c;但却将某些命令标记为暗红色。 具体的原因可参考 https://sourceforge.net/p/texstudio/wiki/Frequently%20Asked%20Questions/#how-does-txs-know-about-valid-commandshttps://sourceforge.net/p/texstudio/wiki/Frequent…

Vue 3集成krpano 全景图展示

Vue 3集成krpano 全景图展示 星光云全景系统源码 VR全景体验地址 星光云全景VR系统 将全景krpano静态资源文件vtour放入vue项目中 导入vue之前需要自己制作一个全景图 需要借助官方工具进行制作 工具下载地址&#xff1a;krpano工具下载地址 注意事项&#xff1a;vuecli…

(软件06)串口屏的应用,让你的产品显得高级一点(下篇)

本文目录 学习前言 单片机代码实现 学习前言 目前市面上我记得好像有IIC的屏幕、SPI的屏幕、并口屏幕、还有就是今天我们介绍的这个串口屏了&#xff0c;串口屏&#xff0c;就是用串口进行通讯的&#xff0c;上篇我们已经介绍了屏幕供应商提供的上位机软件进行配置好了&#…

两年经验前端带你重学前端框架必会的ajax+node.js+webpack+git等技术的个人学习心得、作业及bug记录 Day1

黑马程序员前端AJAX入门到实战全套教程&#xff0c;包含学前端框架必会的&#xff08;ajaxnode.jswebpackgit&#xff09;&#xff0c;一套全覆盖 Day1 你好,我是Qiuner. 为帮助别人少走弯路和记录自己编程学习过程而写博客 这是我的 github https://github.com/Qiuner ⭐️ ​…

前端八股文 对$nextTick的理解

$nexttick是什么? 获取更新后的dom内容 为什么会有$nexttick ? vue的异步更新策略 (这也是vue的优化之一 要不然一修改数据就更新dom 会造成大量的dom更新 浪费性能) 这是因为 message &#xff08;data&#xff09;数据在发现变化的时候&#xff0c;vue 并不会立刻去更…

.mkp勒索病毒:深度解析与防范

引言&#xff1a; 在数字化时代&#xff0c;网络安全问题日益严峻&#xff0c;其中勒索病毒作为一种极具破坏性的恶意软件&#xff0c;严重威胁着个人用户和企业机构的数据安全。在众多勒索病毒家族中&#xff0c;.mkp勒索病毒以其强大的加密能力和广泛的传播方式&#xff0c;成…

54、一维和二维自组织映射(matlab)

1、一维和二维自组织映射原理 一维和二维自组织映射&#xff08;Self-Organizing Maps, SOM&#xff09;是一种无监督的机器学习算法&#xff0c;通过学习输入数据的拓扑结构&#xff0c;将高维输入数据映射到低维的网格结构中&#xff0c;使得相似的输入数据点在映射空间中也…

异步调用 - 初识

目录 1、引入 2、同步调用 2.1、例子&#xff1a;支付功能 2.2、同步调用的好处 2.3、同步调用的缺点 3、异步调用 3.1、异步调用的方式 3.2、异步调用的优势 3.3、异步调用的缺点 3.4、什么场景下使用异步调用 3.5、MQ技术选型 1、引入 为什么想要异步通信呢&…

java 线程同步机制 synchronized

synchronized 代码块 的参数 为对象 且要唯一性 synchronized 修饰方法&#xff1a; 非静态是 当前类的实例 this 静态是当前类 :当前类名.class Cclass.class extends有多实例 不能用this 。 用当前类作为唯一标识 synchronized 优缺点

vue3项目图片压缩+rem+自动重启等plugin使用与打包配置

一、Svg配置 每次引入一张 SVG 图片都需要写一次相对路径&#xff0c;并且对 SVG 图片进行压缩优化也不够方便。 vite-svg-loader插件加载SVG文件作为Vue组件&#xff0c;使用SVGO进行优化。 插件网站https://www.npmjs.com/package/vite-svg-loader 1. 安装 pnpm i vite-svg…

查询数据库下所有表的数据量

个人思路: 首先把库里Schema下表名拿出来放记事本(EmEditor)里, 用一下正则匹配替换 (\w) → select \1 tableName,count(1) from \1 union all 然后把最后的union all删除掉,替换为order by tableName

eggNOG-mapper:功能注释集大成者

安装 Home eggnogdb/eggnog-mapper Wiki GitHub git clone https://github.com/eggnogdb/eggnog-mapper.git cd eggnog-mapper python setup.py install # 如果您的 eggnog-mapper 路径是/home/user/eggnog-mapper export PATH/home/user/eggnog-mapper:/home/user/eggnog-…