前言:
在项目开发中,定时任务是一个常见的需求。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 注解使用简便,但它存在明显的局限性:
- Cron 表达式在编译时就确定,运行时无法修改
- 无法根据条件动态启用或禁用定时任务
- 无法在运行时动态添加新的定时任务
- 无法获取定时任务的执行状态
动态定时任务的实现方案
基于 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
总结
在一些相对简单的业务或项目或服务器资源有限的情况下,可以基于本文思路进行简单扩展,即可实现定时任务管理,无需额外引入其他定时任务组件。