1,编译期检查
阅读这边文章之前,请先看 # 记一次数据填充的代码结构优化过程
1.1,问题分析
为什么FillMethod注解不能实现编译期的检查?
- FillMethod注解上指定的方法名是字符串常量,编译期不会去检查。
- 注解上无法直接指定具体Method,注解上的值只能是常量。
结论:从Filler上定义注解来指定具体的数据填充的方法无法实现编译期的检查
那我们不妨放一个方向,从数据填充的方法上指定具体的Filler接口呢?
- 为数据填充方法与Filler接口是一对一的关系
- 注解上指定Class是能进行编译期检查的
结论:定义一个FillFor注解,作用到数据填充方法上,并指定关联到具体的Filler接口。并移除FillMethod注解。
1.2,核心类图
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接口,只是不用在代码中显式的标识。
- 一个filler接口 ---> 一个数据填充处理器实现类
- 移除掉之前的FillFieldService接口,分解为多个数据填充处理器实现类
- 移除FillFor注解,其功能在FillerHandler中实现
另外,之前Filler接口中约定的定义方法名为 set或者get开头,感觉不太好,万一存在get、set方法被重写的情况,现约定
- Filler接口需要提供参数的方法,以provide开头,替换掉之前get开发的方法
- Filler接口需要进行数据填充的方法,以fill开发,替换掉之前set开发的方法
还有,考虑到某个实体类进行数据填充时,可能会实现多个Filler接口,而这些Filler接口可能会有依赖关系(这是之前的版本中没有考虑到的情况)。
- 在FillerHandler接口中定义执行顺序号,由开发人员根据实际情况赋值
- 后续考虑加上依赖关系自动分析,来确定FillerHandler执行顺序号
2.2,核心类图
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();
}