spring schedule配置多任务动态cron(增删启停)

2025-05-29 0 84

一、背景

之前公司经常会遇到配置定时任务,简单的任务可以直接依赖spring
简单任务直接使用 @scheduled 注解配合@EnableScheduling。
但是如何实现简单的动态cron呢?

开发原则:
尽可能在项目本身去实现,少依赖第三方框架,避免项目过于臃肿和复杂。

俩种任务调度方式:

spring schedule配置多任务动态cron(增删启停)

二、本篇说明

springBoot 基础模块 spring-boot-starter-web 已经内置 schedule ,无需引入额外依赖。
先思考几个问题:

1、动态 cron 实现的原理

任务的 【 停止】是基于 future接口 的cancel() 方法。
任务的 【增加、删除、启动】是基于 注册到 类ScheduledTaskRegistrar 的 ScheduledFuture的数量。
涉及核心类:

2、多任务并行执行配置
spring默认机制对schedule是单线程,需要配置多线程并行执行。

3、如何配置多个任务
好多博文,都是配置一个cron,这让初学者很难受。

4、如何配置任务分组
根据自己业务背景,可根据步骤三,进行改造。

5、如何配置服务启动自启任务。
想要程序启动时首次去加我们设置的task,只需实现 CommandLineRunner 即可。

6、如何从数据库读取配置
这个其实很简单,在实现 ScheduledTaskRegistrar 时,先直接查询我们需要的数据即可。

7、如何优雅的实现我们的代码
这里为了我们多个task实现时,去除臃肿的if else ,使用策略模式去实现我们的task,这里代码里面会具体介绍。

参考类图:

spring schedule配置多任务动态cron(增删启停)

8、如何去触发我们的schedule 【增删启停】
配置好 task任务类,注入到 controller ,通过接口直接调用即可。

三、代码实现

先贴出我的github 代码,下面代码描述不全。

1. 普通多任务动态cron 实现

1.1 对应数据库的实体类 TaskEntity

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class TaskEntity {
  5. /**
  6. * 任务id
  7. */
  8. private int taskId;
  9. /**
  10. * 任务说明
  11. */
  12. private String desc;
  13. /**
  14. * cron 表达式
  15. */
  16. private String expression;
  17. }

1.2 配置每个任务实现

配置任务接口 TaskService

  1. public interface TaskService {
  2. void HandlerJob();
  3. Integer jobId();
  4. }

配置任务接口实现 TaskServiceJob1Impl、TaskServiceJob2Impl …

  1. @Service
  2. public class TaskServiceJob1Impl implements TaskService {
  3. @Override
  4. public void HandlerJob() {
  5. System.out.println("——job1 开始执行———:"+new Date());
  6. System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " " + Thread.currentThread().getName() + " 任务一启动");
  7. try {
  8. Thread.sleep(10000);//任务耗时10秒
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " " + Thread.currentThread().getName() + " 结束");
  13. }
  14. @Override
  15. public Integer jobId() {
  16. return 1;
  17. }
  18. }

1.3 配置任务解析器 TaskSolverChooser

注:
这里引入策略模式
为啥要配置 任务解析器选择器:
因为我们实现多个任务时,一个任务对应一个 CronTask,需要在 MyScheduledTask 里面去实现我们每一个方法。
譬如,我们有100个任务就要自定义100个任务实现方法,代码会很臃肿,明显不符合,【开闭原则】,于是这里采用策略模式,解耦我们多个任务业务实现逻辑。

  1. @Slf4j
  2. @Component
  3. public class TaskSolverChooser implements ApplicationContextAware {
  4. private ApplicationContext applicationContext;
  5. private Map<Integer, TaskService> chooseMap = new HashMap<>(16);
  6. /**
  7. * 拿到spring context 上下文
  8. */
  9. @Override
  10. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  11. this.applicationContext = applicationContext;
  12. }
  13. @PostConstruct
  14. private void registerToTaskSolver(){
  15. Map<String, TaskService> taskServiceMap = applicationContext.getBeansOfType(TaskService.class);
  16. for (TaskService value : taskServiceMap.values()) {
  17. chooseMap.put(value.jobId(), value);
  18. log.info("task {} 处理器: {} 注册成功",new Object[]{value.jobId(),value});
  19. }
  20. }
  21. /**
  22. * 获取需要的job
  23. */
  24. public TaskService getTask(Integer jobId){
  25. return chooseMap.get(jobId);
  26. }
  27. }

