一、Quartz介绍
Quartz [kwɔːts] 是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:
1.持久性作业 - 就是保持调度定时的状态;
2.作业管理 - 对调度作业进行有效的管理;
Quartz是一个强大任务调度框架,可以用来干嘛?
简单来说就是实现“计划(或定时)任务”的系统,例如:订单下单后未付款,15分钟后自动撤消订单,并自动解锁锁定的商品;一个OA系统需要在每周五9点自动生成数据报表;或者想每月10号自动还款;又或者每周给暗恋的女生定时发送邮件等等。
二、Quartz的核心概念
三大核心类 JObDetail(作业类),Trigger(触发器),Scheduler(调度器)。Trigger指定JObDetail什么时候发布任务。
1,任务job
job就是你想实现的任务类,每一个job必须实现org.quartz.job接口,且只需实现接口定义的execute()方法。
Job:工作任务调度的接口,任务类需要实现该接口,该接口中定义execute方法,类似jdk提供的TimeTask类的run方法,在里面编写任务执行的业务逻辑。
Job:实例在Quartz中的生命周期,每次调度器执行job时它在调用execute方法前,会创建一个新的job实例,当调用完成后,关联的job对象实例会被是释放,释放的实例会被垃圾回收机制回收。
2,触发器Trigger
Trigger 为你执行任务的触发器,比如你想每天定时1点发送邮件,Trigger将会设置1点执行该任务。
Trigger主要包含两种:SimpleTrigger和CronTriggerr。
3,调度器Scheduler
Scheduler是任务的调度器,会将任务job和触发器TRigger结合,负责基于Trigger设定的时间执行job。
三、Quartz的几个常用API
Scheduler :用于与调度程序交互的主程序接口。
Job :预先定义的希望在未来时间被调度程序执行的任务类,自定义。
JobDetall :使用JobDetail来定义定时任务的实例,JobDetail实例是通过JobBuilder类创建。
JobDataMap :可包含数据对象,在job实例执行的是好,可使用包含的数据;JobDataMap是java Map接口的实现,增加了一些存取基本类型方法。
Trgger触发器 :Trigger对象是用于触发执行Job的,当调度一个Job时,我们实例一个触发器然后调整它的属性来满足Job执行的条件,表明任务在什么时候执行。定义了一个已经被安排的任务将在什么时候执行的时间条件,比如每秒执行一次。
JobBuilder :用于声明一个任务实例,也可以定义关于该任务的详情比如:任务名,组名等,这个声明的实例将作为一个实例执行的任务。
TriggerBuilder :触发器创建器,用于创建触发器trigger实例。
JobListener,TriggerListener,SchedulerListener监听器,用于对组件的监听。
四、Quartz的简单使用
运行程序,可以看到程序每隔1s会打印出内容,且在12s后程序结束。
创建项目并加入依赖,参考:【普通的IDEA maven java项目demo(hello word)-1.8】待更新CSDN链接
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
新建一个能够打印任意内容的Job:
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public class PrintWordsJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) {
String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss:SSS").format(new Date());
System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));
}
}
创建Schedule,执行任务:
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Demo {
public static void main(String[] args) throws SchedulerException, InterruptedException {
// 1、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class)
.withIdentity("job1", "group1").build();
// 2、构建Trigger实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.startNow()// 立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)// 每隔1s执行一次
.repeatForever()).build();// 一直执行
// 3、创建调度器Scheduler并执行
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
System.out.println("--------scheduler start ! ------------");
System.out.println("at:" + new SimpleDateFormat("yy-MM-dd HH-mm-ss:SSS").format(new Date()) + ", prints: Hello scheduler");
scheduler.start();
// 睡眠12秒
TimeUnit.MILLISECONDS.sleep(12000);
scheduler.shutdown();
System.out.println("at:" + new SimpleDateFormat("yy-MM-dd HH-mm-ss:SSS").format(new Date()) + ", prints: Hello scheduler");
System.out.println("--------scheduler shutdown ! ------------");
}
}
运行结果(注意看时间):
--------scheduler start ! ------------
at:23-08-22 00-05-01:123, prints: Hello scheduler
PrintWordsJob start at:23-08-22 00-05-01:129, prints: Hello Job-69
PrintWordsJob start at:23-08-22 00-05-02:052, prints: Hello Job-68
PrintWordsJob start at:23-08-22 00-05-03:057, prints: Hello Job-93
PrintWordsJob start at:23-08-22 00-05-04:061, prints: Hello Job-32
PrintWordsJob start at:23-08-22 00-05-05:057, prints: Hello Job-14
PrintWordsJob start at:23-08-22 00-05-06:051, prints: Hello Job-55
PrintWordsJob start at:23-08-22 00-05-07:058, prints: Hello Job-30
PrintWordsJob start at:23-08-22 00-05-08:048, prints: Hello Job-82
PrintWordsJob start at:23-08-22 00-05-09:058, prints: Hello Job-28
PrintWordsJob start at:23-08-22 00-05-10:059, prints: Hello Job-97
PrintWordsJob start at:23-08-22 00-05-11:053, prints: Hello Job-88
PrintWordsJob start at:23-08-22 00-05-12:048, prints: Hello Job-18
PrintWordsJob start at:23-08-22 00-05-13:057, prints: Hello Job-93
at:23-08-22 00-05-13:135, prints: Hello scheduler
--------scheduler shutdown ! ------------
五、Quartz核心详解
1.Job和JobDetail
Job是Quartz中的一个接口,接口下只有execute方法,在这个方法中编写业务逻辑。
JobDetail用来绑定Job,为Job实例提供许多属性:name、group、jobClass、jobDataMap
JobDetail绑定指定的Job,每次Scheduler调度执行一个Job的时候,首先会拿到对应的Job,然后创建该Job实例,再去执行Job中的execute()的内容,任务执行结束后,关联的Job对象实例会被释放,且会被JVM GC清除。
为什么设计成JobDetail + Job,不直接使用Job?
JobDetail定义的是任务数据,而真正的执行逻辑是在Job中。 这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,Sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。
2.Trigger、SimpleTrigger、CronTrigger
Trigger
Trigger是Quartz的触发器,会去通知Scheduler何时去执行对应Job。
new Trigger().startAt():表示触发器首次被触发的时间;
new Trigger().endAt():表示触发器结束触发的时间;
SimpleTrigger
SimpleTrigger可以实现在一个指定时间段内执行一次作业任务或一个时间段内多次执行作业任务。
将下述代码替换上述【Quartz的简单使用】代码的 // 2、构建Trigger实例,每隔1s执行一次 内容
程序运行5s后开始执行Job,执行Job 5s后,再延时2s结束程序:
// 2、构建Trigger实例,每隔1s执行一次
Date startDate = new Date();
startDate.setTime(startDate.getTime() + 5000);
Date endDate = new Date();
endDate.setTime(startDate.getTime() + 5000);
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.usingJobData("trigger1", "这是jobDetail1的trigger")
.startNow()//立即生效
.startAt(startDate)
.endAt(endDate)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)// 每隔1s执行一次
.repeatForever()).build();// 一直执行
运行结果(注意看时间):
--------scheduler start ! ------------
at:23-08-22 00-08-34:658, prints: Hello scheduler
PrintWordsJob start at:23-08-22 00-08-39:573, prints: Hello Job-81
PrintWordsJob start at:23-08-22 00-08-40:553, prints: Hello Job-63
PrintWordsJob start at:23-08-22 00-08-41:560, prints: Hello Job-87
PrintWordsJob start at:23-08-22 00-08-42:562, prints: Hello Job-25
PrintWordsJob start at:23-08-22 00-08-43:554, prints: Hello Job-65
at:23-08-22 00-08-46:666, prints: Hello scheduler
--------scheduler shutdown ! ------------
CronTrigger
CronTrigger功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用。CroTrigger是基于Cron表达式的,了解Cron表达式可参考:【cron表达式 详解】cron表达式 详解_linux cron表达式_西晋的no1的博客-CSDN博客
在线生成corn表达式: 在线Cron表达式生成器
将下述代码替换上述【Quartz的简单使用】代码的 // 2、构建Trigger实例,每隔1s执行一次 内容
从0秒开始,每5秒执行一次定时任务
// 2.触发器
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "group").startNow()//立刻执行
.usingJobData("trigger1", "这是jobDetail1的trigger")
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? *"))//表示每次0秒时候执行。
.build();
运行结果(注意看时间):
--------scheduler start ! ------------
at:23-08-22 00-21-35:870, prints: Hello scheduler
PrintWordsJob start at:23-08-22 00-21-35:877, prints: Hello Job-39
PrintWordsJob start at:23-08-22 00-21-40:001, prints: Hello Job-68
PrintWordsJob start at:23-08-22 00-21-45:002, prints: Hello Job-8
at:23-08-22 00-21-47:873, prints: Hello scheduler
--------scheduler shutdown ! ------------
六、JobListener
创建MyJobListener实现JobListener接口
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
public class MyJobListener implements JobListener {
public String getName() {
return this.getClass().getSimpleName();
}
//Scheduler在jobDetail将要被执行时调用这个方法(执行前)
public void jobToBeExecuted(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("我的job名1:" + jobName);
}
//Scheduler在jobDetail即将被执行,但又被TriggerListermer 否定时会调用该方法
public void jobExecutionVetoed(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("我的job名2:" + jobName);
}
//Scheduler在jobDetail即将被执行之后调用这个方法。(执行后)
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("我的job名3:" + jobName);
}
}
在scheduler创建后加入监听即可生效
scheduler.getListenerManager().addJobListener(new MyJobListener());
将下述蓝色代码放于上述【Quartz的简单使用】对应位置
// 3、创建调度器Scheduler并执行
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.getListenerManager().addJobListener(new MyJobListener());
scheduler.scheduleJob(jobDetail, trigger);
运行结果(注意看时间):
--------scheduler start ! ------------
at:23-08-22 00-28-12:174, prints: Hello scheduler
我的job名1:job1
PrintWordsJob start at:23-08-22 00-28-12:179, prints: Hello Job-7
我的job名3:job1
我的job名1:job1
PrintWordsJob start at:23-08-22 00-28-13:112, prints: Hello Job-39
我的job名3:job1
我的job名1:job1
…
…
…
PrintWordsJob start at:23-08-22 00-28-23:111, prints: Hello Job-0
我的job名3:job1
我的job名1:job1
PrintWordsJob start at:23-08-22 00-28-24:115, prints: Hello Job-12
我的job名3:job1
at:23-08-22 00-28-24:179, prints: Hello scheduler
--------scheduler shutdown ! ------------
七、TriggerListener
任务调度过程中,与触发器Trigger相关的事件包括:触发器触发,触发器未正常触发,触发器完成等。
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
public class MyTriggerListener implements TriggerListener {
//用于获取触发器的名称
public String getName() {//获取默认类名
return this.getClass().getSimpleName();
}
//当与监听器相关联的trigger被触发,job上的execute()方法将被执行时,Scheduler就调用该方法
public void triggerFired(Trigger trigger, JobExecutionContext context) {
System.out.println("triggerFired");
}
//在Trigger触发后,job将要被执行时由Scheduler调用这个方法。
//TriggerListener给一个选择去否决job的执行。如方法返回true,job此次将不会为trigger触发执行。false,放行。
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
System.out.println("vetoJobExecution");
return false;
}
//Scheduler 调用这个方法是在trigger错过时触发。
public void triggerMisfired(Trigger trigger) {
System.out.println("triggerMisfired");
}
//triggerComplete:trigger被触发并且完成了job的执行时,Scheduler调用这个方法。
public void triggerComplete(Trigger trigger, JobExecutionContext context,
Trigger.CompletedExecutionInstruction triggerInstructionCode) {
System.out.println("triggerComplete");
}
}
将下述蓝色代码放于上述【Quartz的简单使用】对应位置
// 3、创建调度器Scheduler并执行
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());
scheduler.scheduleJob(jobDetail, trigger);
运行结果(注意看时间):
--------scheduler start ! ------------
at:23-08-22 00-34-56:592, prints: Hello scheduler
triggerFired
vetoJobExecution
PrintWordsJob start at:23-08-22 00-34-56:601, prints: Hello Job-69
triggerComplete
triggerFired
vetoJobExecution
…
…
…
PrintWordsJob start at:23-08-22 00-35-07:530, prints: Hello Job-13
triggerComplete
triggerFired
vetoJobExecution
PrintWordsJob start at:23-08-22 00-35-08:535, prints: Hello Job-21
triggerComplete
at:23-08-22 00-35-08:597, prints: Hello scheduler
--------scheduler shutdown ! ------------
八、SchedulerListener
SchedulerListener会在scheduler的生命周期中关键事件发生时被调用,与Scheduler有关事件;增加或者删除一个 job/trigger,关闭scheduler等。
import org.quartz.*;
public class MySchedulerListener implements SchedulerListener {
//用于部署JobDetail 的时候调用
public void jobScheduled(Trigger trigger) {
String name = trigger.getKey().getName();
System.out.println("获取触发器名称:" + name);
}
//卸载JobDetail 的时候调用
public void jobUnscheduled(TriggerKey triggerKey) {
System.out.println(triggerKey.getName());
}
//当trigger来到再也不会触发的时候调用这个方法
public void triggerFinalized(Trigger trigger) {
String name = trigger.getKey().getName();
System.out.println("获取触发器名称:" + name);
}
//当trigger 被暂停时候调用
public void triggerPaused(TriggerKey triggerKey) {
System.out.println("被暂停1");
}
//当trigger组 被暂停时候调用
public void triggersPaused(String triggerGroup) {
System.out.println("被暂停2");
}
///当trigger从 被暂停 到恢复 时候 调用
public void triggerResumed(TriggerKey triggerKey) {
System.out.println("恢复");
}
///当trigger组从 被暂停 到恢复 时候 调用
public void triggersResumed(String triggerGroup) {
System.out.println("恢复");
}
//添加工作任务调用
public void jobAdded(JobDetail jobDetail) {
System.out.println("添加工作任务");
}
//删除工作任务
public void jobDeleted(JobKey jobKey) {
System.out.println("删除工作任务");
}
public void jobPaused(JobKey jobKey) {
// TODO Auto-generated method stub
}
public void jobsPaused(String jobGroup) {
// TODO Auto-generated method stub
}
public void jobResumed(JobKey jobKey) {
// TODO Auto-generated method stub
}
public void jobsResumed(String jobGroup) {
// TODO Auto-generated method stub
}
//scheduler产生Error调用
public void schedulerError(String msg, SchedulerException cause) {
// TODO Auto-generated method stub
}
//scheduler被挂起时候调用
public void schedulerInStandbyMode() {
// TODO Auto-generated method stub
}
//scheduler开启的时候调用
public void schedulerStarted() {
System.out.println("scheduler 开启 的时候调用");
}
//scheduler 正在开启的时候调用 ing.....
public void schedulerStarting() {
System.out.println("scheduler 正在开启的时候调用 ing.....");
}
// scheduler关闭 的时候调用
public void schedulerShutdown() {
System.out.println("scheduler关闭 的时候调用");
}
//scheduler 正在关闭的时候调用 ing.....
public void schedulerShuttingdown() {
System.out.println("//scheduler 正在关闭的时候调用 ing.....");
}
//scheduler 数据被清除了的时候调用
public void schedulingDataCleared() {
System.out.println("//scheduler 数据被清除了的时候调用");
}
}
将下述蓝色代码放于上述【Quartz的简单使用】对应位置
// 3、创建调度器Scheduler并执行
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());
scheduler.scheduleJob(jobDetail, trigger);
运行结果(注意看时间):
添加工作任务
获取触发器名称:trigger1
--------scheduler start ! ------------
at:23-08-22 00-37-43:391, prints: Hello scheduler
scheduler 正在开启的时候调用 ing.....
scheduler 开启 的时候调用
PrintWordsJob start at:23-08-22 00-37-43:395, prints: Hello Job-74
PrintWordsJob start at:23-08-22 00-37-44:294, prints: Hello Job-62
PrintWordsJob start at:23-08-22 00-37-45:301, prints: Hello Job-84
PrintWordsJob start at:23-08-22 00-37-46:292, prints: Hello Job-19
PrintWordsJob start at:23-08-22 00-37-47:301, prints: Hello Job-82
PrintWordsJob start at:23-08-22 00-37-48:288, prints: Hello Job-42
PrintWordsJob start at:23-08-22 00-37-49:296, prints: Hello Job-19
PrintWordsJob start at:23-08-22 00-37-50:298, prints: Hello Job-4
PrintWordsJob start at:23-08-22 00-37-51:290, prints: Hello Job-10
PrintWordsJob start at:23-08-22 00-37-52:294, prints: Hello Job-78
PrintWordsJob start at:23-08-22 00-37-53:292, prints: Hello Job-42
PrintWordsJob start at:23-08-22 00-37-54:298, prints: Hello Job-49
PrintWordsJob start at:23-08-22 00-37-55:291, prints: Hello Job-13
//scheduler 正在关闭的时候调用 ing.....
scheduler关闭 的时候调用
at:23-08-22 00-37-55:397, prints: Hello scheduler
--------scheduler shutdown ! ------------
九、定时任务参数传递问题
将下述蓝色代码放于上述【Quartz的简单使用】1和2之间的位置
// 1、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class)
.withIdentity("job1", "group1").build();
// 传参
JobDataMap jobDataMap=jobDetail.getJobDataMap();
jobDataMap.put("name","传参test");
jobDataMap.put("age",11);
jobDataMap.put("sex","男");
// 2、构建Trigger实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.startNow()// 立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)// 每隔1s执行一次
.repeatForever()).build();// 一直执行
同时更新PrintWordsJob.java文件中的代码为以下内容:
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public class PrintWordsJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) {
String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss:SSS").format(new Date());
System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
System.out.println(jobDataMap.get("name").toString() + ":" + jobDataMap.get("age").toString() +":"+ jobDataMap.get("sex").toString());
}
}
运行结果(注意看时间):
--------scheduler start ! ------------
at:23-08-22 01-00-24:165, prints: Hello scheduler
PrintWordsJob start at:23-08-22 01-00-24:176, prints: Hello Job-6
传参test:11:男
PrintWordsJob start at:23-08-22 01-00-25:114, prints: Hello Job-70
传参test:11:男
PrintWordsJob start at:23-08-22 01-00-26:106, prints: Hello Job-54
传参test:11:男
…
…
…
PrintWordsJob start at:23-08-22 01-00-36:104, prints: Hello Job-73
传参test:11:男
at:23-08-22 01-00-36:181, prints: Hello scheduler
--------scheduler shutdown ! ------------
参考资料:
1. https://blog.csdn.net/faramita_of_mine/article/details/123142384?ops_request_misc=&request_id=&biz_id=102&utm_term=quartz&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-6-123142384.142^v93^chatgptT3_2&spm=1018.2226.3001.4187
2. https://blog.csdn.net/yoonbongchi/article/details/110579024?ops_request_misc=&request_id=&biz_id=102&utm_term=quartz&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-110579024.142^v93^chatgptT3_2&spm=1018.2226.3001.4187