ThreadPoolTaskScheduler构建业务框架实现定时任务
利用ThreadPoolTaskScheduler构建业务框架实现定时任务
在日常办公中,我们总是会有业务需求去使用到定时任务,通常情况下会将触发的模块独立出来,行程一个独立的任务进行管理。在以前的项目中,涉及到定时任务的模块,我们通常都是用spring提供的@Scheduled来实现。那么如果我们不使用注解应该如何实现呢?日常研究同事的优秀代码,昏沉的下午,不如花点时间来总结一下现在业务对这一块的实现,也是对自己工作状态的一个调整。
ThreadPoolTaskScheduler 类的介绍
ThreadPoolTaskScheduler是存在于org.springframework.scheduling.concurrent包下,是spring TaskScheduler 接口的一个实现。该类提供了大量的重载方法进行任务调度。
定时任务主要用到了public ScheduledFuture<?> schedule(Runnable task, Trigger trigger)方法
当trigger对应的时间,触发提交的Runnable的run方法。如果出现异常将会停止本次执行,并将执行结构提交给ScheduledFuture
CronTrigger 类的介绍
CronTrigger类位于org.springframework.scheduling.support包下,是Trigger接口的实现,调度规则给予Crone表达式,能够提供比 SimpleTrigger 更有具体实际意义的调度方案。CronTrigger 支持日历相关的重复时间间隔(比如每月第一个周一执行),而不是简单的周期时间间隔。
首先自定义了一个定时任务的模型 ScheduledTask
定义了三个参数:
volatile ScheduledFuture<?> future; //存放执行后的结果
private String cron; //存放定时任务
private Runnable runnable; //要执行的业务逻辑存放
定义一个抽象的任务 AbstractTask.class
当业务中,很多定时任务有共同的特性时,可以利用模版设备模式,定义好模板,在各自的实现中,去实现具体的逻辑。
在抽象类中定义了如下公共类
1.用于模版扭转的run方法
public void run() {
long start = System.currentTimeMillis();
Logger logger = getLogger();
String taskName = taskName();
logger.info("{}:开始执行", taskName);
handleTask();
long end = System.currentTimeMillis();
logger.info("{}:执行结束,耗时:{}", taskName, (end - start));
}
2.由各个具体实现类依据自己的业务逻辑来完成实现
public abstract void handleTask();
3.统一的数据返回的处理接口
4.获取任务名称的方法
public String taskName() {
return this.getClass().getSimpleName();
}
5.获取Cron 触发表达式的接口,由各个具体实现类实现
public abstract String getCron();
依据业务逻辑 继承AbstractTask.class
当然也可以依据类型的需要,再细化一下模版,比如以处理条目分类,分为单条操作抽象类 , 批量分页操作抽象类等。
可以从一下角度来设计
- 获取定时任务触发的crone表达式
- 获取到查询条件
- 执行条件的过程接口
- 获取到结果以后的统一处理
注意:需要将实现类 注意点spring容器中,在类上添加@Component
自定义一个初始化定时任务 ScheduledTaskHandler.class
该类主要负责将AbstractTask的执行进行初始化,将其中的run方法转换为Runnable的实现,将定时任务配置转换为CronTrigger的实现, 通过提前内置好的 ThreadPoolTaskScheduler schedulerPool 调用其 public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) 方法,实现定时任务工能的执行。
核心逻辑(伪代码):
//一个提前内置好的 schedulerPool
@Autowired
private ThreadPoolTaskScheduler schedulerPool;
.....
{
.....
//获取到spring容器中所有注入的AbstractTask.class子类的bean
Map<String, AbstractTask> abstractTaskMap = applicationContext.getBeansOfType(AbstractTask.class);
//循环处理
for (AbstractTask task : abstractTasks) {
String taskName = task.taskName();
int taskIndex = INDEX.getAndIncrement();
addTask(taskName, new ScheduledTask(task.getCron(), () -> {task.run()}), task.ifAddTaskHandlePool());
}
}
.....
//addTask方法,将其调用schedulerPool.schedule
public void addTask(String taskName, ScheduledTask scheduledTask, boolean ifAddTaskHandlePool) {
if (ifAddTaskHandlePool) {
ScheduledFuture future = schedulerPool.schedule(scheduledTask.getRunnable(), new CronTrigger(scheduledTask.getCron()));
scheduledTask.setFuture(future);
SCHEDULED_TASKS.put(taskName, scheduledTask);
}
}
注:其中有时候为了提交系统的性能,我们也许会通过zk调度,或者通过其他方式,就可以在ScheduledTaskHandler调度的过程中对其进行控制。
提供Controller,增加补偿机制
定时任务如果出现意外,或者临时的需求变化,有时候需要调整执行时间,或者额外执行,就可以对外提供API进行处理,通过上述的方式,我们可以通过AbstractTask类的实现类的bean名称,获取到实例,直接调用该实例实现的handleTask()方法 。
总结
当项目中有很多通用的业务逻辑处理,比如流程、工单或者数据库同步等很多可以抽象出来的东西,尽量抽象出来,日后当有新的相同的实现出现,我们仅需继承,去实现该业务本身的逻辑,而无需重复编写扭转的框架内容。