【工作中问题解决实践 六】基于反射及类装饰模式的数据对比框架(上)

简介: 【工作中问题解决实践 六】基于反射及类装饰模式的数据对比框架(上)

如同三年前【工作中问题解决实践 四】动态解析Json结构最佳实践一样,又遇到了切换业务系统的事情,那么就涉及到了数据的迁移操作,迁移来的数据又需要和三方系统进行对比来验证其有效性。简而言之背景问题就是:我当前系统有一批公司数据,我要巡检一下这批公司数据和天眼查的数据是否能匹配的上,那么就需要对比这些数据而公司数据又是由多个实体组成的,每个实体又有自己的唯一性规则,例如公司的唯一性是税号,人员的唯一性则是人员类型+姓名,股东的唯一性是:股东姓名+股东类型+出资+占比,如果把这些逻辑全部冗余进去,不仅复杂,以后代码也会越写越臃肿,不好扩展。

那么如何才能对一批数据进行可扩展的非侵入式的校验呢?这就需要开发一个数据对比框架,类似于CollectionUtils的对比方法,但实际上又较之更为复杂。于是仿照CollectionUtils的写法写了一套相对业务定制的对比框架,整体分为如下几个部分:

这套设计想法也来自于之前学习的设计模式的灵感:代理模式和简单版的装饰器模式,虽然我这里的具体实体没有功能,不能完全套用代理模式或装饰器模式,但是也提供了一个思路,实体被规则器代理或装饰后没有增强自己的方法而是把自己作为规则器方法实现的数据来源。对于代理模式和装饰器模式感兴趣的可以跳转直戳。

1 规则器接口定义

其实简单来看也可以理解为功能增强接口,只是把数据实体用作了数据来源而非原始功能的提供方,这个就定义了增强规则所应该具备的能力,为了适配不同的实体类型,这里的接口方法定义成泛型:

/**
 * * @Name Compare
 * * @Description
 * * @author tianmaolin
 * * @Date 2022/9/7
 *
 * @param <T> the type parameter
 */
public interface DataCompare<T> {
    /**
     * 获取对比属性配置
     *
     * @return the compare property config
     */
    CompareProperty getComparePropertyConfig();
    /**
     * 数据比对依据关键key
     *
     * @return the string
     */
    String getUniqueKey();
    /**
     * 通过数据唯一标识获取原始数据id
     *
     * @return the id by unique key
     */
    Long getIdByUniqueKey();
    /**
     * 待对比数据
     *
     * @return the data
     */
    T getData();
    /**
     * 数据集合转为比较实体集合
     *
     * @param list the list
     * @return the list
     */
    List<DataCompare<T>> toCompareList(final List<T> list);
}

需要注意的是这里的属性对比类中的属性配置:

/**
 * * @Name CompareProperty
 * * @Description
 * * @author tianmaolin
 * * @Date 2022/9/8
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CompareProperty {
    /**
     * 对比类型
     *
     * @see PropertyConfigTypeEnum
     */
    private Integer type;
    /**
     * 对比属性列表
     */
    private List<String> compareProperty;
}

枚举指明了我们在交集数据对比时对待属性列表的态度

/**
 * * @Name ComStoreBindStatusEnum
 * * @Description
 * * @author tianmaolin
 * * @Date 2022/4/29
 */
@Getter
public enum PropertyConfigTypeEnum {
    FOCUS(1, "对比时仅关注的属性"),
    IGNORE(2, "对比时忽略的属性");
    private final Integer code;
    private final String desc;
    PropertyConfigTypeEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    public static PropertyConfigTypeEnum codeOf(Integer code) {
        for (PropertyConfigTypeEnum value : values()) {
            if (value.code.equals(code)) {
                return value;
            }
        }
        throw new IllegalArgumentException("no PropertyConfigTypeEnum for code" + code);
    }
}

当实体的属性大多数都需要对比时,例如20个字段有18个需要对比,那么就将类型标志位IGNORE,反之如果20个字段仅有1个字段在数据对比时需要关心,则将类型标志位设置为FOCUS

2 规则实现类

基于装饰器比较接口我们明确了在对比数据时装饰类一定要具备的功能(实现的方法)

1 规则器实现配置

这里我们以公司的股东信息为例:

/**
 * * @Name ShareHolderDateCompare
 * * @Description 股东信息对比类
 * * @author tianmaolin
 * * @Date 2022/9/7
 */
