SpringBoot 基于数据库的动态定时任务管理器实现方案

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 的 TaskSchedulerCronTrigger 实现:

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)

通过 ScheduledFuturecancel() 方法取消任务执行:

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);
}

优势与特点

  1. 动态配置:通过数据库管理任务,无需修改代码和重启应用
  2. 灵活调度:支持 Cron 表达式,可精确控制任务执行时间
  3. 线程安全 :使用 ConcurrentHashMap 存储任务引用,保证并发安全
  4. 优雅关闭:应用关闭时自动清理任务,避免资源泄漏
  5. 易于扩展:通过反射机制可支持更多任务类型

注意事项

  1. Cron 表达式验证:在保存任务前,应验证 Cron 表达式的合法性
  2. 任务执行时间:如果任务执行时间较长,应考虑使用异步执行或线程池
  3. 异常处理:任务执行过程中应做好异常捕获和日志记录
  4. 并发控制:对于同一任务的多次触发,应考虑添加锁机制防止并发执行
  5. 数据库性能:任务数量较多时,应考虑数据库查询性能优化
相关推荐
椰羊~王小美2 小时前
@RequestMapping注解的各个属性作用
java
Yeh2020582 小时前
request与response笔记
java·前端·笔记
程序员老邢2 小时前
【产品底稿 07】商助慧 Admin 运维模块落地:从 “能跑” 到 “能运维”,3 个页面搞定日常排障
java·运维·经验分享·spring boot·后端
元宝骑士3 小时前
Spring @Async 异步无法获取当前登录用户?Sa-Token 1.34.0 终极踩坑解决方案
java·后端
0xDevNull3 小时前
Java项目中Redis热点Key自动检测方案详细教程
java·spring boot·redis
一嘴一个橘子3 小时前
MP 自定义业务方法 (三)
java
一叶飘零_sweeeet3 小时前
AI Agent 深潜:六大核心模块的设计本质与 Java 实现
java·人工智能·agent
向往着的青绿色3 小时前
Java反序列化漏洞(持续更新中)
java·开发语言·计算机网络·安全·web安全·网络安全·网络攻击模型
Carsene4 小时前
第一章:为什么我们需要“类型安全”的 SQL DSL 框架?
java·sql