-
- 什么是Quartz
Quartz是一个开源的任务调度框架。作用是基于定时、定期的策略来执行任务。
它是OpenSymphony开源组织在Job scheduling领域又一个开源项目。
“任务进度管理器”就是一个在预先被纳入日程,当时间到达时,负责执行(或者通知)其他软件组件的系统。 简单来说就是实现“计划(或定时)任务”的系统,例如:订单下单后未付款,15分钟后自动撤消订单,并自动解锁锁定的商品。
官网:Quartz Enterprise Job Scheduler
参考:GitHub - dufyun/quartz-core-learning: 关于Quartz技术的学习积累的例子,开始Quartz学习之旅,精进Quartz
-
- Quartz可以用来做什么?
Quartz是一个任务调度框架。比如你遇到这样的问题:
- 想每月11号自动提示信用卡还款金额;每月28号,信用卡自动还款。
- 订单下单后未付款,15分钟后自动撤消订单,并自动解锁锁定的商品。
- 想每年4月1日自己给当年暗恋女神发一封匿名贺卡。
- 想每隔1小时,备份一下自己的爱情动作片、学习笔记到云盘。
- 医院系统11点才能开放挂号。
- 企业中如每天凌晨2点触发数据同步、发送Email等操作。
- ……
这些问题总结起来就是:在某一个有规律的时间点干某件事。并且时间的触发的条件可以非常复杂(比如每月最后一个工作日的17:50),复杂到需要一个专门的框架来干这个事。Quartz就是来干这样的事,你给它一个触发条件的定义,它负责到了时间点,触发相应的Job起来干活。
-
- 同类型框架有哪些?
TimeTask:在Quartz前还是显得过于简单、不完善,不能直接满足开发者的较为复杂的应用场景。
Elastic-Job:当当网推出的分布式定时任务框架。
TBSchedule:Alibaba开发的分布式定时任务框架。
- Quartz的3个核心要素
- 调度器(Scheduler)
一个Quartz的独立运行容器。
Scheduler是Quartz的核心,它会将JobDetail及Trigger整合起来,负责基于Trigger设定的触发条件来执行JobDetail中的Job。
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start(); //启动
scheduler.shutdown(); //关闭
注1:Quartz的Scheduler可直接实现自动装配。
注2:调度器操作:启动、暂停、继续、停止、关闭操作。
-
- 任务(Job)
- Job
- 任务(Job)
Job是一个接口,有一个方法 void execute()。
/**
* 任务类:具体要执行的任务
* com.example.job.MyJob
*/
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//编写我们自己的业务逻辑(任务)
System.out.println("烤10串鱿鱼须");
}
}
-
-
- JobDetail
-
JobDetail用来装载Job的,可以看成是Job的一个清单。
Quartz每次执行job时,都重新创建一个Job实例,会接收一个Job实现类,以便运行的时候通过newInstance()的反射调用机制去实例化Job.JobDetail是用来描述Job实现类以及相关静态信息
JobDetail为Job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例。
JobDetail jobDetail = newJob(HelloJob.class) .withIdentity("helloJob", "group1") .build(); |
JobDetail重要属性:name、group、jobClass、jobDataMap
// 通过JobBuilder构建JobDetail实例 JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) // 接受一个Job实例 .withDescription("Job实例的描述") .withIdentity("helloJob", "group1") // Job名/组名 .usingJobData("name", "admin") // Job实例的属性name=”admin” .build(); |
其它操作:
jobDetail.getKey().getName(); //获得JobDetail中Job的名称
jobDetail.getKey().getGroup(); //获得JobDetail中Job的组名
jobDetail.getJobClass().getName();
-
- 触发器(Trigger)
Trigger为执行任务Job的触发条件。
Quartz提供的5种触发器类型:
- SimpleTrigger 简单触发器(或频率触发器)
- CronTirgger 表达式(定时)触发器(或日历触发器、七子触发器)
- DateIntervalTrigger
- NthIncludedDayTrigger
- Calendar类(org.quartz.Calendar)
-
- SimpleTrigger简单触发器
-
作用:执行N次,重复N次。
指定从某一个时间开始,以一定的时间间隔(单位是毫秒)执行的任务。
它适合的任务类似于:9:00 开始,每隔1小时,执行一次。
属性:
- repeatInterval 重复间隔
- repeatCount 重复次数。实际执行次数是 repeatCount+1。因为在startTime的时候一定会执行一次。** 下面有关repeatCount 属性的都是同理。 **
SimpleTrigger 内部实现机制是通过计算间隔时间来计算下次的执行时间,这就导致其不适合调度定时的任务。例如我们想每天的 1:00AM 执行任务,如果使用 SimpleTrigger 的话间隔时间就是一天。注意这里就会有一个问题,即当有 misfired 的任务并且恢复执行时,该执行时间是随机的(取决于何时执行 misfired 的任务,例如某天的 3:00PM)。这会导致之后每天的执行时间都会变成 3:00PM,而不是我们原来期望的 1:00AM。
-
-
- CronTrigger表达式触发器(常用)
-
作用:几时几分几秒,哪天/哪月/哪年,执行,适合于更复杂的任务。
类似于SimpleTrigger,指定从某一个时间开始,以一定的时间间隔执行的任务。 但是不同的是SimpleTrigger指定的时间间隔为毫秒,没办法指定每隔一个月执行一次(每月的时间间隔不是固定值),而CalendarIntervalTrigger支持的间隔单位有秒,分钟,小时,天,月,年,星期。
相较于SimpleTrigger有两个优势:
- 更方便,比如每隔1小时执行,你不用自己去计算1小时等于多少毫秒。
- 支持不是固定长度的间隔,比如间隔为月和年。但劣势是精度只能到秒。
它适合的任务类似于:9:00 开始执行,并且以后每周 9:00 执行一次
属性:
- interval 执行间隔
- intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,星期,年)
类似于 LINUX 上的任务调度命令 crontab,即利用一个包含 7 个字段的表达式来表示时间调度方式。例如,"0 15 10 * * ? *" 表示每天的 10:15AM 执行任务。对于涉及到星期和月份的调度,CronTirgger 是最适合的,甚至某些情况下是唯一选择。例如,"0 10 14 ? 3 WED" 表示三月份的每个星期三的下午 14:10PM 执行任务。
CronExpression表达式(重点、难点):
由左到右按顺序代表: * * * * * * *
格式: [秒] [分] [小时] [日] [月] [周] [年]
七个部分,都可以用*来表示每X。周可以省略不写
-
-
- DailyTimeIntervalTrigger 触发器
-
作用:指定每天的某个时间段内,以一定的时间间隔执行任务。并且它可以支持指定星期。
适合的任务类似于:指定每天9:00 至 18:00 ,每隔70秒执行一次,并且只要周一至周五执行。
它的属性有:
- startTimeOfDay 每天开始时间
- endTimeOfDay 每天结束时间
- daysOfWeek 需要执行的星期
- interval 执行间隔
- intervalUnit 执行间隔的单位(秒,分钟,小时,天,月,年,星期)
- repeatCount 重复次数
例子:
dailyTimeIntervalSchedule() .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) //每天9:00开始 .endingDailyAt(TimeOfDay.hourAndMinuteOfDay(16, 0)) //16:00 结束 .onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) //周一至周五执行 .withIntervalInHours(1) //每间隔1小时执行一次 .withRepeatCount(100) //最多重复100次(实际执行100+1次) .build(); dailyTimeIntervalSchedule() .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) //第天9:00开始 .endingDailyAfterCount(10) //每天执行10次,这个方法实际上根据 startTimeOfDay+interval*count 算出 endTimeOfDay .onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) //周一至周五执行 .withIntervalInHours(1) //每间隔1小时执行一次 .build(); |
- 任务状态
quartz包中org.quartz.Trigger接口有默认任务状态:
public static enum TriggerState {
NONE,
NORMAL,
PAUSED,
COMPLETE,
ERROR,
BLOCKED;
private TriggerState() {
}
}
PAUSED 暂停 code:1
BLOCKED 阻塞 code:4
ERROR 错误 code:3
NONE 不存在 code:-1
NORMAL 正常 code:0
COMPLETE 完成 code:2
- 入门案例
- 导包
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.0</version>
</dependency>
-
- 创建任务类
创建任务类,实现Job接口,重写execute()方法。
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//获取context上下文中的JobDetail对象
JobDetail jobDetail = context.getJobDetail();
//获取JobDetail对象中name属性的值
String name = jobDetail.getJobDataMap().getString("name");
System.out.println(name);
}
}
注1:关注execute()方法中的context上下文参数。
注2:execute()方法主要用于根据业务需求编写业务逻辑代码,比如:发短信。
-
- 测试
@Test
public void test1(){
try {
//1.定义/构建 JobDetail 实例,用于定义作业的实例
// 将HelloJob类添加到JobDetail清单中
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("任务名", "任务组名")
.build();
//2.构建Trigger实例
//Date startTime = new Date(System.currentTimeMillis() + 3*1000L); 3秒后启动任务
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("触发器名", "触发器组名")
//立即启动(默认),如设为某一时间再启动使用:.startAt(statTime)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))
.build();
//3.创建scheduler调度器(Springboot可自动装配scheduler)
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail, trigger);
//4.启动调度器
scheduler.start();
//模拟运行一段时间后关闭,真实项目不要写
Thread.sleep(20000);
//5.关闭任务调度
scheduler.shutdown(true);
} catch (Exception e) {
e.printStackTrace();
}
}
问题1:为什么设计成JobDetail + Job,不直接使用Job?
这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。
- Quartz体系结构
每个定时任务都是一个线程,这些线程都在quartz的线程池中。
第1步:Job和Trigger必须先要注册在Scheduler中。
第2步:Scheduler调用注册的Trigger。
第3步:Trigger被唤醒,调用Job,开始执行。
一个job可以被多个Trigger 绑定,但是一个Trigger只能绑定一个job!
API:
- Scheduler用于与调度程序交互的主程序接口。
Scheduler调度程序-任务执行计划表,只有安排进执行计划的任务Job(通过Scheduler.scheduleJob),当它预先定义的执行时间到了的时候(任务触发Trigger),该任务才会执行。
- Job我们预先定义的希望在未来时间能被调度程序执行的任务类,我们可以自定义。
- JobDetail使用JobDetail来定义定时任务的实例,JobDetail实例是通过JobBuilder类创建的。
- JobDataMap可以包含不限量的(序列化的)数据对象,在Job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便 于存取基本类型的数据的方法。
- Trigger触发器,Trigger对象是用来触发执行job的。当调度一个job时,我们实例一个触发器然后调整它的属性来满足job执行的条件。表明任务在什么时候会执行。定义了一个已经被安排的任务将会在什么时候执行的时间条件,比如每2秒就执行一次。
- JobBuilder用于声明一个任务实例,也可以定义关于该任务的详情,比如任务名、组名等,这个声明的实例将会作为一个实际执行的任务。
- TriggerBuilder触发器创建器,用于创建触发器trigger实例。
- JobListener、TriggerListener、SchedulerListener监听器,用于对组件的监听。