IT评测·应用市场-qidao123.com

标题: SpringBoot自定义cron表达式注册定时任务 [打印本页]

作者: 羊蹓狼    时间: 2023-4-21 16:23
标题: SpringBoot自定义cron表达式注册定时任务
springBoot自定义cron表达式注册定时任务

一、原理

  1. public class TestScheduled {
  2.     /**
  3.      * 1、使用Spring自带的TaskScheduler注册任务
  4.      * 2、注册后返回:ScheduledFuture,用于取消定时任务
  5.      */
  6.     @Resource
  7.     private TaskScheduler taskScheduler;
  8.     public void registrarTask() {
  9.         //具体的任务Runnable(一般使用类实现Runnable接口)
  10.         Runnable taskRunnable = new Runnable() {
  11.             @Override
  12.             public void run() {
  13.             }
  14.         };
  15.         //cron表达式触发器
  16.         CronTrigger trigger = new CronTrigger("0/5 * * * * ?");
  17.         //开启定时任务的真正方法
  18.         ScheduledFuture<?> future = this.taskScheduler.schedule(taskRunnable, trigger);
  19.         //取消定时任务
  20.         future.cancel(true);
  21.     }
  22. }
复制代码
二、具体实现

1、配置任务调度器

  1. package com.cc.ssd.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.scheduling.TaskScheduler;
  5. import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
  6. /** TaskScheduler任务调度器配置类
  7. * @since 2023/4/21 0021
  8. * @author CC
  9. **/
  10. @Configuration
  11. public class CronTaskConfig {
  12.     /**
  13.      * 任务调度器自定义配置
  14.      */
  15.     @Bean(name = "taskScheduler")
  16.     public TaskScheduler taskScheduler() {
  17.         // 任务调度线程池
  18.         ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
  19.         // 定时任务执行线程池核心线程数:可同时执行4个任务
  20.         taskScheduler.setPoolSize(4);
  21.         taskScheduler.setRemoveOnCancelPolicy(true);
  22.         // 线程名称前缀
  23.         taskScheduler.setThreadNamePrefix("Cs-ThreadPool-");
  24.         return taskScheduler;
  25.     }
  26. }