1.4 配置MyScheduledTask (动态cron核心配置)

说明:
1、配置多线程执行任务
2、配置 刷新 task
3、配置 停止 task
4、配置 执行task 业务逻辑

  1. @Component
  2. public class MyScheduledTask implements SchedulingConfigurer {
  3. private volatile ScheduledTaskRegistrar registrar;
  4. private final ConcurrentHashMap<Integer, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();
  5. private final ConcurrentHashMap<Integer, CronTask> cronTasks = new ConcurrentHashMap<>();
  6. @Autowired
  7. private TaskSolverChooser taskSolverChooser;
  8. @Override
  9. public void configureTasks(ScheduledTaskRegistrar registrar) {
  10. //设置20个线程,默认单线程,如果不设置的话,不能同时并发执行任务
  11. registrar.setScheduler(Executors.newScheduledThreadPool(10));
  12. this.registrar = registrar;
  13. }
  14. /**
  15. * 修改 cron 需要 调用该方法
  16. */
  17. public void refresh(List<TaskEntity> tasks){
  18. //取消已经删除的策略任务
  19. Set<Integer> sids = scheduledFutures.keySet();
  20. for (Integer sid : sids) {
  21. if(!exists(tasks, sid)){
  22. scheduledFutures.get(sid).cancel(false);
  23. }
  24. }
  25. for (TaskEntity TaskEntity : tasks) {
  26. String expression = TaskEntity.getExpression();
  27. //计划任务表达式为空则跳过
  28. if(!StringUtils.hasLength(expression)){
  29. continue;
  30. }
  31. //计划任务已存在并且表达式未发生变化则跳过
  32. if (scheduledFutures.containsKey(TaskEntity.getTaskId())
  33. && cronTasks.get(TaskEntity.getTaskId()).getExpression().equals(expression)) {
  34. continue;
  35. }
  36. //如果策略执行时间发生了变化,则取消当前策略的任务
  37. if(scheduledFutures.containsKey(TaskEntity.getTaskId())){
  38. scheduledFutures.get(TaskEntity.getTaskId()).cancel(false);
  39. scheduledFutures.remove(TaskEntity.getTaskId());
  40. cronTasks.remove(TaskEntity.getTaskId());
  41. }
  42. //业务逻辑处理
  43. CronTask task = cronTask(TaskEntity, expression);
  44. //执行业务
  45. ScheduledFuture<?> future = registrar.getScheduler().schedule(task.getRunnable(), task.getTrigger());
  46. cronTasks.put(TaskEntity.getTaskId(), task);
  47. scheduledFutures.put(TaskEntity.getTaskId(), future);
  48. }
  49. }
  50. /**
  51. * 停止 cron 运行
  52. */
  53. public void stop(List<TaskEntity> tasks){
  54. tasks.forEach(item->{
  55. if (scheduledFutures.containsKey(item.getTaskId())) {
  56. // mayInterruptIfRunning设成false话,不允许在线程运行时中断,设成true的话就允许。
  57. scheduledFutures.get(item.getTaskId()).cancel(false);
  58. scheduledFutures.remove(item.getTaskId());
  59. }
  60. });
  61. }
  62. /**
  63. * 业务逻辑处理
  64. */
  65. public CronTask cronTask(TaskEntity TaskEntity, String expression) {
  66. return new CronTask(() -> {
  67. //每个计划任务实际需要执行的具体业务逻辑
  68. //采用策略,模式 ,执行我们的job
  69. taskSolverChooser.getTask(TaskEntity.getTaskId()).HandlerJob();
  70. }, expression);
  71. }
  72. private boolean exists(List<TaskEntity> tasks, Integer tid){
  73. for(TaskEntity TaskEntity:tasks){
  74. if(TaskEntity.getTaskId() == tid){
  75. return true;
  76. }
  77. }
  78. return false;
  79. }
  80. @PreDestroy
  81. public void destroy() {
  82. this.registrar.destroy();
  83. }
  84. }

