记一次数据填充的代码结构优化过程2

126 阅读6分钟

1,编译期检查

阅读这边文章之前,请先看 # 记一次数据填充的代码结构优化过程

1.1,问题分析

为什么FillMethod注解不能实现编译期的检查?

  1. FillMethod注解上指定的方法名是字符串常量,编译期不会去检查。
  2. 注解上无法直接指定具体Method,注解上的值只能是常量。

结论:从Filler上定义注解来指定具体的数据填充的方法无法实现编译期的检查

那我们不妨放一个方向,从数据填充的方法上指定具体的Filler接口呢?

  1. 为数据填充方法与Filler接口是一对一的关系
  2. 注解上指定Class是能进行编译期检查的

结论:定义一个FillFor注解,作用到数据填充方法上,并指定关联到具体的Filler接口。并移除FillMethod注解。

1.2,核心类图

image.png

1.3,版本五

/**
 *   数据填充标识接口
 * @see ZoneInfoFiller
 * @see CustomerInfoFiller
 * @see FillFor
 * @see FillerAdapter
 */
public interface Filler {
}

/**
 * 用于{@link FillFieldService} 中数据填充方法与{@link Filler} 的绑定,以便{@link FillerAdapter} 进行自动数据填充, <br/>
 * 使用方法:在{@link FillFieldService} 的数据填充方法上声明指定的 {@link Filler} <br/>
 * @see FillFieldService 定义数据填充方法的接口
 * @see FillerAdapter 数据填充调用
 * @see Filler
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FillFor {

    /**
     * 绑定具体的{@link Filler} 接口 <br/>
     *  例:<br/>
     *    {@code  @FillFor(filler = CustomerInfoFiller.class)}
     */
    Class<? extends Filler> filler();

}

/**
 *  数据填充适配器,用于数据填充与业务代码的解藕,自动适配到各个具体的{@link Filler} 进行数据填充
 * @see Filler
 * @see FillFor
 * @see FillFieldService
 */
@Component
public class FillerAdapter {

    /** 每一个{@link Filler} 在{@link FillFieldService} 中对应的数据填充方法 */
    private static final Map<Class<? extends Filler>, String> FILL_METHOD_CACHE = new ConcurrentHashMap<>();

    @Resource
    private FillFieldService fillFieldService;

    static {
        Method[] methods = FillFieldService.class.getMethods();
        for (Method method : methods) {
            FillFor fillFor = method.getAnnotation(FillFor.class);
            if (fillFor != null) {
                FILL_METHOD_CACHE.put(fillFor.filler(), method.getName());
            }
        }
    }

    /**
     *  数据填充,适配到各个{@link Filler}
     * @param list 待填充的数据
     */
    public void fill(List<? extends Filler> list) {
        if (ListUtils.isEmpty(list)) {
            return;
        }
        Filler filler = null;
        for (Filler item : list) {
            if (item != null) {
                filler = item;
                break;
            }
        }
        if (filler == null) {
            return;
        }
        Class<?>[] interfaces = filler.getClass().getInterfaces();
        for (Class<?> anInterface : interfaces) {
            // 是否为数据填充接口
            if (Filler.class.isAssignableFrom(anInterface)) {
                // 获取指定的数据填充方法,进行数据填充
                String fillMethod = FILL_METHOD_CACHE.get(anInterface);
                ReflectUtil.invoke(pplFillFieldService, fillMethod, list);
            }
        }
    }

}
// 客户信息字段
public interface CustomerInfoFiller extends Filler {

    String getUin();
    void setCId(String cId);
    void setCName(String cName);

}
// 区域信息字段
public interface ZoneInfoFiller extends Filler {

    String getZoneName();
    void setZoneId(String zoneId);
    void setAreaName(String areaName);

}

/**
 * 负责字段的填充  <br/>
 * 目前数据填充方法的入参只能是 {@code  List<? extends Filler> list}  <br/>
 * 例: <br/>
 * {@code  List<? extends CustomerInfoFiller> list}  <br/>
 * {@code  List<? extends ZoneInfoFiller> list} <br/>
 * @see FillerAdapter
 * @see FillFor
 */
public interface FillFieldService {

    /**
     *   根据 {@link CustomerInfoFiller} 进行客户信息数据填充
     * @param list 待填充的数据
     */
    @FillFor(filler = CustomerInfoFiller.class)
    void fillCustomerInfo(List<? extends CustomerInfoFiller> list);

    /**
     *   根据 {@link ZoneInfoFiller} 进行区域信息数据填充
     * @param list 待填充的数据
     */
    @FillFor(filler = ZoneInfoFiller.class)
    void fillZoneInfo(List<? extends ZoneInfoFiller> list);

}

// table_a的定时任务
public void jobSyncTableA() {
        String sql = "****";
        // 从业务表生成table_a的数据
        List<TableA> all = bBHelper.getRaw(TableA.class, sql);
        // 调用适配器进行数据填充
        fillerAdapter.fill(all);
        // 数据存储
        store();
    }

// table_b的定时任务
public void jobSyncTableB() {
        String sql = "****";
        // 从业务表生成table_b的数据
        List<TableB> all = bBHelper.getRaw(TableB.class, sql);
        // 调用适配器进行数据填充
        fillerAdapter.fill(all);
        // 数据存储
        store();
    }

2,Filler-Handler模式

渐渐的意识到一个问题,若随着业务的发展,数据填充的使用场景越来越多,导致FillFieldService接口的方法越来越多,其实现类代码越来越冗长。考虑拆分。

2.1,拆分设计

