当前位置: 首页 > news >正文

SpringBoot - 定时任务改Cron不重启,调度规则生效

在这里插入图片描述

01 背景

在 Spring Boot 项目开发进程中,定时任务功能至关重要。

虽然 Spring Boot 提供了基于 @Scheduled 注解的便捷定时任务实现方式,但默认设置下,一旦应用启动,定时任务的 Cron 表达式便不可更改。

然而,实际业务场景往往复杂多变,可能需要依据运行状态或用户配置实时调整定时任务的执行频率。

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

02 Spring 定时任务机制概览

Spring Boot 支持三种定时任务实现方式:

1.@Scheduled 注解 :这是最简便的实现方式,直接在方法上添加注解即可。
示例代码如下:

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

注解:
@Scheduled 注解 :用于定义定时任务的执行规则,其中 cron 属性值为 Cron 表达式,用于精确指定任务的执行时间间隔等规则。

@Component 注解:用于标记该类为 Spring 容器中的一个组件,使得 Spring 容器在扫描包时能够自动识别并加载该类。

SchedulingConfigurer 接口 :通过实现该接口,可进行更为灵活的配置,获取 TaskScheduler 和 ScheduledTaskRegistrar,从而实现定时任务的动态管理。

TaskScheduler 接口 :这是最底层的 API,能提供最大程度的灵活性,方便开发人员依据实际需求进行任务调度的细粒度控制。

不过,传统的 @Scheduled 注解使用简便,却存在明显局限性,例如不支持动态修改 Cron 表达式等功能,这使得它并不适合需要动态调整的场景,这也正是我们深入研究动态定时任务实现方案的原因所在。

03 动态定时任务的高阶实现方案

在众多动态定时任务实现方案中,基于 SchedulingConfigurer 接口实现动态定时任务是一种常见且有效的选择。

以下是详细实现步骤:

动态定时任务配置类

@Configuration
@EnableScheduling
public class DynamicScheduleConfig implements SchedulingConfigurer {@Autowiredprivate CronRepository cronRepository;@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.setScheduler(taskScheduler());String initialCron = cronRepository.getCronByTaskName("sampleTask");taskRegistrar.addTriggerTask(() -> {System.out.println("动态定时任务执行,时间:" + new Date());},triggerContext -> {String cron = cronRepository.getCronByTaskName("sampleTask");CronTrigger trigger = new CronTrigger(cron);return trigger.nextExecution(triggerContext);});}@Beanpublic TaskScheduler taskScheduler() {ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();scheduler.setPoolSize(10);scheduler.setThreadNamePrefix("task-");scheduler.initialize();return scheduler;}
}

注解 :

  1. @Configuration 注解 :表明该类是一个配置类,相当于传统 Spring 配置文件中的 标签,用于定义 Spring 容器中的各种 Bean 组件以及相关的配置信息。
  2. @EnableScheduling 注解 :开启定时任务功能,这是使用 Spring 定时任务所必需的注解,用于激活被 @Scheduled 注解标记的方法,使得 Spring 容器能够识别并按照预定规则执行相应的定时任务。
  3. configureTasks 方法 :实现了 SchedulingConfigurer 接口中的 configureTasks 方法,用于具体配置定时任务的相关参数和规则。
  4. taskScheduler 方法 :创建并配置了一个线程池任务调度器,通过设置线程池大小和线程名称前缀,能够有效提升任务调度的性能和可维护性,同时将其作为一个 Bean 注入到 Spring 容器中,供其他组件使用。

Cron 表达式存储与管理类

@Component
public class CronRepository {private final Map<String, String> cronMap = new ConcurrentHashMap<>();@PostConstructpublic void init() {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) {try {new CronTrigger(cron);cronMap.put(taskName, cron);} catch (IllegalArgumentException e) {throw new IllegalArgumentException("Invalid cron expression: " + cron, e);}}
}

注解 :

  1. @Component 注解 :将该类标记为 Spring 容器中的一个组件,方便 Spring 容器在扫描包时自动识别并加载该类,从而使得该类的相关功能能够被其他组件所使用。
  2. @PostConstruct 注解 :标记在 init 方法上,表示该方法会在 Spring 容器加载完该 Bean 之后立即执行,用于进行一些初始化操作,如在本例中初始化 Cron 表达式的映射关系。
  3. getCronByTaskName 方法 :根据任务名称获取对应的 Cron 表达式,若未找到对应的任务,则返回默认的 Cron 表达式,确保任务能够正常执行。
  4. updateCron 方法 :用于更新指定任务的 Cron 表达式,但在更新之前会先验证 Cron 表达式的合法性,防止因非法的 Cron 表达式导致任务调度出现异常。

定时任务控制类

@RestController
@RequestMapping("/scheduler")
public class SchedulerController {@Autowiredprivate 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;}
}

注解 :

  1. @RestController 注解 :表明该类是一个控制器组件,并且其返回的数据将以 JSON 格式直接返回给客户端,适用于构建 RESTful 风格的 web 应用程序接口。
  2. @RequestMapping 注解 :用于定义该控制器的公共请求路径前缀,所有该控制器中的处理方法都将映射到以 “/scheduler” 开头的请求路径上,方便进行统一的 URL 路由管理。
  3. getCron 方法 :用于处理获取指定任务的 Cron 表达式的 GET 请求,通过从 CronRepository 中查询并返回相应的结果,以 JSON 格式响应给客户端。
  4. updateCron 方法 :用于处理更新指定任务的 Cron 表达式的 PUT 请求,调用 CronRepository 的 updateCron 方法更新 Cron 表达式,并将更新结果以 JSON 格式返回给客户端,方便前端页面进行相应的展示和操作提示。

