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;目…

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

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

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

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

学会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…

mac 系统如何生成秘钥

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

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

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

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

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

C#自定义控件 生成 与 加入到项目

C#自定义控件生成 在C#中&#xff0c;自定义控件通常是通过继承现有的控件类&#xff08;如UserControl、Form等&#xff09;并添加或修改其属性和方法来实现的。以下是一个简单的示例&#xff0c;演示如何创建一个自定义控件&#xff1a; 首先&#xff0c;创建一个新的Window…

荟萃分析R Meta-Analyses 1

参考&#xff1a;Harrer, M.、Cuijpers, P.、Furukawa, TA 和 Ebert, DD (2021)。 使用 R 进行荟萃分析&#xff1a;实践指南。佛罗里达州博卡拉顿和伦敦&#xff1a;Chapman & Hall/CRC Press。 ISBN 978-0-367-61007-4。 1.1什么是荟萃分析&#xff1f; 它的创始人之一 G…

电脑如何更新AMD独立显卡驱动?安装官方驱动的方法来了!

前言 有小伙伴在电脑上安装了独立显卡之后&#xff0c;总会用驱动人生或者驱动精灵等软件给独立显卡安装驱动。这种安装方法并不能说是错的&#xff0c;反正能用就行。 安装官方驱动的办法其实很简单&#xff0c;现在独立显卡一共就那么几家&#xff0c;最常见的显卡就是Nvidi…

我们是如何在 IDE 中设计 AutoDev 的 AI 编程开发智能体语言与框架?

上周微软发布了自家的 AI 编程和软件开发智能体框架&#xff1a;AutoDev&#xff0c;其与我们开发的 IDE 插件 AutoDev 有颇多的相似之处&#xff0c;特别是一些设计思路&#xff0c;以及在对于辅助软件开发任务的智能体以及一些基础设施上。 稍有不同的是&#xff1a; 交互介质…

随手笔记——禾赛Pandar64雷达旋转方向及坐标系定义

随手笔记——禾赛Pandar64雷达旋转方向及坐标系定义 手册下载

Maven学习记录

一、简介 1. Maven&#xff1a; 基于 Java 平台的项目管理和整合工具&#xff0c;将项目的开发和管理过程抽象成一个项目对象模型&#xff08;POM&#xff09;。开发人员只需要做一些简单的配置&#xff0c;Maven 就可以自动完成项目的编译、测试、打包、发布以及部署等工作。…

【NLP学习记录】Embedding和EmbeddingBag

Embedding与EmbeddingBag详解 ●&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 ●&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 ●&#x1f680; 文章来源&#xff1a;K同学的学习圈子1、Embedding详解 Embedding是Pytorch中最基本…

备考ICA----Istio实验8---请求超时 Request Timeouts 实验

备考ICA----Istio实验8—请求超时 Request Timeouts 实验 1. 重置bookinfo环境 kubectl delete all --all --now kubectl delete dr --all kubectl delete vs --all kubectl delete gw --all kubectl apply -f istio/samples/bookinfo/platform/kube/bookinfo.yaml kubectl a…