@Service
public class ShareHolderDataCompare implements DataCompare<CompanyShareholderEntity> {
    private CompanyShareholderEntity companyShareholderEntity;
    @Override
    public String getUniqueKey() {
        List<String> uniqueEnums = new ArrayList();
        uniqueEnums.add(StringUtils.trimToEmpty(companyShareholderEntity.getShareholderName()));
        uniqueEnums.add(companyShareholderEntity.getType().toString());
        uniqueEnums.add(StringUtils.trimToEmpty(companyShareholderEntity.getAmomon()));
        uniqueEnums.add(StringUtils.trimToEmpty(companyShareholderEntity.getPercent()));
        return String.join(",", uniqueEnums);
    }
    @Override
    public Long getIdByUniqueKey() {
        return companyShareholderEntity.getId();
    }
    @Override
    public CompareProperty getComparePropertyConfig() {
        List<String> ignoreProperties = new ArrayList<>();
        ignoreProperties.add(FieldNameUtil.noPrefix(CompanyShareholderEntity::getId));
        ignoreProperties.add(FieldNameUtil.noPrefix(CompanyShareholderEntity::getCreateTime));
        return new CompareProperty(PropertyConfigTypeEnum.IGNORE.getCode(), ignoreProperties);
    }
    @Override
    public CompanyShareholderEntity getData() {
        return companyShareholderEntity;
    }
    @Override
    public List<DataCompare<CompanyShareholderEntity>> toCompareList(List<CompanyShareholderEntity> list) {
        if (null == list) {
            return Lists.newArrayList();
        }
        return list.stream().map(x -> {
            ShareHolderDataCompare shareHolderDateCompare = new ShareHolderDataCompare();
            shareHolderDateCompare.companyShareholderEntity = x;
            return shareHolderDateCompare;
        }).collect(Collectors.toList());
    }
}

这里只是股东,如果想要扩展别的实体对比,那么只需配置好对比规则类就可以直接对比了。

2 Hutool获取属性名称

值得注意的是这里在设置关键属性时为了避免魔法值使用,直接基于开源的hutool做了处理,通过get或者set方法获取属性名称,并可以定制样式:

/**
 * * @Name FieldUtil
 * * @Description
 * * @author tianmaolin
 * * @Date 2022/9/5
 */
import cn.hutool.core.util.StrUtil;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
/**
 * 属性工具类,用来获取 Getter 和 Setter 属性的名称。支持首字母小写样式,下划线的样式和自定义样式
 */
