前言
在开发中,定时任务是一个常见且重要的需求。但是,之前我在开发时碰到数据库连接初始化在Spring Boot提供的 @Scheduled 注解初始化定时任务之后(使用低代码开发),导致定时任务执行报错,虽然当时不影响项目正常启动,但是考虑不可控情况,导致项目无法正常启动。
为了解决这个问题,我开发了这个基于Spring Boot的动态定时任务功能,它不仅解决了初始化顺序的问题,还提供了更灵活的定时任务管理机制。
这个功能适用于单体项目,我不想额外引用其他框架,增加代码体积;
分布式项目可以使用XXL-Job等调度平台;
项目我放到GitHub上了,可自行拉取代码并修改。
基于当前项目可增加拓展功能:
- 结合数据库实现可视化管理定时任务;
- 结合Spring Boot 动态配置文件实现热更新定时任务执行时间,具体实现逻辑参考这个:SpringBoot 动态加载配置文件及刷新Bean - 如.若 - 博客园
maven配置
<dependency>
<groupId>io.github.lin0306</groupId>
<artifactId>dynamic-task</artifactId>
<version>1.0.1</version>
</dependency>
技术背景
传统定时任务的局限性
Spring Boot内置的@Scheduled注解虽然使用简单,但存在以下局限:
- Cron表达式在编译时固定,无法动态修改
- 无法在运行时动态添加或取消任务
- 任务管理不够灵活,难以实现复杂的调度需求
解决方案
为了解决上述问题,我开了一个基于Spring Boot的动态定时任务功能,该框架具有以下特点:
- 通过自定义注解方式快速定义定时任务
- 支持多种Cron表达式格式(标准格式、预定义注解、配置文件引用)
- 提供API实现定时任务的动态管理(添加、取消、清理)
- 自动任务ID生成,简化任务标识
- 基于线程池的任务调度执行
- 应用停止时自动清理任务,实现优雅关闭
框架设计与实现
技术栈
- Java 1.8
- Spring Boot 2.7.18
核心代码
- @DynamicTask注解:用于标记定时任务方法
- DynamicTaskProcessor:负责扫描和注册带有@DynamicTask注解的方法
- DynamicTaskConfig:提供动态管理定时任务的API
实现原理
1. 自定义注解
首先,我们定义了@DynamicTask注解,用于标记需要定时执行的方法:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicTask {
// 任务标识,默认使用类名+#+方法名
String taskId() default "";
// cron表达式
String cron();
}
2. 任务处理器
DynamicTaskProcessor类实现了ApplicationListener接口,在应用启动完成后自动扫描和注册定时任务:
@Slf4j
@Component
public class DynamicTaskProcessor implements ApplicationListener<ApplicationReadyEvent> {
@Resource
private DynamicTaskConfig dynamicTaskConfig;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// 注册定时任务
dynamicTaskConfig.addCronTask(...);
}
/**
* 服务停止时销毁所有定时任务
*/
@PreDestroy
public void destroy() {
DynamicTaskConfig.clearTask();
}
}
3. 任务配置管理
DynamicTaskConfig类负责任务的添加、取消和清理:
@Slf4j
@Component
public class DynamicTaskConfig {
@Resource
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
private static final Map<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
// 添加定时任务
public void addCronTask(String taskId, String cronExpression, Runnable task) {
// ... 实际定时任务逻辑 ...
}
// 取消任务
public boolean cancelTask(String taskId) {
// ... 实际取消任务逻辑 ...
}
// 清理所有任务
public static void clearTask() {
// ... 清理所有任务逻辑 ...
}
}
使用示例
1. 启用定时任务
在application.properties或application.yml中添加配置:
# 启用动态定时任务
dynamic.schedule.enable=true
2. 使用注解定义任务
@Component
public class TaskDemo {
// 使用标准Cron表达式(每分钟执行一次)
@DynamicTask(cron = "0 * * * * *")
public void standardCronTask() {
System.out.println("标准Cron表达式任务执行:" + LocalDateTime.now());
}
// 使用预定义注解(每天执行一次)
@DynamicTask(cron = "@daily")
public void annotationTask() {
System.out.println("预定义注解任务执行:" + LocalDateTime.now());
}
// 从配置文件读取Cron表达式
@DynamicTask(cron = "${task.cron.expression}")
public void configTask() {
System.out.println("配置文件任务执行:" + LocalDateTime.now());
}
// 自定义任务ID
@DynamicTask(taskId = "customTaskId", cron = "0 0 12 * * ?")
public void customIdTask() {
System.out.println("自定义ID任务执行:" + LocalDateTime.now());
}
}
3. 动态管理任务
@Service
public class TaskService {
@Resource
private DynamicTaskConfig dynamicTaskConfig;
// 动态添加任务
public void addDynamicTask() {
dynamicTaskConfig.addCronTask("dynamicTask", "0 */5 * * * *", () -> {
System.out.println("动态添加的任务执行:" + LocalDateTime.now());
});
}
// 取消任务
public void cancelTask(String taskId) {
dynamicTaskConfig.cancelTask(taskId);
}
}
框架特色
1. 多种Cron表达式支持
框架支持以下几种Cron表达式格式:
标准Cron表达式
使用标准的Spring Cron表达式格式,例如:0 0 12 * * ?(每天中午12点执行)
预定义注解
| 注解 | 描述 | Cron表达式 |
|---|---|---|
| @yearly 或 @annually | 每年执行一次 | 0 0 0 1 1 * |
| @monthly | 每月执行一次 | 0 0 0 1 * * |
| @weekly | 每周执行一次 | 0 0 0 * * 0 |
| @daily 或 @midnight | 每天执行一次 | 0 0 0 * * * |
| @hourly | 每小时执行一次 | 0 0 * * * * |
配置文件引用
使用${property.name}格式从配置文件中读取Cron表达式。
禁用任务
使用-符号可以禁用定时任务。
2. 自动任务ID生成
如果未指定taskId,框架会自动生成任务ID,格式为:类名#方法名,例如:TaskDemo#standardCronTask。
3. 优雅关闭
框架在应用停止时会自动清理所有定时任务,避免资源泄漏。
实现细节与优化
1. 任务注册流程
- 应用启动完成后,DynamicTaskProcessor扫描所有带有@DynamicTask注解的方法
- 解析注解中的Cron表达式,支持标准表达式、预定义注解和配置文件引用
- 生成任务ID(用户自定义或自动生成)
- 创建任务执行器,通过反射调用目标方法
- 调用DynamicTaskConfig.addCronTask方法注册任务
2. 任务执行机制
框架使用Spring的ThreadPoolTaskScheduler进行任务调度,支持并发执行多个任务。每个任务的执行结果会被包装在ScheduledFuture中,并存储在scheduledTasks集合中,方便后续管理。
3. 异常处理
框架对任务执行过程中的异常进行了处理,确保一个任务的异常不会影响其他任务的执行。同时,对无效的Cron表达式和配置项进行了验证,提供了友好的错误提示。
应用场景
- 定时数据同步:定期从外部系统拉取数据或推送数据
- 定时报表生成:每天、每周或每月自动生成业务报表
- 缓存定时刷新:定期刷新系统缓存数据
- 定时任务监控:监控系统运行状态,发送告警信息
- 批量处理任务:定时执行批量数据处理任务
总结与展望
本文介绍了一个基于Spring Boot的动态定时任务调度框架,该框架通过自定义注解和配置类,实现了定时任务的动态管理和灵活配置。相比于Spring Boot内置的@Scheduled注解,该框架具有更强的灵活性和可管理性,适用于各种复杂的定时任务场景。