1.5 配置程序启动时首次去加我们设置的task

  1. @Component
  2. public class StartInitTask implements CommandLineRunner {
  3. @Autowired
  4. private MyScheduledTask myScheduledTask;
  5. @Override
  6. public void run(String args) throws Exception {
  7. List<TaskEntity> list = Arrays.asList(
  8. new TaskEntity(1, "测试1", "0/1 * * * * ?"),
  9. new TaskEntity(2, "测试2", "0/1 * * * * ?")
  10. );
  11. myScheduledTask.refresh(list);
  12. }
  13. }

1.6 配置web接口去触发,增删启停

  1. @RestController
  2. public class StartController {
  3. @Autowired
  4. private MyScheduledTask scheduledTask;
  5. @PostMapping(value = "/startOrChangeCron")
  6. public String changeCron(@RequestBody List<TaskEntity> list){
  7. if (CollectionUtils.isEmpty(list)) {
  8. // 这里模拟存在数据库的数据
  9. list = Arrays.asList(
  10. new TaskEntity(1, "测试1","0/1 * * * * ?") ,
  11. new TaskEntity(2, "测试2","0/1 * * * * ?")
  12. );
  13. }
  14. scheduledTask.refresh(list);
  15. return "task任务:" + list.toString() + "已经开始运行";
  16. }
  17. @PostMapping(value = "/stopCron")
  18. public String stopCron(@RequestBody List<TaskEntity> list){
  19. if (CollectionUtils.isEmpty(list)) {
  20. // 这里模拟将要停止的cron可通过前端传来
  21. list = Arrays.asList(
  22. new TaskEntity(1, "测试1","0/1 * * * * ?") ,
  23. new TaskEntity(2, "测试2","0/1 * * * * ?")
  24. );
  25. }
  26. scheduledTask.stop(list);
  27. List<Integer> collect = list.stream().map(TaskEntity::getTaskId).collect(Collectors.toList());
  28. return "task任务:" + collect.toString() + "已经停止启动";
  29. }
  30. }

2. 分组多任务动态cron 实现

实现原理:
基于反射实现,根据方法全类名,去动态执行方法。多任务分组配置,根据任务类型进行分组。
eg:
定时任务人员的相关操作,有检测人员离职状态,人员业绩达标,人员考勤…等,
作用:
对人员定时任务做一个分类,在同一个类里面去实现不同的task,
比较
《1. 普通多任务动态cron 实现》,是一个类可以实现一个task
《2. 分组多任务动态cron 实现》,是一个类可以实现多个task
详细可参考: 分组多任务动态cron

3 测试记录

测试1 项目启动自启
TaskServiceJob1Impl和TaskServiceJob1Impl … 设置 阻塞10s
观察日志时间可发现,已经同时并发执行俩个任务。

spring schedule配置多任务动态cron(增删启停)

测试2 触发 刷新【增、删、启】我们的task,。
其实这里没这么智能,如果需要触发刷新接口,实际上是重新加载我们的task,就是对应触发我们,增加任务任务,删除任务,启动任务。
使用idea插件测试接口

spring schedule配置多任务动态cron(增删启停)

观察日志

spring schedule配置多任务动态cron(增删启停)

测试3 触发 停止接口,停止一个接口。
这里测试略过…

四、总结

其实实现简单的动态配置,以上代码可用,比较简单。

到此这篇关于spring schedule配置多任务动态cron(增删启停)的文章就介绍到这了,更多相关spring schedule 多任务动态cron内容请搜索快网idc以前的文章或继续浏览下面的相关文章希望大家以后多多支持快网idc!

原文链接:https://blog.csdn.net/qq_38345773/article/details/114764525

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 spring schedule配置多任务动态cron(增删启停) https://www.kuaiidc.com/108431.html

相关文章

发表评论
暂无评论