Quartz定时任务框架
Quartz API,Jobs和Triggers_w3cschool;【学习链接】
参考:http://t.csdnimg.cn/AfVJR;
01-快速入门
1.1-Quartz 核心概念
我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。
Quartz API的关键接口是:
- Scheduler - 与调度程序交互的主要API。
- Job - 你想要调度器执行的任务组件需要实现的接口
- JobDetail - 用于定义作业的实例【这个JobDetail里其实就封装了我们自己实现Job接口的实现类(也就是
MyJob
类)】。 - Trigger(即触发器) - 定义执行给定作业的计划的组件【也就是定义了一些任务执行的策略,比如时间等】。
- JobBuilder - 用于定义/构建 JobDetail 实例,用于定义作业的实例。
- TriggerBuilder - 用于定义/构建触发器实例。
- Scheduler 的生命期,从 SchedulerFactory 创建它时开始,到 Scheduler 调用shutdown() 方法时结束;Scheduler 被创建后,可以增加、删除和列举 Job 和 Trigger,以及执行其它与调度相关的操作(如暂停 Trigger)。但是,Scheduler 只有在调用 start() 方法后,才会真正地触发 trigger(即执行 job)
其整体架构如下图
1.2-快速入门代码
1.先使用springboot脚手架创建一个springboot项目(不再赘述)
2.导入依赖
<--注意这样导入只使用与springboot2.0以上版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
3.定义一个自己的job类实现job接口
public class MyJob implements Job {
static int count = 0;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("任务进行" + count++ + "次");
}
}
4.编写测试类
@SpringBootTest
class QuartzApplicationTests {
@Test
void contextLoads() throws SchedulerException, InterruptedException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.getContext().put("skey", "svalue");
scheduler.start();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("1", "h").build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("1","h")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();
scheduler.scheduleJob(jobDetail, trigger);
Thread.sleep(600000);
scheduler.shutdown();
}
}
5.运行可以看到我们自己定义的MyJob
中的execute
方法被定时执行了
小总结:
- 构建一个Scheduler,Scheduler里会放置Trigger【存储了任务调度规则】和JobDetail【存储了任务的内容】,
- Trigger和JobDetail两个通过Key【JobKey和TriggerKey】设置组别和名字,通过Key就可以将Trigger和JobDetail关联起来,值得注意的是Trigger里既有JobKey又有TriggerKey
- JobDetail里放置了我们自己定义的Job的实现类
- 通过上述操作,这样Scheduler就可以通过Trigger的规则拿到对应的JobDetail,从而调用相对应的Job实现类。
02-Quartz核心详解
2.1-Job 和 JobDetail
Job和JobDetail的关系,JobDetail里包含了Job的Class对象,具体源码体现如下:
可以看到JobDetail里包含了一个jobClass的属性,该属性就是对应Job的Class对象
2.1.1-Job
从代码层面来说,Job就是一个接口,我们使用Quartz定义任务时,其实就是定义了一个实现Job接口的实现类,我们任务需要做的内容就是定义在Job实现类重写的execute方法中。Job的具体源码如下
-
**Job实例:**一个自己定义的Job实例可以创建多个JobDeatil
-
Job状态与并发:
- @DisallowConcurrentExecution:该注解放在我们自己定义的Job实现类上,告诉Quartz不能并发的执行这个Job实现类的多个JobDetail
- @PersistJobDataAfterExecution:将该注解加在job实现类上,告诉Quartz在成功执行了job实现类的execute方法后(没有发生任何异常),更新所有JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据
- 值得注意的是,使用了**@PersistJobDataAfterExecution注解后最好同时使用@DisallowConcurrentExecution**,因为当一个Job的两个实例在并发的执行后,如果有一个JobDetail的JobDateMap改变了,因为使用了**@DisallowConcurrentExecution**注解,所以会去更新所有JobDetail的JobDateMap,如果此时有其他JobDetail在并发的执行,并且要使用JobDateMap,就会出现数据不一致的问题。
-
Job的其他特性:
-
**Durability:**如果一个job是非持久的,当没有活跃的trigger【意思就是一个Job关联的所有Trigger都不会被执行了】与之关联的时候,会被自动地从scheduler中删除。也就是说,非持久的job的生命期是由trigger的存在与否决定的;
该属性通过如下代码设置:
JobDetail jobDetail = JobBuilder.newJob(DumbJob.class) .storeDurably(true) //表示开启持久化 .build();
-
**RequestsRecovery:**如果一个job是可恢复的,并且在其执行的时候,scheduler发生硬关闭(hard shutdown)(比如运行的进程崩溃了,或者关机了),则当scheduler重新启动的时候,该job会被重新执行。此时,该job的JobExecutionContext.isRecovering() 返回true。
该属性通过如下代码设置:
JobDetail jobDetail = JobBuilder.newJob(DumbJob.class) .requestRecovery(true) //开启可恢复
-
2.1.2-JobDetail
JobDetail从代码上来说也是一个接口,我们创建Schedule之后,会将JobDeTail和Trigger放入到Schedule中,其中JobDetail就是为了方便Schedule调用我们自定义的Job提供了一系列的方法,方便将Job加入到任务调度器中,以及方便Schedule对我们定义的Job进行管理。JobDetail接口的源码如下:
2.2-Trigger
介绍:Trigger主要是用来定义任务调度规则的
2.2.1-Trigger的公共属性
所有类型的Trigger都有TriggerKey这个属性,表示Trigger的身份
Ttrigger的公共属性有:
- jobKey属性:当trigger触发时被执行的job的身份;
- startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。
- endTime属性:表示trigger失效的时间点。比如,”每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。
其它的属性,会在下文中解释。
2.2.2-Trigger的优先级
比如,你有N个trigger需要同时触发,但Quartz只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值可以是任意整数,正数、负数都可以,优先级的范围为1-10,优先级最高的为10。
在代码层面通过withPriority(int)方法进行设置
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withPriority(6)
.build()
我们进入该方法的内部查看,其实是设置了TriggerBuilder的priority
我们再进入到TriggerBuilder的build()方法中查看,发现在构建器构建Trigger时将优先级给了对应的Trigger
2.2.3-日历示例(calendar)
2.2.4-Simple Trigger和CronTrigger
简单介绍一下这两者的区别主要是:CronTrigger可以定义指定的日历进行任务规则调度,Simple Trigger不能指定日历进行任务规则调度,主要用于一些简单的日历调度。具体的使用可以在使用的时候参考API。
2.3-Listeners监听器
2.3.1-JobListener
概述:Listener就是用来监听相对应的Job和Trigger的,我们可以点进去看一下JobListener的接口,包含了一下主要方法
- 监听器名称:通过
getName()
方法,可以为每个监听器实例设置一个唯一的名称,以便在Quartz中进行标识和管理。 - Job执行前监听:
jobToBeExecuted()
方法允许监听器在Job执行之前进行一些预处理操作,例如记录日志、获取执行前的状态等。 - Job执行被否决监听:
jobExecutionVetoed()
方法用于处理Job被否决执行的情况,可以在此方法中执行一些清理或记录操作。 - Job执行后监听:
jobWasExecuted()
方法用于处理Job执行完成后的事件,不论是成功完成还是出现异常。可以在此方法中处理执行后的清理工作、记录执行日志等。
关于执行前监听和执行后监听很好理解,我们着重介绍一下Job执行本否决是什么意思:
-
TriggerListener 中的否决决策:
- 在与Trigger关联的TriggerListener中,可以通过实现
vetoJobExecution()
方法来决定是否否决Job的执行。如果一个TriggerListener否决了Job的执行,那么调度器会通知所有相关的JobListener,并且不会执行这个Job。
- 在与Trigger关联的TriggerListener中,可以通过实现
-
JobListener 中的 jobExecutionVetoed() 方法:
- 在实现了
JobListener
接口的监听器中,如果Job被触发器触发时被否决执行,即没有实际执行Job的情况下,jobExecutionVetoed(JobExecutionContext context)
方法会被调用。这种情况通常源于其他监听器或Quartz内部的决策,例如另一个TriggerListener中调用了vetoJobExecution()
方法。
- 在实现了
代码示例:
1.创建JobListener
/**
* @author hanLin.liu
* @create 2024-08-07 10:25
*/
@Component
public class MyJobListener implements JobListener {
@Override
public String getName() {
return "myJobListener";
}
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
System.out.println("即将执行");
}
@Override
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
System.out.println("执行被否决");
}
@Override
public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
System.out.println("执行完成");
}
}
2.在创建完Listener之后,我们需将这个Listener加入到schedule中
// 创建一个schedule
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//将Listener加入到schedule中
scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("liu"));
2.3.2-TriggerListener
我们点击去TriggerListener接口,可以看到有以下方法
接下来我们来解释一些各个方法的作用
- getName():
- 作用:返回监听器的名称。
- 用途:标识这个监听器实例,便于在配置和日志中识别。
- triggerFired(Trigger var1, JobExecutionContext var2):
- 作用:在与Trigger关联的作业(Job)被调度执行时触发。
- 参数:
var1
:触发该事件的Trigger对象。var2
:当前JobExecutionContext对象,包含Job执行的上下文信息。
- 用途:通常用于在Job实际执行之前执行一些准备工作或记录日志信息。
- vetoJobExecution(Trigger var1, JobExecutionContext var2):
- 作用:决定是否否决(阻止)与Trigger关联的作业的执行。
- 参数:
var1
:触发该事件的Trigger对象。var2
:当前JobExecutionContext对象,包含Job执行的上下文信息。
- 返回值:
true
表示否决Job的执行,false
表示允许Job的执行。 - 用途:根据业务逻辑或条件来决定是否执行Job。如果返回
true
,Quartz将不会执行该Job,而是直接跳过。
- triggerMisfired(Trigger var1):
- 作用:在Trigger错过触发时触发。
- 参数:
var1
:触发该事件的Trigger对象。
- 用途:处理Trigger未能在预定时间触发的情况,可以执行相应的补救措施或记录日志。
- triggerComplete(Trigger var1, JobExecutionContext var2, CompletedExecutionInstruction var3):
- 作用:在与Trigger关联的作业执行完成时触发。
- 参数:
var1
:触发该事件的Trigger对象。var2
:当前JobExecutionContext对象,包含Job执行的上下文信息。var3
:作业执行完成后的指令(例如,是否应删除Trigger等)。
- 用途:处理Job执行完成后的清理工作或记录日志。可以根据
var3
参数的值来决定是否需要采取进一步的操作。
代码示例和JobListener类似,这里我们就不再赘述了。
2.3.3-SchedulerListeners
具体的接口如下:
具体接口的作用就不再赘述了,其使用也和TriggerListener和JobListener一样的