springBoot自定义cron表达式注册定时任务
一、原理
- 1、使用Spring自带的TaskScheduler注册任务
- 2、注册后返回:ScheduledFuture,用于取消定时任务
- 3、注册任务后不会马上取消任务,所以将任务缓存。在需要取消任务的时候调用取消接口取消
- 4、cron表达式可以由前端或者后端生成。实现中会校验cron表达式
- public class TestScheduled {
- /**
- * 1、使用Spring自带的TaskScheduler注册任务
- * 2、注册后返回:ScheduledFuture,用于取消定时任务
- */
- @Resource
- private TaskScheduler taskScheduler;
- public void registrarTask() {
- //具体的任务Runnable(一般使用类实现Runnable接口)
- Runnable taskRunnable = new Runnable() {
- @Override
- public void run() {
- }
- };
- //cron表达式触发器
- CronTrigger trigger = new CronTrigger("0/5 * * * * ?");
- //开启定时任务的真正方法
- ScheduledFuture<?> future = this.taskScheduler.schedule(taskRunnable, trigger);
- //取消定时任务
- future.cancel(true);
- }
- }
复制代码 二、具体实现
1、配置任务调度器
- 作用:设置:核心线程数:可同时执行任务数;设置线程名称前缀
- 可以不配置。不配置就默认使用spring自带的
- package com.cc.ssd.config;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.scheduling.TaskScheduler;
- import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
- /** TaskScheduler任务调度器配置类
- * @since 2023/4/21 0021
- * @author CC
- **/
- @Configuration
- public class CronTaskConfig {
- /**
- * 任务调度器自定义配置
- */
- @Bean(name = "taskScheduler")
- public TaskScheduler taskScheduler() {
- // 任务调度线程池
- ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
- // 定时任务执行线程池核心线程数:可同时执行4个任务
- taskScheduler.setPoolSize(4);
- taskScheduler.setRemoveOnCancelPolicy(true);
- // 线程名称前缀
- taskScheduler.setThreadNamePrefix("Cs-ThreadPool-");
- return taskScheduler;
- }
- }
复制代码 2、定时任务注册类
- 作用:缓存、注册定时任务;还可以查询、删除定时任务
- package com.cc.ssd.registrar;
- import com.cc.ssd.task.CronTaskFuture;
- import com.cc.ssd.task.CronTaskRunnable;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.BeanUtils;
- import org.springframework.beans.factory.DisposableBean;
- import org.springframework.scheduling.TaskScheduler;
- import org.springframework.scheduling.config.CronTask;
- import org.springframework.scheduling.support.CronExpression;
- import org.springframework.stereotype.Component;
- import org.springframework.util.Assert;
- import javax.annotation.Resource;
- import java.time.LocalDateTime;
- import java.time.format.DateTimeFormatter;
- import java.util.*;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.stream.Collectors;
- /** 注册定时任务:缓存定时任务、注册定时任务到调度中心
- * @author CC
- **/
- @Component
- public class CronTaskRegistrar implements DisposableBean {
- private static final Logger log = LoggerFactory.getLogger(CronTaskRegistrar.class);
- /**
- * 缓存任务
- * key:具体的任务
- * value:注册定时任务后返回的ScheduledFuture
- */
- private final Map<Runnable, CronTaskFuture> scheduledTasks = new ConcurrentHashMap<>(16);
- /**
- * 使用自定义的任务调度配置
- */
- @Resource(name = "taskScheduler")
- private TaskScheduler taskScheduler;
- /** 获取任务调度配置
- * @return 任务调度配置
- */
- public TaskScheduler getTaskScheduler() {
- return this.taskScheduler;
- }
- /** 新增定时任务1
- * 存在任务:删除此任务,重新新增这个任务
- * @param taskRunnable 执行的具体任务定义:taskRunnable 实现Runnable
- * @param cronExpression cron表达式
- */
- public void addCronTask(Runnable taskRunnable, String cronExpression) {
- //验证cron表达式是否正确
- boolean validExpression = CronExpression.isValidExpression(cronExpression);
- if (!validExpression) {
- throw new RuntimeException("cron表达式验证失败!");
- }
- //获取下次执行时间
- CronExpression parse = CronExpression.parse(cronExpression);
- LocalDateTime next = parse.next(LocalDateTime.now());
- if (Objects.nonNull(next)) {
- //定时任务下次执行的时间
- String format = next.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
- log.info("定时任务下次执行的时间:{}", format);
- }
- //封装成 CronTask(cron任务)
- CronTask cronTask = new CronTask(taskRunnable, cronExpression);
- this.addCronTask(cronTask);
- }
- /** 新增定时任务2
- * @param cronTask :<p>CronTask用于在指定时间间隔内执行定时任务。</p>
- * <p>它是通过CronTrigger来实现的,CronTrigger是一个基于cron表达式的触发器,</p>
- * <p>可以在指定的时间间隔内触发任务执行。</p>
- * @since 2023/4/21 0021
- * @author CC
- **/
- private void addCronTask(CronTask cronTask) {
- if (Objects.nonNull(cronTask)) {
- //1有这个任务,先删除这个任务。再新增
- Runnable task = cronTask.getRunnable();
- String taskId = null;
- if (task instanceof CronTaskRunnable) {
- taskId = ((CronTaskRunnable) task).getTaskId();
- }
- //通过任务id获取缓存的任务,如果包含则删除,然后新增任务
- Runnable taskCache = this.getTaskByTaskId(taskId);
- if (Objects.nonNull(taskCache) && this.scheduledTasks.containsKey(taskCache)) {
- this.removeCronTaskByTaskId(taskId);
- }
- //2注册定时任务到调度中心
- CronTaskFuture scheduledFutureTask = this.scheduleCronTask(cronTask);
- //3缓存定时任务
- this.scheduledTasks.put(task, scheduledFutureTask);
- //todo cc 4可以将任务保存到数据库中……重新启动程序然后加载数据库中的任务到缓存中……
- }
- }
- /** 注册 ScheduledTask 定时任务
- * @param cronTask cronTask
- * @return 注册定时任务后返回的 ScheduledFutureTask
- */
- private CronTaskFuture scheduleCronTask(CronTask cronTask) {
- //注册定时任务后记录的Future
- CronTaskFuture scheduledTask = new CronTaskFuture();
- //开启定时任务的真正方法
- scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
- // scheduledTask.setThreadLocal(this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()));
- return scheduledTask;
- }
- /** 获取任务列表
- * @return
- */
- public List<CronTaskRunnable> getScheduledTasks() {
- List<CronTaskRunnable> tasks = new ArrayList<>();
- Set<Runnable> keySet = scheduledTasks.keySet();
- keySet.forEach(key -> {
- CronTaskRunnable task = new CronTaskRunnable();
- if (key instanceof CronTaskRunnable) {
- CronTaskRunnable taskParent = (CronTaskRunnable) key;
- BeanUtils.copyProperties(taskParent, task);
- }
- tasks.add(task);
- });
- return tasks.stream()
- .sorted(Comparator.comparing(CronTaskRunnable::getTaskId))
- .collect(Collectors.toList());
- }
- /** 根据任务id删除单个定时任务
- * @param taskId 任务id
- */
- public void removeCronTaskByTaskId(String taskId) {
- //通过任务id获取任务
- Runnable task = this.getTaskByTaskId(taskId);
- //需要通过任务id获取任务,然后再移除
- CronTaskFuture cronTaskFuture = this.scheduledTasks.remove(task);
- if (Objects.nonNull(cronTaskFuture)) {
- cronTaskFuture.cancel();
- }
- }
- /** 通过任务id获取任务。未查询到返回null
- * @param taskId 任务id
- * @return java.lang.Runnable
- * @since 2023/4/21 0021
- * @author CC
- **/
- private Runnable getTaskByTaskId(String taskId) {
- Assert.notNull(taskId, "任务id不能为空!");
- Set<Map.Entry<Runnable, CronTaskFuture>> entries = scheduledTasks.entrySet();
- //根据任务id获取该任务缓存
- Map.Entry<Runnable, CronTaskFuture> rcf = entries.stream().filter(rf -> {
- Runnable key = rf.getKey();
- String taskId1 = null;
- if (key instanceof CronTaskRunnable) {
- taskId1 = ((CronTaskRunnable) key).getTaskId();
- }
- return taskId.equals(taskId1);
- }).findAny().orElse(null);
- if (Objects.nonNull(rcf)) {
- return rcf.getKey();
- }
- return null;
- }
- /** 删除所有的定时任务
- * DisposableBean是Spring框架中的一个接口,它定义了一个destroy()方法,
- * 用于在Bean销毁时执行清理工作。
- * 当一个Bean实现了DisposableBean接口时,
- * Spring容器会在该Bean销毁时自动调用destroy()方法,
- * 以便进行一些清理工作,例如释放资源等。
- * 如果您的Bean需要在销毁时执行一些清理工作,
- * 那么实现DisposableBean接口是一个很好的选择。
- */
- @Override
- public void destroy() {
- //关闭所有定时任务
- for (CronTaskFuture task : this.scheduledTasks.values()) {
- task.cancel();
- }
- //清空缓存
- this.scheduledTasks.clear();
- log.info("取消所有定时任务!");
- //todo cc 修改或删除数据库的任务
- }
- }
复制代码 3、定时任务的执行结果ScheduledFuture
- 作用:CronTaskFuture类中使用的是ScheduledFuture对象来表示定时任务的执行结果。
- package com.cc.ssd.task;
- import java.util.Objects;
- import java.util.concurrent.ScheduledFuture;
- /** CronTaskFuture类中使用的是ScheduledFuture对象来表示定时任务的执行结果。
- * ——最后ps:也可以不要这个记录类,直接缓存ScheduledFuture对象。
- * 用来记录单独的Future、定时任务注册任务后产生的
- * @author CC
- **/
- public final class CronTaskFuture {
- /** 每个线程一个副本
- * 经过测试这里不能使用ThreadLocal
- */
- // private static final ThreadLocal<ScheduledFuture<?>> THREAD_LOCAL = new ThreadLocal<>();
- /** 最后ps:由于ScheduledFuture是线程安全的。这里不用 volatile 或者 ThreadLocal
- * 注册任务后返回的:ScheduledFuture 用于记录并取消任务
- * 这两个都可以不使用。直接给future赋值
- * volatile:线程之间可见:volatile用于实现多线程之间的可见性和一致性,保证数据的正确性。
- * ThreadLocal:用于实现线程封闭,保证线程安全
- * 使用建议:
- * CronTaskFuture类中使用的是ScheduledFuture对象来表示定时任务的执行结果。
- * ScheduledFuture对象是线程安全的,因此不需要使用volatile关键字来保证多线程同步。
- * 如果需要在多线程中使用线程本地变量,可以使用ThreadLocal。
- * 因此,建议在CronTaskFuture类中使用ScheduledFuture对象,而不是使用volatile或ThreadLocal。
- * 另外,如果需要在Spring容器销毁时执行一些清理操作,可以实现DisposableBean接口,并在destroy()方法中进行清理操作。
- */
- public ScheduledFuture<?> future;
- // public volatile ScheduledFuture<?> future;
- // public void setThreadLocal(ScheduledFuture<?> future){
- // THREAD_LOCAL.set(future);
- // }
- /**
- * 取消当前定时任务
- */
- public void cancel() {
- try {
- // ScheduledFuture<?> future = THREAD_LOCAL.get();
- ScheduledFuture<?> future = this.future;
- if (Objects.nonNull(future)) {
- future.cancel(true);
- }
- } catch (Exception e) {
- throw new RuntimeException("销毁定时任务失败!");
- } finally {
- // THREAD_LOCAL.remove();
- }
- }
- }
复制代码 4、具体的任务。
- 实现Runable接口
- 任务处理的方式按照自己的需求去实现即可
- package com.cc.ssd.task;
- import lombok.Data;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.stereotype.Component;
- /** 具体任务实现
- * @author CC
- * @since 2023/4/21 0021
- */
- @Data
- public class CronTaskRunnable implements Runnable {
- private static final Logger log = LoggerFactory.getLogger(CronTaskRunnable.class);
- /**
- * 任务id(必须唯一)
- */
- private String taskId;
- /**
- * 任务类型:自定义
- */
- private Integer taskType;
- /**
- * 任务名字
- */
- private String taskName;
- /**
- * 任务参数
- */
- private Object[] params;
- public CronTaskRunnable() {
- }
- public CronTaskRunnable(String taskId, Integer taskType, String taskName, Object... params) {
- this.taskId = taskId;
- this.taskType = taskType;
- this.taskName = taskName;
- this.params = params;
- }
- /** 执行任务
- * @since 2023/4/21 0021
- * @author CC
- **/
- @Override
- public void run() {
- long start = System.currentTimeMillis();
- //具体的任务。
- log.info("\n\t {}号.定时任务开始执行 - taskId:{},taskName:{},taskType:{},params:{}",
- taskType, taskId, taskName, taskType, params);
- //任务处理的方式:
- //todo cc 1就在这里执行:模拟任务
- //todo cc 2开启策略模式,根据任务类型 调度不同的任务
- //todo cc 3使用反射:传来bean名字,方法名字,调用不同的任务
- //todo cc 4开启队列,把要执行的任务放到队列中,然后执行 —— 使用场景:每个任务执行很耗时的情况下使用
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- log.info("\n\t {}号.任务执行完成 - 耗时:{},taskId:{},taskType:{}",
- taskType, System.currentTimeMillis() - start, taskId, taskType);
- }
- }
复制代码 5、测试Controller
- package com.cc.ssd.web.controller;
- import com.cc.ssd.registrar.CronTaskRegistrar;
- import com.cc.ssd.task.CronTaskRunnable;
- import org.springframework.web.bind.annotation.*;
- import javax.annotation.Resource;
- import java.util.List;
- import java.util.Map;
- /**
- * @author CC
- * @since 2023/4/21 0021
- */
- @RestController
- @RequestMapping("/scheduled")
- public class TestScheduledController {
- @Resource
- private CronTaskRegistrar cronTaskRegistrar;
- /** 获取任务列表
- * @return java.util.List<com.cc.ssd.task.SchedulingRunnableTask>
- * @since 2023/4/21 0021
- * @author CC
- **/
- @GetMapping
- public List<CronTaskRunnable> getScheduledTasks() {
- return cronTaskRegistrar.getScheduledTasks();
- }
- /** 添加任务
- * @param param param
- * @return java.lang.String
- * @since 2023/4/21 0021
- * @author CC
- **/
- @PostMapping
- public String addCronTask(@RequestBody Map<String, Object> param) {
- //自己拿任务参数的逻辑:可以把每个任务保存到数据库,重新启动任务的同时,加载这些任务到任务调度中心
- String taskId = (String) param.get("taskId");
- Integer taskType = (Integer) param.get("taskType");
- String taskName = (String) param.get("taskName");
- Object params = param.get("params");
- //添加任务参数
- CronTaskRunnable task = new CronTaskRunnable(taskId, taskType, taskName, params);
- //注册任务:cron表达式,可以从传入不一样的
- cronTaskRegistrar.addCronTask(task, "0/5 * * * * ?");
- return "ok";
- }
- /** 根据任务id删除定时任务
- * @param taskId 任务id
- * @return java.lang.String
- * @since 2023/4/21 0021
- * @author CC
- **/
- @DeleteMapping
- public String removeCronTaskByTaskId(@RequestParam String taskId) {
- cronTaskRegistrar.removeCronTaskByTaskId(taskId);
- return "ok";
- }
- /** 删除全部任务
- * @return java.lang.String
- * @since 2023/4/21 0021
- * @author CC
- **/
- @DeleteMapping("/removeAll")
- public String removeCronTask() {
- cronTaskRegistrar.destroy();
- return "ok";
- }
- }
复制代码 6、最后效果

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