另外,基于 TaskScheduler 接口实现的高级任务调度管理工具类 AdvancedTaskScheduler 也可以为我们提供更多的定时任务操作功能,以下是其核心代码片段及分析:

@Component
public class AdvancedTaskScheduler {@Autowiredprivate TaskScheduler taskScheduler;@Autowiredprivate CronRepository cronRepository;private final Map<String, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>();private static class ScheduledTask {private String taskName;private String cron;private Runnable runnable;private ScheduledFuture<?> future;private boolean running;// 省略构造方法、getter 和 setter 方法}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 -> {String currentCron = cronRepository.getCronByTaskName(taskName);return new CronTrigger(currentCron).nextExecution(triggerContext);});scheduledTasks.put(taskName, new ScheduledTask(taskName, cron, runnable, future, true));}public void updateTaskCron(String taskName, String 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);}}// 省略其他方法
}

注解 :

  1. @Component 注解 :将该类标记为 Spring 容器中的一个组件,使得 Spring 容器能够自动识别并加载该类,从而为应用提供高级任务调度管理功能。
  2. registerTask 方法 :用于注册一个新的定时任务,通过从 CronRepository 中获取对应的 Cron 表达式,并结合 TaskScheduler 进行任务调度,同时将任务的相关信息存储到 scheduledTasks 容器中,方便后续对任务进行管理和操作。
  3. updateTaskCron 方法 :用于更新已注册任务的 Cron 表达式,先调用 CronRepository 的 updateCron 方法更新 Cron 表达式存储,然后取消之前的任务调度,依据新的 Cron 表达式重新调度任务,并更新任务的相关信息,确保任务能够按照新的规则执行。
    04 基于数据库存储 Cron 表达式
    在生产环境中,为了确保定时任务的配置能够持久化并方便团队协作管理,将 CronRepository 修改为使用数据库存储是一种常见的优化方案。

以下是基于数据库存储的 CronRepository 实现类:

@Component
public class DatabaseCronRepository implements CronRepository {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic 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 * * * ?";}}@Overridepublic void updateCron(String taskName, String 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);}}
}

注解 :

1.@Component 注解 :将该类标记为 Spring 容器中的一个组件,使得 Spring 容器在扫描包时能够自动识别并加载该类,替换掉之前基于内存存储的 CronRepository 实现,为应用提供基于数据库存储的 Cron 表达式管理功能。
2.getCronByTaskName 方法 :通过 JdbcTemplate 执行 SQL 查询语句,从数据库中获取指定任务的 Cron 表达式。
若查询结果为空,则返回默认的 Cron 表达式,确保任务能够正常执行。
3.updateCron 方法 :先验证 Cron 表达式的合法性,然后通过 JdbcTemplate 执行 SQL 更新语句,更新数据库中对应任务的 Cron 表达式。
若更新操作影响的行数为 0,则执行插入操作,将新的任务及其 Cron 表达式插入到数据库中,从而实现 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
);

在一些相对简单或服务器资源有限的业务场景中,基于本文所介绍的动态定时任务实现方案,

我们可以通过简单的扩展和定制,即可满足项目中对定时任务动态调整的需求,

无需额外引入其他复杂的定时任务组件,从而有效降低项目的开发成本和维护难度。

然而,对于一些复杂度较高、对定时任务的可靠性、容错性、分布式调度等有更高要求的大型项目,仅仅依靠 Spring Boot 自身的定时任务功能可能难以满足其业务需求。

在这种情况下,可以考虑引入如 xxl-job、ElasticJob 等专业的任务调度组件。

http://www.xdnf.cn/news/1076311.html

相关文章:

  • RuoYi-Vue前后端分离版实现前后端合并
  • 用Fiddler中文版抓包工具掌控微服务架构中的接口调试:联合Postman与Charles的高效实践
  • docker desktop部署本地gitlab服务
  • 学习昇腾开发的第12天--安装第三方依赖
  • 基于springboot的养老院管理系统
  • LINUX2.6设备注册与GPIO相关的API
  • Vue3 中 Excel 导出的性能优化与实战指南
  • JavaScript 安装使用教程
  • ip网络基础
  • FastGPT与MCP:解锁AI新时代的技术密码
  • 百度轮岗:任命新CFO,崔珊珊退居业务二线
  • 使用Electron开发跨平台RSS阅读器:从零到一的完整指南
  • Linux查看空间大小相关命令内容
  • 数据结构复习4
  • 前端计算机视觉:使用 OpenCV.js 在浏览器中实现图像处理
  • Oracle 常用函数
  • 38.docker启动python解释器,pycharm通过SSH服务直连
  • 【软考高项论文】论信息系统项目的进度管理
  • Zookeeper安装使用教程
  • SQL规范
  • IDEA相关配置记录
  • 【中文核心期刊推荐】《计算机应用与软件》
  • Windows CMD命令分类大全
  • 前端开发面试题总结-原生小程序部分
  • 衡石科技使用手册-企业即时通讯工具数据问答机器人用户手册
  • STM32要学到什么程度才算合格?
  • 华为云Flexus+DeepSeek征文|基于 Dify-LLM 构建网站智能客服助手的实践探索
  • Go语言安装使用教程
  • C++ 快速回顾(五)
  • Python 数据分析与机器学习入门 (二):NumPy 核心教程,玩转多维数组