SpringBoot自定义cron表达式注册定时任务
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、最后效果
- 自己用controller去测试一波吧

SpringBoot自定义cron表达式注册定时任务的更多相关文章
- 使用Cron表达式创建定时任务
CronTriggerCronTrigger功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用.CroTr ...
- SpringBoot自定义servlet、注册自定义的servlet、过滤器、监听器、拦截器、切面、webmvcconfigureradapter过时问题
[转]https://www.cnblogs.com/NeverCtrl-C/p/8191920.html 1 servlet简介 servlet是一种用于开发动态web资源的技术 参考博客:serv ...
- Quartz 和 springboot schedule中的cron表达式关于星期(周几)的不同表示
一.Quartz中cron 表达式分析: quartz 官方源码(org.quartz.CronExpression)解释: Cron expressions are comprised of 6 r ...
- 详解定时任务中的 cron 表达式
1.前言 我们经常使用 cron 表达式来定义定时任务的执行策略,今天我们就总结一下 cron 表达式的一些相关知识. 2. cron 表达式的定义 cron 表达式是一个字符串,该字符串由 6 个空 ...
- SpringBoot: 18.使用Scheduled 定时任务器(转)
Scheduled 定时任务器:是 Spring3.0 以后自带的一个定时任务器. 1.在pom.xml文件中添加Scheduled依赖 <!-- 添加spring定时任务 Scheduled ...
- Cron表达式,springboot定时任务
详细请看这篇博客 参考:https://blog.csdn.net/belonghuang157405/article/details/83410197 Cron表达式是一个字符串,字符串以5或6个空 ...
- springboot内嵌定时任务使用及cron表达式讲解
第一步:pom引入依赖 <dependencies> <dependency> <groupId>org.springframework.boot</grou ...
- 动态更改Spring定时任务Cron表达式的优雅方案
"Most of you are familiar with the virtues of a programmer. There are three, of course: lazines ...
- (2)Spring集成Quartz定时任务框架介绍和Cron表达式详解
在JavaEE系统中,我们会经常用到定时任务,比如每天凌晨生成前天报表,每一小时生成汇总数据等等.我们可以使用java.util.Timer结合java.util.TimerTask来完成这项工作,但 ...
- Spring集成Quartz定时任务框架介绍和Cron表达式详解
原文地址:http://www.cnblogs.com/obullxl/archive/2011/07/10/spring-quartz-cron-integration.html 在JavaEE系统 ...
随机推荐
- Q:su命令切换用户无法使用,被拒绝
su命令切换用户无法使用,被拒绝 问题描述 su 命令报错 su: Permission denied 如下图: su 命令 报错 su: Permission denied,不管是su普通用户还是r ...
- 变量调用分析——这个ball到底是那个ball?
public class Ball implements Rollable{ public static void main(String[] args) { Ball ball = new Ball ...
- WEB开发日志1
2020/6/11 23:23 今天做系统时,用到二级菜单,菜单下方放了一个<iframe>标签,但二级菜单的菜单项太多,导致一部分菜单项被<iframe>覆盖,从而无法再选中 ...
- manjaro安装指导
本文"指导"二字口气有点大,是说给自己听的,指导我下次的安装. 正文: 1.安装系统:在清华大学开源站上下载KDE版(本机适用19版54内核无驱动问题),用rufus烧制启动盘,以 ...
- 无法将类 org.example.sh.utils.PageInfo<T>中的构造器 PageInfo应用到给定类型;
是因为没有在工具类中加入构造器, @Data @NoArgsConstructor @AllArgsConstructor @ToString
- golang学习路线
一.golang基础 李文周的博客:https://www.liwenzhou.com/ 视频教程:https://link.juejin.cn/?target=https%3A%2F%2Fwww.b ...
- K8S Operator的开发与使用
从应用角度考虑,为什么会出现如此多的Operator场景,为什么很多中间件和厂商都会提供基于Operator的部署方案,他的价值是什么? 随着时代的发展,企业应用部署环境从传统的物理机->虚拟机 ...
- iOS C#远程推送证书.p12文件制作
1.PushChat.certSigningRequest 请求证书文件 生成Certificate Signing Request (CSR): 2.填写你的邮箱和Common Name, ...
- 【新版】使用 go-cqhttp 扫码登录,一键接入 ChatGPT 机器人到 QQ 群
目录 项目效果 安装 go-cqhttp 虚拟文件 启动 ChatGPT 项目效果 由于 ChatGPT 目前只能在漂亮国使用,所以想要在国内使用 ChatGPT 必然险阻重重 不仅时时刻刻要跟企鹅公 ...
- 如何利用javaweb实现数据的可视化
描述 之前一直使用html进行网页版的数据库查询啥的,没有图片的参与,也没有将一条条数据变成较为直观的图画形式,这就是来实现以下数据的图画形式 了解及基础说明 通过查阅资料,我首先了解到要是想实现数据 ...