泽兴芝士网

一站式 IT 编程学习资源平台

SpringBoot 运行时修改定时任务 Cron 表达式

前言:

在项目开发中,定时任务是一个常见的需求。Spring Boot 通过 @Scheduled 注解提供了简便的定时任务实现方式,但默认情况下,一旦应用启动,定时任务的 Cron 表达式就无法动态调整。

如何在 Spring Boot 应用运行期间动态修改定时任务的 Cron 表达式,实现定时任务的灵活调度。

Spring 定时任务机制概述

Spring 定时任务的实现方式:

@Scheduled 注解:最简单的方式,直接在方法上添加注解 SchedulingConfigurer 接口:通过实现该接口,可以进行更灵活的配置 TaskScheduler 接口:最底层的 API,提供最大的灵活性。

传统的 @Scheduled 注解使用示例:

@Component
public class ScheduledTasks {
    
    @Scheduled(cron = "0 0/5 * * * ?")  // 每5分钟执行一次
    public void executeTask() {
        System.out.println("定时任务执行,时间:" + new Date());
    }
}

@Scheduled 注解的局限性

虽然 @Scheduled 注解使用简便,但它存在明显的局限性:

  1. Cron 表达式在编译时就确定,运行时无法修改
  2. 无法根据条件动态启用或禁用定时任务
  3. 无法在运行时动态添加新的定时任务
  4. 无法获取定时任务的执行状态

动态定时任务的实现方案

基于 SchedulingConfigurer 接口的实现:

这是最常用的方式,通过实现 SchedulingConfigurer 接口,我们可以获取 TaskScheduler 和 ScheduledTaskRegistrar,从而实现定时任务的动态管理。

创建任务配置类

@Configuration
@EnableScheduling
public class DynamicScheduleConfig implements SchedulingConfigurer {
    
    @Autowired
    private CronRepository cronRepository;  // 用于存储和获取Cron表达式
    
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskScheduler());
        
        // 从数据库或配置中心获取初始Cron表达式
        String initialCron = cronRepository.getCronByTaskName("sampleTask");
        
        // 添加可动态修改的定时任务
        taskRegistrar.addTriggerTask(
            // 定时任务的执行逻辑
            () -> {
                System.out.println("动态定时任务执行,时间:" + new Date());
            },
            // 定时任务触发器,可根据需要返回下次执行时间
            triggerContext -> {
                // 每次任务触发时,重新获取Cron表达式
                String cron = cronRepository.getCronByTaskName("sampleTask");
                CronTrigger trigger = new CronTrigger(cron);
                return trigger.nextExecution(triggerContext);
            }
        );
    }
    
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);  // 设置线程池大小
        scheduler.setThreadNamePrefix("task-");
        scheduler.initialize();
        return scheduler;
    }
}

创建存储和管理 Cron 表达式的组件

@Repository
public class CronRepository {
    
    // 这里可以连接数据库或配置中心,本例使用内存存储简化演示
    private final Map<String, String> cronMap = new ConcurrentHashMap<>();
    
    public CronRepository() {
        // 设置初始值
        cronMap.put("sampleTask", "0 0/1 * * * ?");  // 默认每分钟执行一次
    }
    
    public String getCronByTaskName(String taskName) {
        return cronMap.getOrDefault(taskName, "0 0/1 * * * ?");
    }
    
    public void updateCron(String taskName, String cron) {
        // 验证cron表达式的有效性
        try {
            new CronTrigger(cron);
            cronMap.put(taskName, cron);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Invalid cron expression: " + cron, e);
        }
    }
}

创建 REST API 用于修改 Cron 表达式

@RestController
@RequestMapping("/scheduler")
public class SchedulerController {
    
    @Autowired
    private CronRepository cronRepository;
    
    @GetMapping("/cron/{taskName}")
    public Map<String, String> getCron(@PathVariable String taskName) {
        Map<String, String> result = new HashMap<>();
        result.put("taskName", taskName);
        result.put("cron", cronRepository.getCronByTaskName(taskName));
        return result;
    }
    
    @PutMapping("/cron/{taskName}")
    public Map<String, String> updateCron(
            @PathVariable String taskName, 
            @RequestParam String cron) {
        cronRepository.updateCron(taskName, cron);
        
        Map<String, String> result = new HashMap<>();
        result.put("taskName", taskName);
        result.put("cron", cron);
        result.put("message", "Cron expression updated successfully");
        return result;
    }
}

使用 Spring 的 TaskScheduler 直接管理定时任务

@Service
public class AdvancedTaskScheduler {
    
    @Autowired
    private TaskScheduler taskScheduler;
    
    @Autowired
    private CronRepository cronRepository;
    
    // 维护所有调度任务
    private final Map<String, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>();
    
