背景
项目中有很多地方存在如下业务逻辑:
从数据库中查询多条数据并构成旧集合,然后遍历旧集合,处理每条数据,将返回的新元素并加入新集合。
目前实现该逻辑的方式是,采用线程池并借助CountDownLatch
。
核心代码如下:
//新集合
List<OperatorProcessVO> vos = Collections.synchronizedList(new ArrayList<>());
//数据库记录
CountDownLatch latch = new CountDownLatch(operatorProcesses.size());
try {
for (OperatorProcess operatorProcess : operatorProcesses) {
//线程池
executorService.submit(()->{
try {
//业务处理
......
......
//得到新元素vo,并加入新集合
vos.add(vo);
}catch (Exception e){
log.error("xxx", e);
}
finally {
latch.countDown();
}
});
}
//等待
latch.await();
} catch (Exception e) {
log.error("xxx", e);
}
当前存在的问题是,项目中有很多地方都有这样的处理,导致出现了大量重复的代码,加大了后期维护成本。
因此,希望有一种统一的框架来将上述逻辑封装,而不是重复造轮子。
实现方案
1.提交任务入口
首先,定义任务提交类TaskExecuteFramework
,其中的submitTask
方法用于提交任务。如下
@Component
@Slf4j
public class TaskExecuteFramework {
//线程池
private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
public <OLD,NEW,ADDITIONAL> List<NEW> submitTask(List<OLD> oldList, TaskProcessor<NEW, OLD> taskProcessor, ADDITIONAL additional){
CountDownLatch latch = new CountDownLatch(oldList.size());
//新集合
List<NEW> newList = Collections.synchronizedList(new ArrayList<>());
try {
for (OLD old : oldList) {
executorService.submit(()->{
try {
if(additional != null){
ThreadLocalInfoUtil.setValue("additionalParam", additional);
}
//处理业务数据
NEW n = taskProcessor.handTask(old);
//加入新集合
if(newList != null){
Optional
.ofNullable(n)
.ifPresent(entity->{
newList.add(entity);
});
}
}catch (Exception e){
log.error("handTask error", e);
}finally {
latch.countDown();
ThreadLocalInfoUtil.remove();
}
});
}
latch.await();
}catch (Exception e){
log.error("submitTask error", e);
}
return newList;
}
}
该方法的核心逻辑相比之前没有变化,不同点在于:
首先,由于需要处理不同类型的业务,所以参数类型不能写死,而是使用泛型代替。其中泛型
OLD
代表从数据库查询的集合元素,NEW
代表新集合元素,ADDITIONAL
稍后介绍。
其次,由于每一种查询任务的处理逻辑都不同,所以需要一个接口来抽象任务的处理,TaskProcessor
就是做这个工作的。
2.任务处理接口
该接口就是上面提到的TaskProcessor
public interface TaskProcessor<NEW,OLD> {
NEW handTask(OLD old);
}
其方法入参OLD
表示旧的元素,返回值NEW
代表处理后的新元素。
3.额外参数处理
在前面的submitTask
方法里有如下代码
if(additional != null){
ThreadLocalInfoUtil.setValue("additionalParam", additional);
}
为什么需要这块逻辑呢?在调用TaskProcessor
类的handTask
方法时,如果某个业务处理还需要其他参数,常规做法在handTask
方法再加参数,但是这样会影响到所有实现类。
所以,在提交任务时可以传额外的ADDITIONAL
参数并放入ThreadLocal
,这样在TaskProcessor
的handTask
方法中,就可以直接从ThreadLocal
获取。
使用方式
1.实现TaskProcessor接口
具体业务类会实现TaskProcessor
接口。
如下,有三个实现类,分别代表三种业务实现。
以ShowProcessTaskProcessor
类为例,如下
@Service("showProcessTaskProcessor")
public class ShowProcessTaskProcessor implements TaskProcessor<OperatorProcessVO, OperatorProcess>{
}
2.注入TaskExecuteFramework
在ShowProcessTaskProcessor
类中注入
@Autowired
private TaskExecuteFramework taskExecuteFramework;
@Autowired
@Qualifier("showProcessTaskProcessor")
private TaskProcessor<OperatorProcessVO, OperatorProcess> taskProcessor;
然后调用其submitTask
方法
//按10个大小切分数据库集合元素
List<List<OperatorProcess>> partition = Lists.partition(operatorProcesses, 10);
List<OperatorProcessVO> vos = new ArrayList<>();
for (List<OperatorProcess> processes : partition) {
//提交任务
vos.addAll(taskExecuteFramework.submitTask(processes, taskProcessor, null));
}