SpringBoot自带TaskScheduler 接口实现定时任务的动态增、删、启、停。

📴简单的代码实现

完全可以!不依赖 Quartz 或 SnailJob 等第三方框架,仅使用 Spring Boot 自带的 TaskScheduler 接口,就能非常轻量且优雅地实现定时任务的动态增、删、启、停。

核心思路是:利用 TaskScheduler 动态调度任务,它会返回一个 ScheduledFuture<?> 对象。我们将这个对象保存在内存(如 ConcurrentHashMap)中,通过它就可以随时控制任务的暂停(cancel)和恢复(重新 schedule)。

以下是结合你的 iot_task 表结构设计的完整代码实现:

1. 数据库实体类 (Entity)

首先,对应你的 iot_task 表建立实体类。为了实现"启动/停止"功能,建议在表中增加一个 status 字段(例如:1-运行中,0-已停止)。

java 复制代码
public class IotTask {
    private Long id;
    private String taskName;       // 任务名称(作为任务的唯一标识)
    private String cron;           // Cron 表达式
    private String executeAction;  // 执行动作(具体的业务逻辑标识)
    private Integer status;        // 状态:1-启动,0-停止

    // 省略 Getter 和 Setter
}

2. 核心任务管理服务 (Service)

这是实现动态控制的核心。我们通过 TaskScheduler 配合 CronTrigger 来注册任务,并用 Map 缓存任务的执行句柄。

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

@Service
public class DynamicTaskService {

    @Autowired
    private TaskScheduler taskScheduler;

    // 假设你有对应的 Mapper 来操作数据库
    // @Autowired private IotTaskMapper taskMapper; 

    // 内存中缓存正在运行的任务,key为taskName,value为任务的Future句柄
    private final Map<String, ScheduledFuture<?>> taskCache = new ConcurrentHashMap<>();

    /**
     * 动态添加或启动定时任务
     */
    public void startTask(IotTask task) {
        String taskName = task.getTaskName();
        
        // 如果任务已在运行,先停止旧任务,防止重复注册
        if (taskCache.containsKey(taskName)) {
            stopTask(taskName);
        }

        try {
            // 定义任务具体要执行的业务逻辑
            Runnable taskLogic = () -> {
                System.out.println("正在执行 IoT 任务: " + taskName + ", 动作: " + task.getExecuteAction());
                // 这里可以根据 task.getExecuteAction() 调用具体的业务 Service
            };

            // 使用 CronTrigger 按照 cron 表达式调度任务
            ScheduledFuture<?> future = taskScheduler.schedule(taskLogic, new CronTrigger(task.getCron()));
            
            // 将任务句柄存入缓存,方便后续控制
            taskCache.put(taskName, future);
            
            // 同步更新数据库状态为"启动"
            // task.setStatus(1);
            // taskMapper.updateById(task);
            
            System.out.println("任务 [" + taskName + "] 启动成功!");
        } catch (Exception e) {
            System.err.println("任务 [" + taskName + "] 启动失败,请检查 Cron 表达式格式!");
            e.printStackTrace();
        }
    }

    /**
     * 动态停止(暂停)定时任务
     */
    public void stopTask(String taskName) {
        ScheduledFuture<?> future = taskCache.get(taskName);
        if (future != null) {
            // cancel(true) 表示尝试中断正在执行的任务
            future.cancel(true);
            taskCache.remove(taskName);
            
            // 同步更新数据库状态为"停止"
            // taskMapper.updateStatusByName(taskName, 0);
            
            System.out.println("任务 [" + taskName + "] 已停止!");
        }
    }

    /**
     * 动态删除定时任务
     */
    public void deleteTask(String taskName) {
        // 先停止任务
        stopTask(taskName);
        // 然后再从数据库删除对应的记录
        // taskMapper.deleteById(taskName);
        System.out.println("任务 [" + taskName + "] 已删除!");
    }
    
    /**
     * 动态修改任务的 Cron 表达式
     */
    public void updateTaskCron(IotTask task) {
        // 修改 Cron 的本质是:先停止旧任务,再用新的 Cron 启动新任务
        stopTask(task.getTaskName());
        startTask(task);
        // 同步更新数据库中的 cron 字段
        // taskMapper.updateById(task);
    }
}