    @Data
    @AllArgsConstructor
    private static class ScheduledTask {
        private String taskName;
        private String cron;
        private Runnable runnable;
        private ScheduledFuture<?> future;
        private boolean running;
    }
    
    /**
     * 注册一个新的定时任务
     */
    public void registerTask(String taskName, Runnable runnable) {
        // 判断任务是否已存在
        if (scheduledTasks.containsKey(taskName)) {
            throw new IllegalArgumentException("Task with name " + taskName + " already exists");
        }
        String cron = cronRepository.getCronByTaskName(taskName);
        ScheduledFuture<?> future = taskScheduler.schedule(
            runnable,
            triggerContext -> {
                // 动态获取最新的cron表达式
                String currentCron = cronRepository.getCronByTaskName(taskName);
                return new CronTrigger(currentCron).nextExecution(triggerContext);
            }
        );
        
        scheduledTasks.put(taskName, new ScheduledTask(taskName, cron, runnable, future, true));
    }
    
    /**
     * 更新任务的Cron表达式
     */
    public void updateTaskCron(String taskName, String cron) {
        // 更新数据库/配置中心中的cron表达式
        cronRepository.updateCron(taskName, cron);
        
        // 重新调度任务
        ScheduledTask task = scheduledTasks.get(taskName);
        if (task != null) {
            // 取消现有任务
            task.getFuture().cancel(false);
            
            // 创建新任务
            ScheduledFuture<?> future = taskScheduler.schedule(
                task.getRunnable(),
                triggerContext -> {
                    return new CronTrigger(cron).nextExecution(triggerContext);
                }
            );
            
            // 更新任务信息
            task.setCron(cron);
            task.setFuture(future);
        }
    }
    
    /**
     * 暂停任务
     */
    public void pauseTask(String taskName) {
        ScheduledTask task = scheduledTasks.get(taskName);
        if (task != null && task.isRunning()) {
            task.getFuture().cancel(false);
            task.setRunning(false);
        }
    }
    
    /**
     * 恢复任务
     */
    public void resumeTask(String taskName) {
        ScheduledTask task = scheduledTasks.get(taskName);
        if (task != null && !task.isRunning()) {
            ScheduledFuture<?> future = taskScheduler.schedule(
                task.getRunnable(),
                triggerContext -> {
                    String currentCron = cronRepository.getCronByTaskName(taskName);
                    return new CronTrigger(currentCron).nextExecution(triggerContext);
                }
            );
            
            task.setFuture(future);
            task.setRunning(true);
        }
    }
    
    /**
     * 删除任务
     */
    public void removeTask(String taskName) {
        ScheduledTask task = scheduledTasks.get(taskName);
        if (task != null) {
            task.getFuture().cancel(false);
            scheduledTasks.remove(taskName);
        }
    }
    
    /**
     * 获取所有任务信息
     */
    public List<Map<String, Object>> getAllTasks() {
        return scheduledTasks.values().stream()
            .map(task -> {
                Map<String, Object> map = new HashMap<>();
                map.put("taskName", task.getTaskName());
                map.put("cron", task.getCron());
                map.put("running", task.isRunning());
                return map;
            })
            .collect(Collectors.toList());
    }
}

使用数据库存储 Cron 表达式

对于生产环境,我们需要确保定时任务的配置能够持久化,将 CronRepository 修改为使用数据库存储:

@Repository
public class DatabaseCronRepository implements CronRepository {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    public String getCronByTaskName(String taskName) {
        try {
            return jdbcTemplate.queryForObject(
                "SELECT cron FROM scheduled_tasks WHERE task_name = ?",
                String.class,
                taskName
            );
        } catch (EmptyResultDataAccessException e) {
            return "0 0/1 * * * ?";  // 默认值
        }
    }
    
    @Override
    public void updateCron(String taskName, String cron) {
        // 验证cron表达式
        try {
            new CronTrigger(cron);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Invalid cron expression: " + cron, e);
        }
        
        // 更新数据库
        int updated = jdbcTemplate.update(
            "UPDATE scheduled_tasks SET cron = ? WHERE task_name = ?",
            cron, taskName
        );
        
        if (updated == 0) {
            // 如果记录不存在,则插入
            jdbcTemplate.update(
                "INSERT INTO scheduled_tasks (task_name, cron) VALUES (?, ?)",
                taskName, cron
            );
        }
    }
}

表结构:

CREATE TABLE scheduled_tasks (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    task_name VARCHAR(100) NOT NULL UNIQUE,
    cron VARCHAR(100) NOT NULL,
    description VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

总结

在一些相对简单的业务或项目或服务器资源有限的情况下,可以基于本文思路进行简单扩展,即可实现定时任务管理,无需额外引入其他定时任务组件。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言