每一个数据填充方法都是一个单独的小功能,可以按这个维度进行拆分。 定义数据填充处理器接口 FillerHandler,一个Filler接口对应一个FillerHandler,同时可以移除掉Filller顶层标识接口,直接使用ZoneInfoFiller这样的接口。但在业务含义上依然将ZoneInfoFiller这样的接口统称为Filler接口,只是不用在代码中显式的标识。

  1. 一个filler接口 ---> 一个数据填充处理器实现类
  2. 移除掉之前的FillFieldService接口,分解为多个数据填充处理器实现类
  3. 移除FillFor注解,其功能在FillerHandler中实现

另外,之前Filler接口中约定的定义方法名为 set或者get开头,感觉不太好,万一存在get、set方法被重写的情况,现约定

  1. Filler接口需要提供参数的方法,以provide开头,替换掉之前get开发的方法
  2. Filler接口需要进行数据填充的方法,以fill开发,替换掉之前set开发的方法

还有,考虑到某个实体类进行数据填充时,可能会实现多个Filler接口,而这些Filler接口可能会有依赖关系(这是之前的版本中没有考虑到的情况)。

  1. 在FillerHandler接口中定义执行顺序号,由开发人员根据实际情况赋值
  2. 后续考虑加上依赖关系自动分析,来确定FillerHandler执行顺序号

2.2,核心类图

image.png

2.3,版本六

/**
 *  数据填充处理器
 * @param <T> 支持数据填充处理的参数类型,此参数类型暂时只支持接口 <br/>
 *           例:{@link CustomerInfoFiller}
 * @see FillerAdapter
 */
public interface FillerHandler<T> {

    /**
     *   数据填充
     */
    void fill(List<T> obj);

    /**
     *   获取对应的 Filler 的类型
     */
    default Class<T> getSupportFiller(){
        // 自动获取范型 T 的类型返回
        Type[] types = getClass().getGenericInterfaces();
        for (Type type : types) {
            if (type instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) type;
                if (pt.getRawType().equals(FillerHandler.class)) {
                    Type fillerType = pt.getActualTypeArguments()[0];
                    return ((Class<T>) fillerType);
                }
            }
        }
        throw new UnsupportedOperationException("the default 'getSupportFiller' method can not get value, please Override");
    }

    /**
     *   获取执行顺序,多个不同的FilerHandler处理数据时,可能会有依赖关系,通过顺序号控制,值越小越先执行 <br/>
     *   开发人员根据具体的Filler进行判断来确定order值,一个Filler需要填充的字段值是不是另一个Filler所需要的字段值
     */
    default int getExecOrder() {
        return 0;
    }

}

/**
 *  数据填充适配器,用于数据填充与业务代码的解藕,自动适配到各个具体的{@link FillerHandler} 进行数据填充
 * @see FillerHandler
 */
@Component
public class FillerAdapter implements InitializingBean {

    @Resource
    private List<FillerHandler<?>> fillerHandlers;

    private final Map<Class<?>, FillerHandler<?>> filerHandlerCache = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        if (ListUtils.isEmpty(fillerHandlers)) {
            return;
        }
        // bean初始化完成后,扫描所有的FillerHandler,注册进来
        for (FillerHandler<?> fillerHandler : fillerHandlers) {
            filerHandlerCache.put(fillerHandler.getSupportFiller(), fillerHandler);
        }
    }


    /**
     *  数据填充,适配到各个{@link FillerHandler}
     * @param list 待填充的数据
     */
    public void fill(List<?> list) {
        if (ListUtils.isEmpty(list)) {
            return;
        }
        Object filler = null;
        for (Object item : list) {
            if (item != null) {
                filler = item;
                break;
            }
        }
        if (filler == null) {
            return;
        }
        // 提倡面向接口编程,暂时只考虑基于接口的FillerHandler范型,不考虑FillerHandler范型为实体类的情况
        // 若存在FillerHandler范型为实体类的情况,那应该是业务强相关,不能抽离到FillerHandler模式中来
        Class<?>[] interfaces = filler.getClass().getInterfaces();
        List<FillerHandler<?>> handlerList = new ArrayList<>();
        for (Class<?> anInterface : interfaces) {
            // 获取指定的FillerHandler,进行数据填充
            FillerHandler<?> fillerHandler = filerHandlerCache.get(anInterface);
            if (fillerHandler != null) {
                handlerList.add(fillerHandler);
            }
        }
        // 顺序号值越小越先执行
        handlerList.stream().sorted(Comparator.comparing(FillerHandler::getExecOrder))
                .forEach(fillerHandler -> fillerHandler.fill((List)list));
    }

}

// 客户信息字段
public interface CustomerInfoFiller {

    String provideUin();
    void fillCId(String cId);
    void fillCName(String cName);

}
// 区域信息字段
public interface ZoneInfoFiller {

    String provideZoneName();
    void fillZoneId(String zoneId);
    void fillAreaName(String areaName);

}

@Service
public class ZoneInfoFillerHandler implements FillerHandler<ZoneInfoFiller> {

    @Override
    public void fill(List<ZoneInfoFiller> list) {
        // 区域信息数据填充实现
    }
}

@Service
public class CustomerInfoFillerHandler implements FillerHandler<CustomerInfoFiller> {

    @Override
    public void fill(List<CustomerInfoFiller> result) {
        // 客户信息数据填充实现
    }
}

// table_a的定时任务
public void jobSyncTableA() {
        String sql = "****";
        // 从业务表生成table_a的数据
        List<TableA> all = bBHelper.getRaw(TableA.class, sql);
        // 调用适配器进行数据填充
        fillerAdapter.fill(all);
        // 数据存储
        store();
    }

// table_b的定时任务
public void jobSyncTableB() {
        String sql = "****";
        // 从业务表生成table_b的数据
        List<TableB> all = bBHelper.getRaw(TableB.class, sql);
        // 调用适配器进行数据填充
        fillerAdapter.fill(all);
        // 数据存储
        store();
    }