likes
comments
collection
share

玩转SpringBoot动态定时任务(启动、暂停)

作者站长头像
站长
· 阅读数 18

最近在做一个项目,需要用到动态定时任务,现在比较普遍的做法是集成第三方框架(例如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实现动态定时任务的核心就是其提供的任务调度类ThreadPoolTaskSchedulerThreadPoolTaskScheduler基于线程池来执行任务,可以按照固定的时间间隔或者指定的Cron表达式来调度任务的执行。

玩转SpringBoot动态定时任务(启动、暂停)

在我的项目中主要用到了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包装类 ScheduledFutureScheduledFuture<?> 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());
        }

    }
}

至此,动态定时任务就说完啦