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.rhkb.cn/news/285547.html

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

相关文章

毕业论文降重(gpt+完美降重指令),sci论文降重gpt指令——超级好用,重复率低于4%

1. 降重方法&#xff1a;gpt降重指令 2. gpt网站 https://yiyan.baidu.com/ https://chat.openai.com/ 3. 降重指令——非常好用&#xff01;&#xff01;sci论文&#xff0c;本硕大论文都可使用&#xff01; 请帮我把下面句子重新组织&#xff0c;通过调整句子逻辑&#xff0…

nvidia显卡如何安装cuda驱动

目录 查看显卡对应的cuda版本下载与你显卡匹配的CUDA Toolkit 查看显卡对应的cuda版本 按 微软 R 键&#xff0c;输入cmd 然后输入 nvidia-smi &#xff0c;回车显示下面信息&#xff1a; 看到 CUDA Version 为 12.2 下载与你显卡匹配的CUDA Toolkit 打开网页&#xff1a…

【项目管理——时间管理】【自用笔记】

1 项目时间管理&#xff08;进度管理&#xff09;概述 过程&#xff1a;&#xff08;2—6&#xff09;为规划过程组&#xff0c;7为监控过程组 题目定义&#xff1a;项目时间管理又称为进度管理&#xff0c;是指确保项目按时完成所需的过程。目标&#xff1a;时间管理的主要目标…

疲劳检测YOLOV8

疲劳检测YOLOV8&#xff0c;只需要OPENCV&#xff0c;采用YOLOV8训练得到PT模型&#xff0c;然后转换成ONNX&#xff0c;OPENCV调用&#xff0c;支持C/PYTHON/ANDROID开发疲劳检测YOLOV8

嵌入式开发--获取STM32产品系列的信息

嵌入式开发–获取STM32产品系列和容量信息 获取STM32产品系列 有时候我们需要知道当前MCU是STM32的哪一个系列&#xff0c;这当然可以从外部丝印看出来&#xff0c;但是运行在内部的软件如何知道呢&#xff1f; ST为我们提供了一个接口&#xff0c;F1和G4系列地址统一为&…

第十四届蓝桥杯省赛C++ A组所有题目以及题解(C++)【编程题均通过100%测试数据】

第一题《幸运数》【模拟】 【问题描述】 小蓝认为如果一个数含有偶数个数位&#xff0c;并且前面一半的数位之和等于后面一半的数位之和&#xff0c;则这个数是他的幸运数字。例如 2314是一个幸运数字,因为它有4个数位,并且2314。现在请你帮他计算从1至100000000之间共有多少…

李国武:如何评估一家精益制造咨询公司的实施能力?

在制造业转型升级的大背景下&#xff0c;精益制造已成为企业提升竞争力、实现可持续发展的关键。然而&#xff0c;面对市场上众多的精益制造咨询公司&#xff0c;如何评估其实施能力成为了众多企业的难题。本文将从多个方面为大家揭示评估精益制造咨询公司实施能力的方法&#…

Java实现JDBC编程

1 数据库编程的必备条件 编程语言&#xff0c;如Java&#xff0c;C、C、Python等 数据库&#xff0c;如Oracle&#xff0c;MySQL&#xff0c;SQL Server等 数据库驱动包&#xff1a;不同的数据库&#xff0c;对应不同的编程语言提供了不同的数据库驱动包&#xff0c;如&#x…

Linux-shell中变量的引用($变量名,${变量名})

1. 背景 最近写脚本时&#xff0c;发现有个变量在某个地方生效&#xff0c;某个地方又不生效&#xff0c;引用方式为 $变量名。 2. 方法 其实 shell 脚本中对变量的引用有两种方式&#xff1a; $变量名${变量名} 用下面的脚步&#xff0c;去测试效果&#xff1a; a100 b2…

前端Webpack5高级进阶课程