public class FieldNameUtil {
    /**
     * 下划线样式,小写
     *
     * @param <T> the type parameter
     * @param fn  the fn
     * @return the string
     */
    public static <T> String underline(IGetter<T> fn) {
        return toSymbolCase(fn, '_');
    }
    /**
     * 下划线样式,大写
     *
     * @param <T> the type parameter
     * @param fn  the fn
     * @return the string
     */
    public static <T> String underlineUpper(IGetter<T> fn) {
        return underline(fn).toUpperCase();
    }
    /**
     * 依据符号转换样式
     *
     * @param <T>    the type parameter
     * @param fn     the fn
     * @param symbol the symbol
     * @return the string
     */
    public static <T> String toSymbolCase(IGetter<T> fn, char symbol) {
        return StrUtil.toSymbolCase(noPrefix(fn), symbol);
    }
    /***
     * 转换getter方法引用为属性名,首字母小写
     * @param <T>  the type parameter
     * @param fn the fn
     * @return the string
     */
    public static <T> String noPrefix(IGetter<T> fn) {
        return getGeneralField(fn);
    }
    /**
     * 下划线样式,小写
     *
     * @param <T> the type parameter
     * @param <R> the type parameter
     * @param fn  the fn
     * @return the string
     */
    public static <T, R> String underline(ISetter<T, R> fn) {
        return toSymbolCase(fn, '_');
    }
    /**
     * 下划线样式,大写
     *
     * @param <T> the type parameter
     * @param <R> the type parameter
     * @param fn  the fn
     * @return the string
     */
    public static <T, R> String underlineUpper(ISetter<T, R> fn) {
        return underline(fn).toUpperCase();
    }
    /**
     * 依据符号转换样式
     *
     * @param <T>    the type parameter
     * @param <R>    the type parameter
     * @param fn     the fn
     * @param symbol the symbol
     * @return the string
     */
    public static <T, R> String toSymbolCase(ISetter<T, R> fn, char symbol) {
        return StrUtil.toSymbolCase(noPrefix(fn), symbol);
    }
    /**
     * 转换setter方法引用为属性名,首字母小写
     *
     * @param <T> the type parameter
     * @param <R> the type parameter
     * @param fn  the fn
     * @return the string
     */
    public static <T, R> String noPrefix(ISetter<T, R> fn) {
        return getGeneralField(fn);
    }
    /**
     * 获得set或get或is方法对应的标准属性名,其它前缀的方法名使用原名
     */
    private static String getGeneralField(Serializable fn) {
        SerializedLambda lambda = getSerializedLambda(fn);
        String getOrSetMethodName = lambda.getImplMethodName();
        final String generalField = StrUtil.getGeneralField(getOrSetMethodName);
        return StrUtil.isEmpty(generalField) ? getOrSetMethodName : generalField;
    }
    /***
     * 获取类对应的Lambda
     */
    private static SerializedLambda getSerializedLambda(Serializable fn) {
        //先检查缓存中是否已存在
        SerializedLambda lambda;
        try {
            //提取SerializedLambda并缓存
            Method method = fn.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            lambda = (SerializedLambda) method.invoke(fn);
        } catch (Exception e) {
            throw new IllegalArgumentException("获取SerializedLambda异常, class=" + fn.getClass().getSimpleName(), e);
        }
        return lambda;
    }
    /**
     * getter方法接口定义
     *
     * @param <T> the type parameter
     */
    @FunctionalInterface
    public interface IGetter<T> extends Serializable {
        /**
         * Apply object.
         *
         * @param source the source
         * @return the object
         */
        Object apply(T source);
    }
    /**
     * setter方法接口定义
     *
     * @param <T> the type parameter
     * @param <U> the type parameter
     */
    @FunctionalInterface
    public interface ISetter<T, U> extends Serializable {
        /**
         * Accept.
         *
         * @param t the t
         * @param u the u
         */
        void accept(T t, U u);
    }
}


相关文章
|
6月前
|
设计模式
二十三种设计模式全面解析-装饰器模式-超越继承的灵活装扮
二十三种设计模式全面解析-装饰器模式-超越继承的灵活装扮
|
6月前
|
设计模式 存储
二十三种设计模式全面解析-原型模式进阶之原型管理器:集中管理对象原型的设计模式之道
二十三种设计模式全面解析-原型模式进阶之原型管理器:集中管理对象原型的设计模式之道
|
4月前
|
安全 Java
打破常规!JAVA反射技术让你“动态”编程
【7月更文挑战第1天】Java反射技术是动态编程的利器,它揭示了类的内部信息,允许运行时操作对象、调用方法和创建实例。动态加载类、调用方法和创建对象是其常见应用场景,但需注意反射带来的性能损失、安全风险和代码可读性下降。在平衡灵活性与效率时谨慎使用。
43 0
|
6月前
|
设计模式 存储 SQL
第四篇 行为型设计模式 - 灵活定义对象间交互
第四篇 行为型设计模式 - 灵活定义对象间交互
127 0
|
6月前
|
存储 Java
java反射——设计框架的灵魂
java反射——设计框架的灵魂
|
6月前
|
设计模式
二十三种设计模式全面解析-建造者模式:构建完美对象的秘密武器
二十三种设计模式全面解析-建造者模式:构建完美对象的秘密武器
|
6月前
|
设计模式
二十三种设计模式全面解析-装饰器模式的高级应用:打造灵活可扩展的通知系统
二十三种设计模式全面解析-装饰器模式的高级应用:打造灵活可扩展的通知系统
|
缓存 监控 Java
Java动态代理:优化静态代理模式的灵活解决方案
Java动态代理:优化静态代理模式的灵活解决方案
220 0
|
JSON 数据库 数据格式
【工作中问题解决实践 六】基于反射及类装饰模式的数据对比框架(下)
【工作中问题解决实践 六】基于反射及类装饰模式的数据对比框架(下)
113 0
|
设计模式 前端开发 Java
前端通用编程基础的设计模式之单例
单例模式是一种常见的设计模式,它可以保证一个类只有一个实例,并提供了一个全局访问点。下面就让我们来看看单例模式的特点和优势。
59 0