背景
考虑到系统中使用的XXL-JOB太过重量级,且要部署当独的服务,故找了下轻量级的解决方案,发现Java多线程API中的ThreadPoolTaskScheduler即可实现我的需求,于是动手实践。
具体实现
- 首先,对我们的目标任务进行包装
@Data
public class EbsTaskInfo {
/**
* 目标任务
*/
private final Task task;
private String params;
/**
* 执行结果
*/
@Nullable
volatile ScheduledFuture<?> future;
public EbsTaskInfo(Task task) {
this.task = task;
}
}
- 定一个一个执行器的处理器接口,通一个run方法即可
public interface IEbsJobHandler {
ReturnT<String> run(String params);
}
- 具体业务定时器基于IEbsJobHandler实现其run方法逻辑即可
- 开始定时任务的关键代码
/**
* 关键方法,开始定时任务
*
* @param beanName
* @param cron
* @return
*/
@Override
public boolean scheduleTask(String beanName, String cron, String params) {
IEbsJobHandler target = null;
try {
// 从 spring 容器中获取到执行器的bean
target = (IEbsJobHandler) context.getBean(beanName);
} catch (Throwable e) {
throw new ServiceException("无法获取bean信息, 无法执行任务:" + e.getMessage());
}
Method method = null;
try {
// 通过该bean,反射获取到该bean的run方法,不带参数
method = target.getClass().getMethod("run");
} catch (NoSuchMethodException e) {
throw new ServiceException("无法获取run方法, 无法执行任务:" + e.getMessage());
}
try {
// 构建CronTask,将目标执行器 + cron表达式传入,设置时区为:系统默认时区
CronTask cronTask = new CronTask(createRunnable(target, method),
new CronTrigger(cron, TimeZone.getTimeZone(ZoneId.systemDefault())));
// 构建任务执行器
EbsTaskInfo taskInfo = new EbsTaskInfo(cronTask);
// 将参数保存起来,后面执行正真执行器方法时,需传入
taskInfo.setParams(params);
// 启动执行器
ScheduledFuture<?> future = taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
// 将执行结果异步放入执行任务对象中
taskInfo.setFuture(future);
// 调用取消任务
// log.info("若该任务已存在,则先将已存在的任务先取消");
cancelTask(beanName);
// 以beanName为key,任务对象为value,将其放入taskMap中,核心业务逻辑完成
taskMap.put(beanName, taskInfo);
return true;
} catch (Throwable e) {
log.error("配置异常:{}", e.getMessage());
throw new ServiceException("配置异常" + e.getMessage());
}
}
/**
* 关键方法
*
* @param target
* @param method
* @return
*/
private Runnable createRunnable(Object target, Method method) {
// 通过AopUtils获取到调用的方法对象
Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
// 并将其包装成ScheduledMethodRunnable返回,用于创建定时任务
// ScheduledMethodRunnable 实现了 Runnable run方法
// 其线程run方法内部逻辑实现了通过传入的目标实例 + 目标方法进行执行
return new ScheduledMethodRunnable(target, invocableMethod);
}
- 最好再配置下线程池
@Bean("taskScheduler")
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("sync-pool-");
scheduler.setPoolSize(8);
scheduler.initialize();
return scheduler;
}
- 取消任务
task.getFuture().cancel(true);
- 有了以上知识,核心就实现了,剩下的就是优化
- 考虑实现动态任务创建,需要创建一张数据表来保存任务基本信息和状态
- 服务启动的时候,去读数据库表定时任务配置,并让任务run起来
- 最好在执行器上加一个自定义的注解,好做切面,收集任务执行器的耗时、结果等
- 另外,考虑一个bean可对应多个Task如何改造?
- 还有,如果应用集群部署时,任务如何执行?如何保证任务执行的高可用