3. 配置线程池 (Configuration)

为了让动态任务有更好的并发处理能力,建议配置一个专用的线程池。

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class SchedulingConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10); // 根据实际任务量调整线程池大小
        scheduler.setThreadNamePrefix("iot-dynamic-task-");
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.setAwaitTerminationSeconds(60);
        scheduler.initialize();
        return scheduler;
    }
}

4. 系统启动时的任务恢复 (Runner)

为了保证系统重启后,数据库中原本处于"启动"状态的任务能自动恢复运行,需要在 Spring Boot 启动完成后加载这些任务。

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;

@Component
public class TaskInitializationRunner implements CommandLineRunner {

    @Autowired
    private DynamicTaskService dynamicTaskService;
    // @Autowired private IotTaskMapper taskMapper;

    @Override
    public void run(String... args) throws Exception {
        // 1. 从数据库查询所有状态为"启动"(status=1) 的任务
        // List<IotTask> activeTasks = taskMapper.selectByStatus(1);
        // List<IotTask> activeTasks = ...; // 假设这里获取到了数据

        // 2. 遍历并重新注册到调度器中
        // for (IotTask task : activeTasks) {
        //     dynamicTaskService.startTask(task);
        // }
        System.out.println("系统启动完毕,正在恢复数据库中的动态定时任务...");
    }
}

💡 方案总结

  1. 纯原生实现 :完全基于 Spring Boot 自带的 TaskScheduler,无需引入任何沉重的第三方调度框架,代码非常轻量。
  2. 动态控制 :通过 ConcurrentHashMap 存储 ScheduledFuture,完美实现了任务的动态添加、删除、启动和停止
  3. 状态持久化 :结合 iot_task 表和 CommandLineRunner,确保了任务配置和运行状态在系统重启后依然能够保持一致。

📴TaskScheduler使用详解

org.springframework.scheduling.TaskScheduler 是 Spring 框架提供的一个用于任务调度的核心接口。相比于大家熟知的 @Scheduled 静态注解,TaskScheduler 最大的优势在于它支持编程式调度,允许你在程序运行时动态地创建、启动、暂停、停止和修改定时任务,非常适合需要高灵活性和可运维性的企业级应用场景。

下面为你详细解析 TaskScheduler 的核心用法与最佳实践。

🛠️ 核心实现与配置

TaskScheduler 最常用的实现类是 ThreadPoolTaskScheduler。它本质上是对 JDK ScheduledThreadPoolExecutor 的封装,内部维护了一个线程池,具备高并发、低延迟的生产级特性。

在 Spring Boot 中,通常将其配置为一个 Bean 交给 Spring 容器管理:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class TaskSchedulerConfig {

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        // 设置线程池大小(核心线程数)
        scheduler.setPoolSize(10);
        // 设置线程名称前缀,方便日志排查
        scheduler.setThreadNamePrefix("my-task-scheduler-");
        // 设置是否为守护线程
        scheduler.setDaemon(false);
        // 应用关闭时,等待任务执行完毕(最多等待60秒)
        scheduler.setWaitForTasksToCompleteOnShutdown(true);
        scheduler.setAwaitTerminationSeconds(60);
        // 必须调用初始化方法
        scheduler.initialize();
        return scheduler;
    }
}

⚙️ 多种任务调度策略

