Spring Boot 实现定时任务动态管理

前言

本文主要介绍了SpringBoot架构下动态定时任务的使用,定时任务表达式配置在数据库中,通过反射执行到目标方法。

Quartz

Quartz 是一个开源的作业调度框架,支持分布式定时任务,Quartz定时任务据我了解可分为Trigger(触发器)Job(任务)和Scheduler(调度器),定时任务的逻辑大体为:创建触发器和任务,并将其加入到调度器中。

Quartz 的核心类有以下三部分:

任务 Job : 需要实现的任务类,实现 execute() 方法,执行后完成任务;
触发器 Trigger : 包括 SimpleTrigger 和 CronTrigger;
调度器 Scheduler : 任务调度器,负责基于 Trigger触发器,来执行 Job任务.

Trigger 有五种触发器:

  • SimpleTrigger 触发器:需要在特定的日期/时间启动,且以指定的间隔时间(单位毫秒)重复执行 n 次任务,如 :在 9:00 开始,每隔1小时,每隔几分钟,每隔几秒钟执行一次 。没办法指定每隔一个月执行一次(每月的时间间隔不是固定值)。

  • CalendarIntervalTrigger 触发器:指定从某一个时间开始,以一定的时间间隔(单位有秒,分钟,小时,天,月,年,星期)执行的任务。

  • DailyTimeIntervalTrigger 触发器:指定每天的某个时间段内,以一定的时间间隔执行任务。并且支持指定星期。如:指定每天 9:00 至 18:00 ,每隔 70 秒执行一次,并且只要周一至周五执行。

  • CronTrigger 触发器:基于日历的任务调度器,即指定星期、日期的某时间执行任务。

  • NthIncludedDayTrigger 触发器:不同时间间隔的第 n 天执行任务。比如,在每个月的第 15 日处理财务发票记帐,同样设定双休日或者假期。

创建任务表

create table sys_job (job_id              bigint(20)    not null auto_increment    comment '任务ID',job_name            varchar(64)   default ''                 comment '任务名称',job_group           varchar(64)   default 'DEFAULT'          comment '任务组名',invoke_target       varchar(500)  not null                   comment '调用目标方法',cron_expression     varchar(255)  default ''                 comment 'cron执行表达式',misfire_policy      varchar(20)   default '3'                comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)',concurrent          char(1)       default '1'                comment '是否并发执行(0允许 1禁止)',status              char(1)       default '0'                comment '状态(0正常 1暂停)',create_by           varchar(64)   default ''                 comment '创建者',create_time         datetime                                 comment '创建时间',update_by           varchar(64)   default ''                 comment '更新者',update_time         datetime                                 comment '更新时间',remark              varchar(500)  default ''                 comment '备注信息',primary key (job_id, job_name, job_group)
) engine=innodb auto_increment=100 comment = '定时任务调度表';INSERT INTO `sys_job`(`job_id`, `job_name`, `job_group`, `invoke_target`, `cron_expression`, `misfire_policy`, `concurrent`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2, '系统默认(有参)', 'DEFAULT', 'com.demo.task.Task.testParams(\'hello\')', '0/15 * * * * ?', '3', '1', '0', 'admin', '2024-01-16 19:07:33', '', NULL, '');
INSERT INTO `sys_job`(`job_id`, `job_name`, `job_group`, `invoke_target`, `cron_expression`, `misfire_policy`, `concurrent`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3, '系统默认(无参)', 'DEFAULT', 'task.testNoParams()', '0/20 * * * * ?', '3', '1', '0', 'admin', '2024-01-16 19:07:33', '', NULL, '');create table sys_job_log (job_log_id          bigint(20)     not null auto_increment    comment '任务日志ID',job_name            varchar(64)    not null                   comment '任务名称',job_group           varchar(64)    not null                   comment '任务组名',invoke_target       varchar(500)   not null                   comment '调用目标字符串',job_message         varchar(500)                              comment '日志信息',status              char(1)        default '0'                comment '执行状态(0正常 1失败)',exception_info      varchar(2000)  default ''                 comment '异常信息',create_time         datetime                                  comment '创建时间',primary key (job_log_id)
) engine=innodb comment = '定时任务调度日志表';

添加依赖

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId>
</dependency><dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId>
</dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>4.1.14</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId>
</dependency>

定义Job

Quartz定时任务默认都是并发执行的,不会等待上一次任务执行完毕,只要间隔时间到就会执行, 如果定时任执行太长,会长时间占用资源,导致其它任务堵塞。

一般设置都是禁止并发执行

