SpringBoot 基于数据库的动态定时任务管理器实现方案
技术选型
- Spring Boot:应用框架
- TaskScheduler:Spring 内置的任务调度器
- CronTrigger:Cron 表达式触发器
- ConcurrentHashMap:线程安全的任务存储容器
- Hutool:工具类库
核心实现思路
1. 任务实体设计
首先,我们需要设计一个任务实体来存储任务信息:
java
public class SysJobEntity {
private Long id; // 任务ID
private String jobName; // 任务名称
private String cronExpression; // Cron表达式
private String invokeTarget; // 调用目标(反射调用)
private Integer status; // 任务状态(0正常 1暂停)
// 其他字段...
}
2. 任务管理器核心代码
java
@Component
@Slf4j
@RequiredArgsConstructor
public class DatabaseScheduledTaskManager {
private final JobTaskApplicationService jobService;
private final TaskScheduler taskScheduler;
/**
* 存储已调度的任务
*/
private final ConcurrentHashMap<Long, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
/**
* 应用启动时初始化所有启用的定时任务
*/
@PostConstruct
public void initScheduledTasks() {
log.info("开始初始化数据库定时任务");
List<SysJobEntity> jobList = jobService.selectJobList();
if (ObjectUtil.isEmpty(jobList)) {
log.info("没有可用的定时任务");
} else {
jobList.forEach(this::scheduleTask);
}
log.info("数据库定时任务初始化完成,共加载 {} 个任务", scheduledTasks.size());
}
/**
* 应用关闭时取消所有定时任务
*/
@PreDestroy
public void destroyScheduledTasks() {
log.info("开始销毁数据库定时任务");
for (ScheduledFuture<?> future : scheduledTasks.values()) {
future.cancel(true);
}
scheduledTasks.clear();
log.info("数据库定时任务销毁完成");
}
/**
* 调度单个任务
*/
public void scheduleTask(SysJobEntity job) {
// 如果任务已存在,先取消
cancelTask(job.getId());
// 创建任务执行逻辑
Runnable task = () -> {
try {
log.info("执行定时任务: {}[{}]", job.getJobName(), job.getId());
executeJob(job);
} catch (Exception e) {
log.error("执行定时任务失败: {}", job.getJobName(), e);
}
};
// 根据cron表达式调度任务
ScheduledFuture<?> scheduledFuture = taskScheduler.schedule(task,
new CronTrigger(job.getCronExpression()));
// 存储任务引用
scheduledTasks.put(job.getId(), scheduledFuture);
log.info("成功调度任务: {}[{}] - cron: {}", job.getJobName(), job.getId(), job.getCronExpression());
}
/**
* 取消任务
*/
public void cancelTask(Long jobId) {
ScheduledFuture<?> scheduledFuture = scheduledTasks.get(jobId);
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
scheduledTasks.remove(jobId);
log.info("已取消任务: {}", jobId);
}
}
/**
* 重新调度任务(先取消再重新调度)
*/
public void rescheduleTask(SysJobEntity job) {
cancelTask(job.getId());
if (ObjectUtil.isNotEmpty(job) && TaskJobStatusEnum.normal.getValue().equals(job.getStatus())) {
scheduleTask(job);
}
}
/**
* 执行具体的任务逻辑
*/
private void executeJob(SysJobEntity job) {
log.info("执行任务: {},调用目标: {}", job.getJobName(), job.getInvokeTarget());
// 根据任务名称执行不同的业务逻辑
if ("任务A".equals(job.getJobName())) {
// 执行任务A的业务逻辑
doTaskA();
} else if ("任务B".equals(job.getJobName())) {
// 执行任务B的业务逻辑
doTaskB();
}
// 更多任务...
}
}
核心功能解析
1. 任务初始化(@PostConstruct)
应用启动时,通过 @PostConstruct 注解自动执行 initScheduledTasks() 方法,从数据库中加载所有任务并进行调度。
java
@PostConstruct
public void initScheduledTasks() {
List<SysJobEntity> jobList = jobService.selectJobList();
jobList.forEach(this::scheduleTask);
}
2. 任务调度(scheduleTask)
核心调度逻辑,使用 Spring 的 TaskScheduler 和 CronTrigger 实现:
java
public void scheduleTask(SysJobEntity job) {
// 1. 如果任务已存在,先取消旧任务
cancelTask(job.getId());
// 2. 创建任务执行逻辑(Runnable)
Runnable task = () -> {
try {
executeJob(job);
} catch (Exception e) {
log.error("执行定时任务失败: {}", job.getJobName(), e);
}
};
// 3. 根据cron表达式调度任务
ScheduledFuture<?> scheduledFuture = taskScheduler.schedule(task,
new CronTrigger(job.getCronExpression()));
// 4. 存储任务引用,方便后续管理
scheduledTasks.put(job.getId(), scheduledFuture);
}
3. 任务取消(cancelTask)
通过 ScheduledFuture 的 cancel() 方法取消任务执行:
java
public void cancelTask(Long jobId) {
ScheduledFuture<?> scheduledFuture = scheduledTasks.get(jobId);
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
scheduledTasks.remove(jobId);
}
}
4. 任务重新调度(rescheduleTask)
当任务配置发生变化时(如修改了 Cron 表达式),先取消旧任务,再重新调度:
java
public void rescheduleTask(SysJobEntity job) {
cancelTask(job.getId());
if (job.getStatus() == 0) { // 0表示正常状态
scheduleTask(job);
}
}
5. 应用优雅关闭(@PreDestroy)
应用关闭时,取消所有正在执行的任务,避免资源泄漏:
java
@PreDestroy
public void destroyScheduledTasks() {
for (ScheduledFuture<?> future : scheduledTasks.values()) {
future.cancel(true);
}
scheduledTasks.clear();
}
反射调用扩展(可选)
如果希望更灵活地调用任务,可以通过反射机制实现动态方法调用:
java
private Object invokeMethod(String invokeTarget) throws Exception {
// 解析调用目标字符串
// 格式: com.example.service.TestService.testMethod(param1, param2)
int leftParenthesisIndex = invokeTarget.indexOf("(");
int rightParenthesisIndex = invokeTarget.lastIndexOf(")");
// 提取类名和方法名
String classNameAndMethod = invokeTarget.substring(0, leftParenthesisIndex);
int lastDotIndex = classNameAndMethod.lastIndexOf(".");
String className = classNameAndMethod.substring(0, lastDotIndex);
String methodName = classNameAndMethod.substring(lastDotIndex + 1);
// 加载类并获取Spring容器中的Bean实例
Class<?> clazz = Class.forName(className);
Object targetObject = SpringUtil.getBean(clazz);
// 解析参数并调用方法
// ... 参数解析逻辑 ...
Method method = clazz.getMethod(methodName, paramTypes);
return method.invoke(targetObject, params);
}
使用方式
1. 在数据库中新增任务
sql
INSERT INTO sys_job (job_name, cron_expression, invoke_target, status)
VALUES ('删除过期数据', '0 0 2 * * ?', 'jobService.deleteExpiredData()', 0);
2. 动态修改任务
通过 API 接口修改任务的 Cron 表达式或状态,调用 rescheduleTask() 方法重新调度:
java
@PostMapping("/job/update")
public void updateJob(@RequestBody SysJobEntity job) {
jobService.updateById(job);
taskManager.rescheduleTask(job);
}
3. 暂停/恢复任务
java
@PostMapping("/job/pause/{jobId}")
public void pauseJob(@PathVariable Long jobId) {
taskManager.cancelTask(jobId);
}
@PostMapping("/job/resume/{jobId}")
public void resumeJob(@PathVariable Long jobId) {
SysJobEntity job = jobService.getById(jobId);
taskManager.scheduleTask(job);
}
优势与特点
- 动态配置:通过数据库管理任务,无需修改代码和重启应用
- 灵活调度:支持 Cron 表达式,可精确控制任务执行时间
- 线程安全 :使用
ConcurrentHashMap存储任务引用,保证并发安全 - 优雅关闭:应用关闭时自动清理任务,避免资源泄漏
- 易于扩展:通过反射机制可支持更多任务类型
注意事项
- Cron 表达式验证:在保存任务前,应验证 Cron 表达式的合法性
- 任务执行时间:如果任务执行时间较长,应考虑使用异步执行或线程池
- 异常处理:任务执行过程中应做好异常捕获和日志记录
- 并发控制:对于同一任务的多次触发,应考虑添加锁机制防止并发执行
- 数据库性能:任务数量较多时,应考虑数据库查询性能优化