通过 TaskScheduler,你可以使用以下几种方式来调度 Runnable 任务:

  1. 在指定时间点执行一次

    java 复制代码
    @Autowired
    private TaskScheduler taskScheduler;
    
    public void scheduleAtSpecificTime() {
        // 5秒后执行
        Date executionTime = new Date(System.currentTimeMillis() + 5000);
        taskScheduler.schedule(() -> {
            System.out.println("指定时间任务执行: " + LocalDateTime.now());
        }, executionTime);
    }
  2. 固定速率执行 (Fixed Rate)

    不管上一个任务是否执行完毕,每隔固定的时间就会触发下一次任务。适合周期性检查、心跳检测等场景。

    java 复制代码
    public void startFixedRateTask() {
        // 每2000毫秒(2秒)执行一次
        taskScheduler.scheduleAtFixedRate(() -> {
            System.out.println("固定速率任务执行: " + LocalDateTime.now());
        }, 2000);
    }
  3. 固定延迟执行 (Fixed Delay)

    在上一个任务执行完毕后,等待固定的时间再触发下一次任务。适合前后任务有依赖、或者需要避免任务堆积的场景。

    java 复制代码
    public void startFixedDelayTask() {
        // 任务执行完后,延迟1000毫秒(1秒)再执行下一次
        taskScheduler.scheduleWithFixedDelay(() -> {
            System.out.println("固定延迟任务执行: " + LocalDateTime.now());
        }, 1000);
    }
  4. Cron 表达式触发

    使用标准的 Cron 表达式(如 "0 0/5 * * * ?" 表示每5分钟执行一次),可以实现极其复杂的定时规则

    java 复制代码
    public void startCronTask() {
        // 每10秒执行一次
        CronTrigger trigger = new CronTrigger("0/10 * * * * ?");
        taskScheduler.schedule(() -> {
            System.out.println("Cron任务执行: " + LocalDateTime.now());
        }, trigger);
    }

🎯 动态启停任务(核心亮点)

TaskSchedulerschedule 系列方法都会返回一个 ScheduledFuture<?> 对象。这个对象是任务的生命周期句柄,通过它可以动态地取消(停止)任务或查询任务状态。

以下是一个完整的动态管理任务的示例:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import java.util.concurrent.ScheduledFuture;

@Service
public class DynamicTaskService {

    @Autowired
    private ThreadPoolTaskScheduler taskScheduler;
    
    // 使用 AtomicReference 保证多线程下的安全性
    private final AtomicReference<ScheduledFuture<?>> scheduledFutureRef = new AtomicReference<>();

    // 启动或重启动态任务
    public void startDynamicTask(String cronExpression) {
        stopDynamicTask(); // 先停止之前的任务
        ScheduledFuture<?> future = taskScheduler.schedule(
                () -> System.out.println("动态任务正在执行..."),
                new CronTrigger(cronExpression)
        );
        scheduledFutureRef.set(future);
    }

    // 停止任务
    public void stopDynamicTask() {
        ScheduledFuture<?> future = scheduledFutureRef.getAndSet(null);
        if (future != null && !future.isDone()) {
            future.cancel(true); // true 表示如果任务正在运行,尝试中断它
            System.out.println("动态任务已停止");
        }
    }
}

通过这种机制,你可以轻松结合 REST API 或数据库配置,实现一个无需重启服务即可动态调整定时任务的管控台。

⚠️ 注意事项与最佳实践

  1. 异常处理 :定时任务内部如果抛出未捕获的异常,默认情况下会导致该任务终止且不会自动恢复。建议在 Runnable 任务逻辑中手动添加 try-catch 块来捕获并记录异常。
  2. 分布式环境TaskScheduler 是单机级别的调度。如果在集群环境下部署,会导致每个节点都执行一遍任务。解决分布式重复执行问题,需要引入分布式锁(如 ShedLock + Redis)或专业的分布式调度中间件(如 XXL-JOB、ElasticJob)。
  3. 线程池隔离 :默认情况下 Spring Task 可能使用同一个线程池。对于耗时较长或极其重要的任务,建议配置独立的 TaskScheduler 线程池,避免某个慢任务阻塞其他所有定时任务的执行。
相关推荐
次次皮1 小时前
代理启动前端dist包
java·前端·vue
zmsofts1 小时前
Maven核心能力深度解析:从项目管理到扩展机制
java·python·maven
段ヤシ.1 小时前
回顾Java知识点,面试题汇总Day5(持续更新)
java·开发语言
lifewange1 小时前
中间件细致控制原理 + 可编程实操
中间件
不会C语言的男孩2 小时前
C++ SLTL编程
java·开发语言·c++
java修仙传2 小时前
Java 实习日记:从业务表关系到节点价格分析接口改造
java·开发语言·实习
码农-阿杰2 小时前
Java 线程等待唤醒机制深度解析:synchronized、ReentrantLock、LockSupport 底层实现对比
java·开发语言·c++
数字化顾问2 小时前
(122页PPT)企业数字化IT架构蓝图规划设计方案(附下载方式)
java·运维·架构
不是光头 强2 小时前
Spring Boot 多线程场景下 i18n 国际化失效问题排查与解决
java·开发语言·springboot