//禁止并发执行
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob {@Overrideprotected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception {JobInvokeUtil.invokeMethod(sysJob);}
}public abstract class AbstractQuartzJob implements Job {private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);/*** 线程本地变量*/private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {SysJob sysJob = new SysJob();BeanUtils.copyProperties(context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES),sysJob);try {before(context, sysJob);if (sysJob != null){doExecute(context, sysJob);}after(context, sysJob, null);}catch (Exception e){log.error("任务执行异常  - :", e);after(context, sysJob, e);}}/*** 执行前** @param context 工作执行上下文对象* @param sysJob 系统计划任务*/protected void before(JobExecutionContext context, SysJob sysJob) {threadLocal.set(new Date());}/*** 执行后** @param context 工作执行上下文对象* @param sysJob 系统计划任务*/protected void after(JobExecutionContext context, SysJob sysJob, Exception e){Date startTime = threadLocal.get();threadLocal.remove();// todo 写入数据库当中}/*** 执行方法,由子类重载** @param context 工作执行上下文对象* @param sysJob 系统计划任务* @throws Exception 执行过程中的异常*/protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
}

实体类

@Data
public class SysJob implements Serializable {private static final long serialVersionUID = 1L;/** 任务ID */private Long jobId;/** 任务名称 */private String jobName;/** 任务组名 */private String jobGroup;/** 调用目标字符串 */private String invokeTarget;/** cron执行表达式 */private String cronExpression;/** cron计划策略 */// 0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT;/** 是否并发执行(0允许 1禁止) */private String concurrent;/** 任务状态(0正常 1暂停) */private String status;}

创建定时任务

    public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException {Class<? extends Job> jobClass = getQuartzJobClass(job);// 构建job信息Long jobId = job.getJobId();String jobGroup = job.getJobGroup();JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();// 表达式调度构建器CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);// 按新的cronExpression表达式构建一个新的triggerCronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)).withSchedule(cronScheduleBuilder).build();// 放入参数,运行时的方法可以获取jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);// 判断是否存在if (scheduler.checkExists(getJobKey(jobId, jobGroup))){// 防止创建时存在数据问题 先移除,然后在执行创建操作scheduler.deleteJob(getJobKey(jobId, jobGroup));}// 判断任务是否过期if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))){// 执行调度任务 核心代码scheduler.scheduleJob(jobDetail, trigger);}// 暂停任务if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())){scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));}}/*** 获取quartz任务类** @param sysJob 执行计划* @return 具体执行任务类*/private static Class<? extends Job> getQuartzJobClass(SysJob sysJob){boolean isConcurrent = "0".equals(sysJob.getConcurrent());return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;}

反射类

public class JobInvokeUtil {/*** 执行方法** @param sysJob 系统任务*/public static void invokeMethod(SysJob sysJob) throws Exception {String invokeTarget = sysJob.getInvokeTarget();String beanName = getBeanName(invokeTarget);String methodName = getMethodName(invokeTarget);List<Object[]> methodParams = getMethodParams(invokeTarget);if (!isValidClassName(beanName)) {Object bean = SpringUtils.getBean(beanName);invokeMethod(bean, methodName, methodParams);}else{Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();invokeMethod(bean, methodName, methodParams);}}/*** 调用任务方法** @param bean 目标对象* @param methodName 方法名称* @param methodParams 方法参数*/private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,InvocationTargetException {if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) {Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));method.invoke(bean, getMethodParamsValue(methodParams));}else{Method method = bean.getClass().getMethod(methodName);method.invoke(bean);}}}

定时任务类

@Component("task")
@Slf4j
public class Task {public void testParams(String params) {log.info("执行有参方法:" + params);System.out.println();}public void testNoParams() {log.info("执行无参方法");}
}

初始化定时任务

    @PostConstructpublic void init() throws SchedulerException, TaskException {scheduler.clear();List<SysJob> jobList = jobMapper.selectList(null);for (SysJob job : jobList) {ScheduleUtils.createScheduleJob(scheduler, job);}}

运行效果:

2024-03-25 14:05:30.020 INFO 11296 — [eduler_Worker-1] com.demo.task.Task : 执行有参方法:hello

2024-03-25 14:05:40.005 INFO 11296 — [eduler_Worker-2] com.demo.task.Task : 执行无参方法
2024-03-25 14:05:45.008 INFO 11296 — [eduler_Worker-3] com.demo.task.Task : 执行有参方法:hello

2024-03-25 14:06:00.012 INFO 11296 — [eduler_Worker-4] com.demo.task.Task : 执行有参方法:hello

2024-03-25 14:06:00.014 INFO 11296 — [eduler_Worker-5] com.demo.task.Task : 执行无参方法

