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

最近在做一个项目,需要用到动态定时任务,现在比较普遍的做法是集成第三方框架(例如Quartz、XXL-JOB),我自己在做这个项目的时候也考虑过去集成Quartz实现,但是基于项目本身的复杂度和使用场景放弃了

本文主要分享在不依赖过多的其他框架,使用springBoot自身带有的定时任务框架来实现动态定时任务

注解实现定时任务

具体实现

主要基于@EnableScheduling@Scheduled注解

  • 主启动类上加上 @EnableScheduling 注解
  • 写一个类,注入到容器中,在方法上加上 @Scheduled 注解
java 复制代码
@Slf4j
@Component
public class TimeTask {
    
    @Scheduled(cron = "0 0/31 * * * ?")
    public void refresh(){
        log.info("//// 定时刷新");
        
      //业务代码
    }

}

这样就实现了定时任务,是不是很简单?不难发现,这种方式定时执行时间是固定的,但是大部分业务的定时执行时间是经常在变化的,这时候我们就需要通过动态定时任务实现

实现动态定时任务

Spring实现动态定时任务的核心就是其提供的任务调度类ThreadPoolTaskSchedulerThreadPoolTaskScheduler基于线程池来执行任务,可以按照固定的时间间隔或者指定的Cron表达式来调度任务的执行。

在我的项目中主要用到了ScheduledFuture<?> schedule(Runnable task, Trigger trigger)方法,指定的Cron表达式来调度任务的执行

具体实现

  • 创建ThreadPoolTaskScheduler配置类
java 复制代码
@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中移除

java 复制代码
public final class ScheduledTask {

    public volatile ScheduledFuture<?> future;

    /**
     * 取消定时任务
     */
    public void cancel() {
        ScheduledFuture<?> scheduledFuture = this.future;
        if (Objects.nonNull(scheduledFuture)) {
            scheduledFuture.cancel(true);
        }
    }
}
  • 创建CronTaskRegistrar

实现了DisposableBean接口的类,用于注册定时任务。它具有添加、删除和调度定时任务的方法。在销毁时,会取消所有定时任务。

java 复制代码
@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)

  • 定时任务表设计
sql 复制代码
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 '定时任务表';
  • 项目启动时加载所有任务
java 复制代码
@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());
        }

    }
}

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

相关推荐
劲雨波21 分钟前
Spring Boot响应压缩配置与优化
java·spring boot·后端
霍珵璁1 小时前
Lua语言的嵌入式安全
开发语言·后端·golang
老马啸西风5 小时前
Occlum 是一个内存安全的、支持多进程的 library OS,特别适用于 Intel SGX。
网络·后端·算法·阿里云·云原生·中间件·golang
冯浩(grow up)10 小时前
Spring Boot 连接 MySQL 配置参数详解
spring boot·后端·mysql
Asthenia041210 小时前
面试复盘:left join 底层算法(嵌套/哈希/分块) & 主从复制(异步/半同步/同步)
后端
秋野酱11 小时前
基于javaweb的SpringBoot雪具商城系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
计算机-秋大田11 小时前
基于Spring Boot的ONLY在线商城系统设计与实现的设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·课程设计
爱的叹息11 小时前
spring boot + thymeleaf整合完整例子
java·spring boot·后端
Asthenia041212 小时前
MySQL:意向锁与兼容性/MySQL中的锁加在什么上?/innodb中锁的底层是怎么实现的?
后端
程序猿DD_12 小时前
如何用Spring AI构建MCP Client-Server架构
java·人工智能·后端·spring·架构