复制代码
2、定时任务注册类

  1. package com.cc.ssd.registrar;
  2. import com.cc.ssd.task.CronTaskFuture;
  3. import com.cc.ssd.task.CronTaskRunnable;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.beans.BeanUtils;
  7. import org.springframework.beans.factory.DisposableBean;
  8. import org.springframework.scheduling.TaskScheduler;
  9. import org.springframework.scheduling.config.CronTask;
  10. import org.springframework.scheduling.support.CronExpression;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.util.Assert;
  13. import javax.annotation.Resource;
  14. import java.time.LocalDateTime;
  15. import java.time.format.DateTimeFormatter;
  16. import java.util.*;
  17. import java.util.concurrent.ConcurrentHashMap;
  18. import java.util.stream.Collectors;
  19. /** 注册定时任务:缓存定时任务、注册定时任务到调度中心
  20. * @author CC
  21. **/
  22. @Component
  23. public class CronTaskRegistrar implements DisposableBean {
  24.     private static final Logger log = LoggerFactory.getLogger(CronTaskRegistrar.class);
  25.     /**
  26.      * 缓存任务
  27.      * key:具体的任务
  28.      * value:注册定时任务后返回的ScheduledFuture
  29.      */
  30.     private final Map<Runnable, CronTaskFuture> scheduledTasks = new ConcurrentHashMap<>(16);
  31.     /**
  32.      * 使用自定义的任务调度配置
  33.      */
  34.     @Resource(name = "taskScheduler")
  35.     private TaskScheduler taskScheduler;
  36.     /** 获取任务调度配置
  37.      * @return 任务调度配置
  38.      */
  39.     public TaskScheduler getTaskScheduler() {
  40.         return this.taskScheduler;
  41.     }
  42.     /** 新增定时任务1
  43.      *  存在任务:删除此任务,重新新增这个任务
  44.      * @param taskRunnable 执行的具体任务定义:taskRunnable 实现Runnable
  45.      * @param cronExpression cron表达式
  46.      */
  47.     public void addCronTask(Runnable taskRunnable, String cronExpression) {
  48.         //验证cron表达式是否正确
  49.         boolean validExpression = CronExpression.isValidExpression(cronExpression);
  50.         if (!validExpression) {
  51.             throw new RuntimeException("cron表达式验证失败!");
  52.         }
  53.         //获取下次执行时间
  54.         CronExpression parse = CronExpression.parse(cronExpression);
  55.         LocalDateTime next = parse.next(LocalDateTime.now());
  56.         if (Objects.nonNull(next)) {
  57.             //定时任务下次执行的时间
  58.             String format = next.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
  59.             log.info("定时任务下次执行的时间:{}", format);
  60.         }
  61.         //封装成 CronTask(cron任务)
  62.         CronTask cronTask = new CronTask(taskRunnable, cronExpression);
  63.         this.addCronTask(cronTask);
  64.     }
  65.     /** 新增定时任务2
  66.      * @param cronTask :<p>CronTask用于在指定时间间隔内执行定时任务。</p>
  67.      *                   <p>它是通过CronTrigger来实现的,CronTrigger是一个基于cron表达式的触发器,</p>
  68.      *                   <p>可以在指定的时间间隔内触发任务执行。</p>
  69.      * @since 2023/4/21 0021
  70.      * @author CC
  71.      **/
  72.     private void addCronTask(CronTask cronTask) {
  73.         if (Objects.nonNull(cronTask)) {
  74.             //1有这个任务,先删除这个任务。再新增
  75.             Runnable task = cronTask.getRunnable();
  76.             String taskId = null;
  77.             if (task instanceof CronTaskRunnable) {
  78.                 taskId = ((CronTaskRunnable) task).getTaskId();
  79.             }
  80.             //通过任务id获取缓存的任务,如果包含则删除,然后新增任务
  81.             Runnable taskCache = this.getTaskByTaskId(taskId);
  82.             if (Objects.nonNull(taskCache) && this.scheduledTasks.containsKey(taskCache)) {
  83.                 this.removeCronTaskByTaskId(taskId);
  84.             }
  85.             //2注册定时任务到调度中心
  86.             CronTaskFuture scheduledFutureTask = this.scheduleCronTask(cronTask);
  87.             //3缓存定时任务
  88.             this.scheduledTasks.put(task, scheduledFutureTask);
  89.             //todo cc 4可以将任务保存到数据库中……重新启动程序然后加载数据库中的任务到缓存中……
  90.         }
  91.     }
  92.     /** 注册 ScheduledTask 定时任务
  93.      * @param cronTask cronTask
  94.      * @return 注册定时任务后返回的 ScheduledFutureTask
  95.      */
  96.     private CronTaskFuture scheduleCronTask(CronTask cronTask) {
  97.         //注册定时任务后记录的Future
  98.         CronTaskFuture scheduledTask = new CronTaskFuture();
  99.         //开启定时任务的真正方法
  100.         scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
  101. //        scheduledTask.setThreadLocal(this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()));
  102.         return scheduledTask;
  103.     }
  104.     /** 获取任务列表
  105.      * @return
  106.      */
  107.     public List<CronTaskRunnable> getScheduledTasks() {
  108.         List<CronTaskRunnable> tasks = new ArrayList<>();
  109.         Set<Runnable> keySet = scheduledTasks.keySet();
  110.         keySet.forEach(key -> {
  111.             CronTaskRunnable task = new CronTaskRunnable();
  112.             if (key instanceof CronTaskRunnable) {
  113.                 CronTaskRunnable taskParent = (CronTaskRunnable) key;
  114.                 BeanUtils.copyProperties(taskParent, task);
  115.             }
  116.             tasks.add(task);
  117.         });
  118.         return tasks.stream()
  119.                 .sorted(Comparator.comparing(CronTaskRunnable::getTaskId))
  120.                 .collect(Collectors.toList());
  121.     }
  122.     /** 根据任务id删除单个定时任务
  123.      * @param taskId 任务id
  124.      */
  125.     public void removeCronTaskByTaskId(String taskId) {
  126.         //通过任务id获取任务
  127.         Runnable task = this.getTaskByTaskId(taskId);
  128.         //需要通过任务id获取任务,然后再移除
  129.         CronTaskFuture cronTaskFuture = this.scheduledTasks.remove(task);
  130.         if (Objects.nonNull(cronTaskFuture)) {
  131.             cronTaskFuture.cancel();
  132.         }
  133.     }
  134.     /** 通过任务id获取任务。未查询到返回null
  135.      * @param taskId 任务id
  136.      * @return java.lang.Runnable
  137.      * @since 2023/4/21 0021
  138.      * @author CC
  139.      **/
  140.     private Runnable getTaskByTaskId(String taskId) {
  141.         Assert.notNull(taskId, "任务id不能为空!");
  142.         Set<Map.Entry<Runnable, CronTaskFuture>> entries = scheduledTasks.entrySet();
  143.         //根据任务id获取该任务缓存
  144.         Map.Entry<Runnable, CronTaskFuture> rcf = entries.stream().filter(rf -> {
  145.             Runnable key = rf.getKey();
  146.             String taskId1 = null;
  147.             if (key instanceof CronTaskRunnable) {
  148.                 taskId1 = ((CronTaskRunnable) key).getTaskId();
  149.             }
  150.             return taskId.equals(taskId1);
  151.         }).findAny().orElse(null);
  152.         if (Objects.nonNull(rcf)) {
  153.             return rcf.getKey();
  154.         }
  155.         return null;
  156.     }
  157.     /** 删除所有的定时任务
  158.      *     DisposableBean是Spring框架中的一个接口,它定义了一个destroy()方法,
  159.      *     用于在Bean销毁时执行清理工作。
  160.      *     当一个Bean实现了DisposableBean接口时,
  161.      *     Spring容器会在该Bean销毁时自动调用destroy()方法,
  162.      *     以便进行一些清理工作,例如释放资源等。
  163.      *     如果您的Bean需要在销毁时执行一些清理工作,
  164.      *     那么实现DisposableBean接口是一个很好的选择。
  165.      */
  166.     @Override
  167.     public void destroy() {
  168.         //关闭所有定时任务
  169.         for (CronTaskFuture task : this.scheduledTasks.values()) {
  170.             task.cancel();
  171.         }
  172.         //清空缓存
  173.         this.scheduledTasks.clear();
  174.         log.info("取消所有定时任务!");
  175.         //todo cc 修改或删除数据库的任务
  176.     }
  177. }