添加定时任务

    public int insertJob(SysJob job) throws SchedulerException, TaskException {job.setStatus(ScheduleConstants.Status.PAUSE.getValue());int rows = jobMapper.insert(job);if (rows > 0) {ScheduleUtils.createScheduleJob(scheduler, job);}return rows;}

解决 Quartz Job 中无法注入 Spring Bean

首先自定义一个 JobFactory,通过 AutowireCapableBeanFactory 将创建好的 Job 对象交给 Spring 管理

@Configuration
public class CustomJobFactory extends AdaptableJobFactory {@Autowiredprivate AutowireCapableBeanFactory autowireCapableBeanFactory;/*** Create the job instance, populating it with property values taken* from the scheduler context, job data map and trigger data map.** @param bundle*/@Overrideprotected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {Object jobInstance = super.createJobInstance(bundle);autowireCapableBeanFactory.autowireBean(jobInstance);return jobInstance;}
}

再创建一个配置类,将自定义的 JobFactory 设置到 Schedule

@Configuration
public class QuartzConfig {@Autowiredprivate CustomJobFactory customJobFactory;@SneakyThrows@Beanpublic Scheduler scheduler(){SchedulerFactory schedulerFactory = new StdSchedulerFactory();Scheduler scheduler = schedulerFactory.getScheduler();// 自定义 JobFactory 使得在 Quartz Job 中可以使用 @Autowiredscheduler.setJobFactory(customJobFactory);scheduler.start();return scheduler;}}

总结

本文是基于 Quartz 实现的动态定时任务,有些场景比如任务暂停、任务删除、任务立即执行,参考下面的源码,这里不再赘述了。
项目代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/770588.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小迪安全47WEB 攻防-通用漏洞Java 反序列化EXP 生成数据提取组件安全

#知识点&#xff1a; 1、Java 反序列化演示-原生 API 接口 2、Java 反序列化漏洞利用-Ysoserial 使用 3、Java 反序列化漏洞发现利用点-函数&数据 4、Java 反序列化考点-真实&CTF 赛题-审计分析 #内容点&#xff1a; 1、明白-Java 反序列化原理 2、判断-J…

javaWeb在线考试系统

一、简介 在线考试系统是现代教育中一项重要的辅助教学工具&#xff0c;它为学生提供了便捷的考试方式&#xff0c;同时也为教师提供了高效的考试管理方式。我设计了一个基于JavaWeb的在线考试系统&#xff0c;该系统包括三个角色&#xff1a;管理员、老师和学生。管理员拥有菜…

Knative 助力 XTransfer 加速应用云原生 Serverless 化

作者&#xff1a;元毅 公司介绍 XTransfer 是一站式外贸企业跨境金融和风控服务公司&#xff0c;致力于帮助中小微企业大幅降低全球展业的门槛和成本&#xff0c;提升全球竞争力。公司连续7年专注 B2B 外贸金融服务&#xff0c;已成为中国 B2B 外贸金融第一平台&#xff0c;目…

设计模式(2):单例模式

核心作用&#xff1a; 包装一个类只有一个实例&#xff0c;并且提供一个访问该实例的全局访问点。 常见应用场景&#xff1a; windows的任务管理者(Task Manager)就是很典型的单例模式&#xff1b;在spring中&#xff0c;每个Bean默认就是单例的&#xff0c;这样做的优点是s…

Kubernetes概念:服务、负载均衡和联网:1. 服务(Service)

服务&#xff08;Service&#xff09; 官方文档&#xff1a;https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/ Kubernetes 中 Service 是 将运行在一个或一组 Pod 上的网络应用程序公开为网络服务的方法。 Kubernetes 中 Service 的一个关键目标是让…

荟萃分析R Meta-Analyses 2----发现R

2.1安装R和R Studio 在开始之前&#xff0c;我们必须下载并准备一个计算机程序&#xff0c;该程序使我们能够方便地使用R进行统计分析。目前最好的选择可能是R Studio。该程序为我们提供了一个用户界面&#xff0c;使我们可以更轻松地处理数据、包和输出。最好的部分是 R Studi…

2024/3/25 蓝桥杯

P8739 [蓝桥杯 2020 国 C] 重复字符串 import java.util.HashMap; import java.util.Map; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int k sc.nextInt();sc.nextLine();char[] c sc.nextL…

python编写API接口实现数据筛选、查询与分页

目录 一、背景 二、代码 一、背景 由于系统上需要分页展示数据&#xff0c;并提供按字段筛选数据的功能&#xff0c;于是需要我写个接口&#xff0c;以供前端使用。 接口可以通过python flask框架实现。Flask是一个轻量级的Web框架&#xff0c;它提供了足够的灵活性来构建定…

如何用pycharm运行.sh文件

在PyCharm中运行.sh文件有几种方法&#xff0c;以下是其中一种常见的方法&#xff1a; 创建Shell脚本运行配置&#xff1a; 打开PyCharm&#xff0c;确保您的项目已经打开。 在项目中找到您的.sh文件&#xff0c;右键点击它并选择“Create <your_script_name>.sh”&…

学会Sass的高级用法,减少样式冗余

在当今的前端开发领域&#xff0c;样式表语言的进步已经显著提升了代码组织性和可维护性。Sass&#xff08;Syntactically Awesome Style Sheets&#xff09;作为CSS预处理器的翘楚&#xff0c;以其强大的变量、嵌套规则、混合宏&#xff08;mixin&#xff09;、循环和函数等高…

STM32学习笔记(6_5)- TIM定时器的输出捕获原理

无人问津也好&#xff0c;技不如人也罢&#xff0c;都应静下心来&#xff0c;去做该做的事。 最近在学STM32&#xff0c;所以也开贴记录一下主要内容&#xff0c;省的过目即忘。视频教程为江科大&#xff08;改名江协科技&#xff09;&#xff0c;网站jiangxiekeji.com 现在开…

ASR-LLM-TTS 大模型对话实现案例;语音识别、大模型对话、声音生成

参考:https://blog.csdn.net/weixin_42357472/article/details/136305123(llm+tts) https://blog.csdn.net/weixin_42357472/article/details/136411769 (asr+vad) 这里LLM用的是chatglm;电脑声音播报用的playsound 代码: ##运行 python main.pymain.py from multipro…

mac电脑下安装和启动nginx

一,安装homebrew 必须安装了homebrew&#xff0c;可在终端输入命令brew -v查看是否已经安装,没安装的话安装一下: 如果未安装先安装&#xff08;网上很多文章&#xff09; 二,查看nginx是否存在 使用命令:brew search nginx查看nginx是否存在: 不存在的话,就使用brew inst…

记录RK键盘蓝牙搜索不到

说明书 现象 Q键快速闪烁等待回连但是蓝牙搜索不到 过程 使用手机尝试搜索&#xff0c;依旧失败 解决方案 fnw 长按 w键开始闪烁 打开蓝牙搜索 链接成功

mac 系统如何生成秘钥

1.打开终端&#xff0c;输入 cd ~/.ssh 进入.ssh目录&#xff0c;输入 ls 检查是否已经存在SSH密钥。如果看到类似 id_rsa.pub 的文件&#xff0c;说明已经有一对公钥和私钥&#xff0c;不用新建&#xff0c;直接查看就可以&#xff0c;如果没有需要生成新的密钥。 2.在终端输…

软件工程---专业名称

单元测试(Unit Testing):单元测试是一种软件测试方法,用于验证软件中最小的可测试单元(通常是函数或方法)是否按照预期工作。它通过编写测试用例来检查单元的输入和输出,以确保其功能正确性和稳定性。用例图(Use Case Diagram):用例图是一种UML(统一建模语言)图表,…

(三)Qt+OpenCV调用海康工业相机SDK抓拍示例

系列文章目录 提示&#xff1a;这里是该系列文章的所有文章的目录 第一章&#xff1a; &#xff08;一&#xff09;QtOpenCV调用海康工业相机SDK示例开发 第二章&#xff1a; &#xff08;二&#xff09;Qt多线程实现海康工业相机图像实时采集 第三章&#xff1a; &#xff08;…

如何在CentOS 7上搭建Redis Cluster ?

在CentOS 7上搭建Redis Cluster的过程涉及到准备环境、安装Redis、配置Redis实例以及初始化集群。下面是详细的步骤&#xff1a; 1. 环境准备 确保系统是最新的&#xff0c;如果你没有添加EPEL仓库&#xff0c;首先需要添加它&#xff0c;因为Redis可能需要从该仓库安装。 s…

许可型DeFi 项目NEOPIN:在合规的框架下推动DeFi的创新

合规化已成 DeFi 的主流方向 过去的几年里&#xff0c;全球金融市场见证了 DeFi 的快速增长。DeFi 通过提供无需中介的交易和借款等金融服务&#xff0c;为用户带来前所未有的便利。然而&#xff0c;其匿名性和去中心化的特性也为监管和安全带来挑战 —— DeFi项目的透明度不足…

Flutter中被屏蔽的概念,Scheduler(调度器)

前言 一切皆Widget&#xff0c;良好的底层设计都会屏蔽底层的逻辑&#xff0c;Java如此&#xff0c;Flutter亦是如此&#xff0c;甚至还有开发者面向Getx编程&#xff0c;那么我们可以做如是类比&#xff0c;Flutter是J2EE, Getx是Spring套件&#xff0c;作为Java后台开发&…