最近在做一个项目,需要用到动态定时任务,现在比较普遍的做法是集成第三方框架(例如Quartz、XXL-JOB),我自己在做这个项目的时候也考虑过去集成Quartz实现,但是基于项目本身的复杂度和使用场景放弃了
本文主要分享在不依赖过多的其他框架,使用springBoot自身带有的定时任务框架来实现动态定时任务
注解实现定时任务具体实现主要基于@EnableScheduling和 @Scheduled注解
- 主启动类上加上
@EnableScheduling
注解 - 写一个类,注入到容器中,在方法上加上
@Scheduled
注解
@Slf4j
@Component
public class TimeTask {
@Scheduled(cron = "0 0/31 * * * ?")
public void refresh(){
log.info(" 定时刷新");
//业务代码
}
}
这样就实现了定时任务,是不是很简单?不难发现,这种方式定时执行时间是固定的,但是大部分业务的定时执行时间是经常在变化的,这时候我们就需要通过动态定时任务实现
实现动态定时任务
Spring实现动态定时任务的核心就是其提供的任务调度类ThreadPoolTaskScheduler
,ThreadPoolTaskScheduler
基于线程池来执行任务,可以按照固定的时间间隔或者指定的Cron表达式来调度任务的执行。
在我的项目中主要用到了ScheduledFuture<?> schedule(Runnable task, Trigger trigger)
方法,指定的Cron表达式来调度任务的执行
具体实现
创建ThreadPoolTaskScheduler配置类
@Configuration
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
// 获取系统处理器个数, 作为线程池数量
int corePoolSize = Runtime.getRuntime().availableProcessors();
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
// 定时任务执行线程池核心线程数
taskScheduler.setPoolSize(corePoolSize);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("AntiFraudSchedulerThreadPool-");
return taskScheduler;
}
}
创建ScheduledTask包装类
ScheduledFuture是ScheduledFuture<?> schedule(Runnable task, Trigger trigger)
方法的返回值。
ScheduledFuture继承了Future接口,Future接口提供一组辅助方法,比如:
cancel()
:取消任务isCancelled()
:任务是不是取消了isDone()
:任务是不是已经完成了get()
:用来获取执行结果
当我们调用cancel方法时,会将我们的任务从workQueue中移除
public final class ScheduledTask {
public volatile ScheduledFuture<?> future;
/**
* 取消定时任务
*/
public void cancel() {
ScheduledFuture<?> scheduledFuture = this.future;
if (Objects.nonNull(scheduledFuture)) {
scheduledFuture.cancel(true);
}
}
}
创建CronTaskRegistrar类
实现了DisposableBean接口的类,用于注册定时任务。它具有添加、删除和调度定时任务的方法。在销毁时,会取消所有定时任务。
@Slf4j
@Component
@SuppressWarnings("all")
public class CronTaskRegistrar implements DisposableBean {
@Resource
private TaskScheduler taskScheduler;
// 保存任务Id和定时任务
private final Map<String, ScheduledTask> scheduledTaskMap = new ConcurrentHashMap<>(64);
// 添加任务
public void addTask(Runnable task, String cronExpression,String jobId) {
addTask(new CronTask(task, cronExpression),jobId);
}
public void addTask(CronTask cronTask,String jobId) {
if (Objects.nonNull(cronTask)) {
Runnable task = cronTask.getRunnable();
if (this.scheduledTasks.containsKey(task)) {
removeTask(jobId);
}
// 保存任务Id和定时任务
this.scheduledTaskMap.put(jobId, scheduleTask(cronTask));
}
}
// 通过任务Id,取消定时任务
public void removeTask(String jobId) {
ScheduledTask scheduledTask = this.scheduledTaskMap.remove(jobId);
if (Objects.nonNull(scheduledTask)) {
scheduledTask.cancel();
}
}
public ScheduledTask scheduleTask(CronTask cronTask) {
ScheduledTask scheduledTask = new ScheduledTask();
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
return scheduledTask;
}
// 销毁
@Override
public void destroy() {
this.scheduledTaskMap.values().forEach(ScheduledTask::cancel);
this.scheduledTaskMap.clear();
}
}
动态定时任务的核心逻辑到这基本就已经完成了,具体的Service类代码,这里就不贴出来了,因为里面基本上都是业务逻辑和CronTaskRegistrar类的编排(ps:需要的也可以私聊demo)
定时任务表设计
create table schedule_setting
(
id varchar(32) not null comment '唯一id'
primary key,
job_id varchar(64) null comment '任务ID',
cron_expression varchar(255) null comment 'cron表达式',
job_result varchar(32) null comment '任务结果(通过 复议 拒绝)',
create_date datetime null comment '创建时间',
status varchar(4) null,
create_by varchar(64) null comment '创建人账号',
creator varchar(64) null comment '创建人',
version bigint(19) null comment '版本号'
)
comment '定时任务表';
项目启动时加载所有任务
@Component
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class ScheduleRunner implements CommandLineRunner {
ScheduleSettingRepository scheduleSettingRepository;
CronTaskRegistrar scheduledTaskRegistrar;
@Override
public void run(String... args) throws Exception {
// 查询所有定时任务
List<ScheduleSetting> scheduleSettingList=scheduleSettingRepository.findByStatus(EnableFlag.Y.name());
for (ScheduleSetting scheduleSetting : scheduleSettingList) {
//调用CronTaskRegistrar添加任务方法
scheduledTaskRegistrar.addCronTask(() -> {
···
任务执行方法
...
}, scheduleSetting.getCronExpression(), scheduleSetting.getJobId());
}
}
}
至此,动态定时任务就说完啦。