复制代码
3、定时任务的执行结果ScheduledFuture

  1. package com.cc.ssd.task;
  2. import java.util.Objects;
  3. import java.util.concurrent.ScheduledFuture;
  4. /** CronTaskFuture类中使用的是ScheduledFuture对象来表示定时任务的执行结果。
  5. *  ——最后ps:也可以不要这个记录类,直接缓存ScheduledFuture对象。
  6. *  用来记录单独的Future、定时任务注册任务后产生的
  7. * @author CC
  8. **/
  9. public final class CronTaskFuture {
  10.     /** 每个线程一个副本
  11.      * 经过测试这里不能使用ThreadLocal
  12.      */
  13. //    private static final ThreadLocal<ScheduledFuture<?>> THREAD_LOCAL = new ThreadLocal<>();
  14.     /** 最后ps:由于ScheduledFuture是线程安全的。这里不用 volatile 或者 ThreadLocal
  15.      *      注册任务后返回的:ScheduledFuture 用于记录并取消任务
  16.      *      这两个都可以不使用。直接给future赋值
  17.      *          volatile:线程之间可见:volatile用于实现多线程之间的可见性和一致性,保证数据的正确性。
  18.      *          ThreadLocal:用于实现线程封闭,保证线程安全
  19.      * 使用建议:
  20.      *      CronTaskFuture类中使用的是ScheduledFuture对象来表示定时任务的执行结果。
  21.      *      ScheduledFuture对象是线程安全的,因此不需要使用volatile关键字来保证多线程同步。
  22.      *      如果需要在多线程中使用线程本地变量,可以使用ThreadLocal。
  23.      *      因此,建议在CronTaskFuture类中使用ScheduledFuture对象,而不是使用volatile或ThreadLocal。
  24.      *      另外,如果需要在Spring容器销毁时执行一些清理操作,可以实现DisposableBean接口,并在destroy()方法中进行清理操作。
  25.      */
  26.     public ScheduledFuture<?> future;
  27. //    public volatile ScheduledFuture<?> future;
  28. //    public void setThreadLocal(ScheduledFuture<?> future){
  29. //        THREAD_LOCAL.set(future);
  30. //    }
  31.     /**
  32.      * 取消当前定时任务
  33.      */
  34.     public void cancel() {
  35.         try {
  36. //            ScheduledFuture<?> future = THREAD_LOCAL.get();
  37.             ScheduledFuture<?> future = this.future;
  38.             if (Objects.nonNull(future)) {
  39.                 future.cancel(true);
  40.             }
  41.         } catch (Exception e) {
  42.             throw new RuntimeException("销毁定时任务失败!");
  43.         } finally {
  44. //            THREAD_LOCAL.remove();
  45.         }
  46.     }
  47. }