课程介绍 本套视频教程主要内容包含React/Vue最新版本脚手架分析、基于Webpack5编写自己的loader和plugin等&#xff0c;让你开发时选择更多样&#xff0c;最后&#xff0c;用不到一百行的代码实现Webpack打包。通过本套视频教程的学习&#xff0c;可以帮你彻底打通Webpack的任…

mineadmin前端安装启动

在上一篇文章中&#xff0c; 我们已经搭建好了后端环境并启动 mineadmin 快速安装部署&#xff08;docker环境&#xff09; 一、下载前端项目 1、在搭建后端时候&#xff0c;使用php bin/hyperf.php mine:install 的时候&#xff0c;有一个步骤是安装前端项目的。安装目录为&a…

简介:网络数据中心和数字孪生系统融合

前言 云服务器是在云中提供可扩展的计算服务&#xff0c;避免了使用传统服务器时需要预估资源用量及前期投入的情况。云服务器支持用户自定义一切资源&#xff1a;cpu、内存、硬盘、网络、安全等等&#xff0c;并可在访问量和负载等需求发生变化时轻松地调整它们。云服务器为业…

Visio导出高质量图片

直接导出图片比较糊&#xff0c; 在导出高质量图片&#xff0c;应该 直接保存&#xff0c;然后弹出此选项&#xff0c;进行如下设置 即可导出高质量图片

php 快速入门(一)

一、配置系统环境 1.1 安装软件 1、安装php的开发软件&#xff1a;phpstorm 在这个软件中写代码 2、安装php的运行软件&#xff1a;phpstduy 写好的php程序需要放到phpstduy中&#xff0c;用户才能访问和测试 安装过程注意事项&#xff1a;安装的路径中不能有空格和中文字符&…

大东方保险集团陈志远:洞察保险行业的重要性及未来三年发展前景

在当今社会,保险行业作为风险管理的重要工具,正日益凸显其不可或缺的地位。大东方保险集团陈志远近日在接受采访时,深入探讨了保险行业的重要性以及未来三年的发展前景。 一、保险行业的重要性 陈志远指出,保险行业在现代经济中扮演着举足轻重的角色。它不仅是社会稳定的“减震…

LeetCode 1027——最长等差数列

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 假设我们以 f[d][nums[i]]表示以 nums[i] 为结尾元素间距为 d 的等差数列的最大长度&#xff0c;那么&#xff0c;如果 nums[i]-d 也存在于 nums 数组中&#xff0c;则有&#xff1a; f [ d ] [ n u m s [ i ] ] …

NSS [SWPUCTF 2022 新生赛]Power!

NSS [SWPUCTF 2022 新生赛]Power! 开题。 随便传一个111&#xff0c;后端进行了一个文件包含操作。 输入index.php&#xff0c;回显了一个不可显示图片。 有点小蒙蔽的&#xff0c;一般这种情况就源码&#xff0c;抓包&#xff0c;扫描。源码里面果然有货。 base解码后是index…

了解Spring:Java开发的利器

Spring是一款开源的轻量级Java开发框架&#xff0c;旨在提高开发人员的效率和系统的可维护性。本文将介绍Spring的基本概念、使用优势、设计模式以及与Spring MVC和Spring Boot的关联。 什么是Spring&#xff1f; Spring是一款开源的轻量级Java开发框架&#xff0c;它由多个模…

JAVA----进程

进程(process) 目录 进程(process)1. 进程--即一个**跑起来**的运用程序2. 进程 可视为是操作系统进行资源分配的基本单位3. 在操作系统中,通常使用称为 PCB 这样的结构体来描述进程的.4. PCB5. 文件描述符(重点)6. 进程调度(关键重点)1. PCB 提供了几个属性,支持 进程调度1. 状…

2024常用接口抓包以及接口测试工具总结【建议收藏】

接口 统称为API&#xff0c;程序与程序之间的对接、交接。 接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点&#xff0c;主要是为了检验不同组件&#xff08;模块&#xff09;之间数据的传递是否正确&#xff0c;同时接口测试还要测试当前系统与第三方…