复制代码
4、具体的任务。

  1. package com.cc.ssd.task;
  2. import lombok.Data;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.stereotype.Component;
  6. /** 具体任务实现
  7. * @author CC
  8. * @since 2023/4/21 0021
  9. */
  10. @Data
  11. public class CronTaskRunnable implements Runnable {
  12.     private static final Logger log = LoggerFactory.getLogger(CronTaskRunnable.class);
  13.     /**
  14.      * 任务id(必须唯一)
  15.      */
  16.     private String taskId;
  17.     /**
  18.      * 任务类型:自定义
  19.      */
  20.     private Integer taskType;
  21.     /**
  22.      * 任务名字
  23.      */
  24.     private String taskName;
  25.     /**
  26.      * 任务参数
  27.      */
  28.     private Object[] params;
  29.     public CronTaskRunnable() {
  30.     }
  31.     public CronTaskRunnable(String taskId, Integer taskType, String taskName, Object... params) {
  32.         this.taskId = taskId;
  33.         this.taskType = taskType;
  34.         this.taskName = taskName;
  35.         this.params = params;
  36.     }
  37.     /** 执行任务
  38.      * @since 2023/4/21 0021
  39.      * @author CC
  40.      **/
  41.     @Override
  42.     public void run() {
  43.         long start = System.currentTimeMillis();
  44.         //具体的任务。
  45.         log.info("\n\t {}号.定时任务开始执行 - taskId:{},taskName:{},taskType:{},params:{}",
  46.                 taskType, taskId, taskName, taskType, params);
  47.         //任务处理的方式:
  48.         //todo cc 1就在这里执行:模拟任务
  49.         //todo cc 2开启策略模式,根据任务类型 调度不同的任务
  50.         //todo cc 3使用反射:传来bean名字,方法名字,调用不同的任务
  51.         //todo cc 4开启队列,把要执行的任务放到队列中,然后执行 —— 使用场景:每个任务执行很耗时的情况下使用
  52.         try {
  53.             Thread.sleep(1000);
  54.         } catch (InterruptedException e) {
  55.             throw new RuntimeException(e);
  56.         }
  57.         log.info("\n\t {}号.任务执行完成 - 耗时:{},taskId:{},taskType:{}",
  58.                 taskType, System.currentTimeMillis() - start, taskId, taskType);
  59.     }
  60. }
复制代码
5、测试Controller
  1. package com.cc.ssd.web.controller;
  2. import com.cc.ssd.registrar.CronTaskRegistrar;
  3. import com.cc.ssd.task.CronTaskRunnable;
  4. import org.springframework.web.bind.annotation.*;
  5. import javax.annotation.Resource;
  6. import java.util.List;
  7. import java.util.Map;
  8. /**
  9. * @author CC
  10. * @since 2023/4/21 0021
  11. */
  12. @RestController
  13. @RequestMapping("/scheduled")
  14. public class TestScheduledController {
  15.     @Resource
  16.     private CronTaskRegistrar cronTaskRegistrar;
  17.     /** 获取任务列表
  18.      * @return java.util.List<com.cc.ssd.task.SchedulingRunnableTask>
  19.      * @since 2023/4/21 0021
  20.      * @author CC
  21.      **/
  22.     @GetMapping
  23.     public List<CronTaskRunnable> getScheduledTasks() {
  24.         return cronTaskRegistrar.getScheduledTasks();
  25.     }
  26.     /** 添加任务
  27.      * @param param param
  28.      * @return java.lang.String
  29.      * @since 2023/4/21 0021
  30.      * @author CC
  31.      **/
  32.     @PostMapping
  33.     public String addCronTask(@RequestBody Map<String, Object> param) {
  34.         //自己拿任务参数的逻辑:可以把每个任务保存到数据库,重新启动任务的同时,加载这些任务到任务调度中心
  35.         String taskId = (String) param.get("taskId");
  36.         Integer taskType = (Integer) param.get("taskType");
  37.         String taskName = (String) param.get("taskName");
  38.         Object params = param.get("params");
  39.         //添加任务参数
  40.         CronTaskRunnable task = new CronTaskRunnable(taskId, taskType, taskName, params);
  41.         //注册任务:cron表达式,可以从传入不一样的
  42.         cronTaskRegistrar.addCronTask(task, "0/5 * * * * ?");
  43.         return "ok";
  44.     }
  45.     /** 根据任务id删除定时任务
  46.      * @param taskId 任务id
  47.      * @return java.lang.String
  48.      * @since 2023/4/21 0021
  49.      * @author CC
  50.      **/
  51.     @DeleteMapping
  52.     public String removeCronTaskByTaskId(@RequestParam String taskId) {
  53.         cronTaskRegistrar.removeCronTaskByTaskId(taskId);
  54.         return "ok";
  55.     }
  56.     /** 删除全部任务
  57.      * @return java.lang.String
  58.      * @since 2023/4/21 0021
  59.      * @author CC
  60.      **/
  61.     @DeleteMapping("/removeAll")
  62.     public String removeCronTask() {
  63.         cronTaskRegistrar.destroy();
  64.         return "ok";
  65.     }
  66. }
复制代码
6、最后效果



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4