- 一、Quartz
- 二、Quartz 运行环境
- 三、Quartz 核心组件
- 四、Quartz 设计模式
- 五、Quartz 的体系结构
- 六、Quartz 的几个常用 API
- 七、Quartz 入门案例
- 八、Quartz组件详情
- 8.1 Job
- 8.2 JobDetail
- 8.3 任务的形参 JobExecutionContext
- 8.4 任务的数据传递 JobDataMap
- 8.5 有状态的Job和无状态的Job
- 8.5 Trigger
- 8.5.1 SimpleTrigger 触发器 使用案例
- 8.5.2 CronTrigger 触发器
- 九、Quartz 任务调度
- 9.1 配置、资源 SchedulerFactory
- 9.2 Scheduler的两种创建方式
- 9.2.1 StdSchedulerFacotry
- 9.2.2 DirectSchedulerFacotry(了解)
- 9.3 Quartz 配置文件详解
- 9.3.1 自定义 quartz.properties
- 9.3.2 代码配置
- 十、Quartz 监听器
- 10.1 JobListener
- 10.2 TriggerListener
- 10.3 SchedulerListener
参考资料: 视频资料 一、Quartz
Quartz 官网:scheduler.org
Quartz 是 OpenSymphon 开源组织发布的开源项目,可以和J2EE 和 J2SE 应用程序相结合,也可以单独使用。
Quartz 是开源且具有丰富特性的“任务调度库”,能够集成任何的Java应用,小到独立的应用,大到电子商业系统。Quartz既能创建简单,也能创建复杂的任务调度,以执行上十、上百,甚至上万的任务。任务 job 被定义为标准的Java组件,能够执行任何想要实现的功能。
Quartz 调度框架包含许多企业级的任务如 JTA 事务、 集群的支持。
简而言之,Quartz 就是基于Java实现的任务调度框架,用于执行自定义的任务。
二、Quartz 运行环境- Quartz 可以运行嵌入在另一个独立式应用程序
- Quartz 可以在应用程序服务器(或Servlet容器)内被实例化,并且参与事务
- Quartz 可以作为一个独立的程序运行(其自己的Java虚拟机内),可以通过RMI使用
- Quartz 可以被实例化,作为独立的项目集群(负载均衡和故障转移功能),用于作业的执行
- 任务 Job
- Job 就是想要实现的任务类,每一个 Job 必须实现 org.quartz.job接口, 且只需要实现接口定义的 execute()方法
- 触发器 Trigger
- Trigger 为你执行任务的触发器,,比如每天定时3点发送一份统计邮件, Trigger将会设置3点进行执行该任务。
- Trigger主要包含两种 SimpleTrigger 和 CronTrigger 两种
- 调度器 Scheduler
- Scheduler 为任务的调度器,它会将任务 Job 以及触发器 Trigger 整合起来,负责基于 Trigger 设定的时间来执行Job
-
Builder 模式
官方案例中使用 newJob(字节码).withXXX.build()的方式创建工作
-
Factory 模式
官方案例中通过 StdSchedulerFactory类获取 调度器对象
-
组件模式
Job、Trigger触发器 和 Scheduler调度器三个组件
-
链式编程
Quartz的类其中部分方法返回的结果就是当前对象实例,所以可以支持链式编程
六、Quartz 的几个常用 API
以下是 Quartz 编程 API 的几个重要接口,也是 Quartz 的重要组件:
- Scheduler 是用于与调度程序交互的主程序接口
Scheduler 调度程序,任务执行计划表,只有安排进执行计划的任务 job (通过 scheduler.schedulerJob方法安排进执行计划),当它预先定义的执行时间到了的时候(任务触发 Trigger),该任务才会执行。
-
Job 我们预先定义的希望在未来时间能被调度程序执行的时候,可以自定义。
-
JobDetail ,使用 jobDetail来定义定时任务的实例通过 ,JobBuilder类创建
-
JobDataMap,可以包含不限量(序列化的)数据对象,在 job 实例执行的时候,可以使用其中的数据; JobDataMap 是 Java Map接口的一个实现,额外增加了一些便于存取基本类型数据的方法。
-
Trigger 触发器,Trigger对象是用来执行Job的。当调度一个 Job 时,我们实例一个触发器会调整它的属性来满足 job 执行的条件。表明任务在什么时候会执行,定义了一个已被安排的任务将会在什么时候执行的条件,比如每2秒执行一次
-
JobBuilder 用于声明一个任务实例,也可以定义关于该任务的详情,比如任务名、组名等,这个声明的实例将会作为一个实际执行的任务。
-
TriggerBuilder 触发器创建器,用于创建触发器 trigger 实例
-
JobListener、TriggerListener、SchedulerLisener 监听器,用于对组件的监听。
项目结构:
引入Maven依赖
4.0.0 groupId quartzDemo 1.0-SNAPSHOT 8 8 org.quartz-scheduler quartz 2.3.2 org.quartz-scheduler quartz-jobs 2.3.2 org.projectlombok lombok 1.18.24 compile org.apache.commons commons-lang3 3.12.0
自定义Job
package cn.uni.job;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 获取当前时间
String t = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
// 工作内容
System.out.printf("[%s] 工作中...n", t);
}
}
定义 Scheduler、Trigger与测试
package cn.uni.main;
import cn.uni.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class HelloSchedulerDemo {
public static void main(String[] args) {
try {
// 1. 调度器 ( Scheduler ) 从工厂中获取调度的实例 (默认: 实例化 new StdSchedulerFactory())
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 任务实例 ( JobDetail )
JobDetail jobDetail = JobBuilder.newJob(MyJob.class) // 加载任务类, 与自定义的Job类进行绑定
// 参数1: 任务的名称 (唯一实例), 参数2:任务组名称
.withIdentity("job1", "jobGroup1")
.build();
// 3. 触发器 (Trigger)
SimpleTrigger trigger = TriggerBuilder.newTrigger()
// 参数1: 触发器的名称(唯一实例),参数2:触发器组的名称
.withIdentity("trigger1", "triggerGroup1")
// 设置每过 3 秒执行一次
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
// 设置立马启动
.startNow()
.build();
// 让调度器关联任务和触发器, 保证按照触发器定义的条件执行任务
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
// 停止调度器
// scheduler.shutdown();
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
}
}
运行结果(每隔三秒执行一次自定义的Job):
[2022-07-08 11:23:09] 工作中... [2022-07-08 11:23:12] 工作中... [2022-07-08 11:23:15] 工作中...八、Quartz组件详情
8.1 Job
Job:工作任务调度的接口,任务类需要实现该接口。该接口中定义 execute()方法,类似JDK提供的 TimeTask 类的run方法,在里面编写任务执行的业务逻辑
Job实例在Quartz中的生命周期:每次调度器执行Job时,它在调用execute方法前会创建一个新的Job实例,当调用完成后,关联的Job对象实例会被释放,释放的实例将会被垃圾回收机制回收。
比如在入门案例七的基础上修改Job类如下:
package cn.uni.job;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;
public class MyJob implements Job {
public MyJob(){
System.out.println("hello, job!");
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 获取当前时间
String t = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
// 工作内容
System.out.printf("[%s] 工作中...n", t);
}
}
运行结果:
hello, job! [2022-07-08 12:34:38] 工作中... hello, job! [2022-07-08 12:34:41] 工作中...
从这里可以看出,Job类每次执行前都被重新定义过。
8.2 JobDetailJobDetail为Job实例提供了许多设置属性,以及 JobDataMap成员变量属性,它用来存储特定 job 实例的状态信息,调度器需要借助 jobDetail 对象来添加 job 实例。
JobDetail jobDetail = JobBuilder.newJob(MyJob.class) // 加载任务类, 与自定义的Job类进行绑定
// 参数1: 任务的名称 (唯一实例), 参数2:任务组名称
.withIdentity("job1", "jobGroup1")
.build();
System.out.println("名称: " + jobDetail.getKey().getName());
// 如果没有指定组名, 则默认为: DEFAULT
System.out.println("组的名称: " + jobDetail.getKey().getGroup());
System.out.println("任务类: " + jobDetail.getJobClass().getName());
运行结果:
名称: job1 组的名称: jobGroup1 任务类: cn.uni.job.MyJob
JobDetail 重要属性:name、group、jobClass、jobDataMap
8.3 任务的形参 JobExecutionContext- 当 Scheduler 调用一个Job,就会将 JobExecutionContext对象传递给 Job的execute() 方法
- Job 能通过 JobExecutionContext 对象访问到 Quartz 运行时候的环境以及 Job 本身的详细数据
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDetail jobDetail = jobExecutionContext.getJobDetail();
JobKey key = jobDetail.getKey();
System.out.println("工作名称: " + key.getName());
System.out.println("工作组: " + key.getGroup());
}
8.4 任务的数据传递 JobDataMap
1)可通过 Map 获取
- 当进行任务调度时,JobDataMap 存储在 JobExecutionContext 中,非常方便获取
- JobDataMap 可以用来装在任何可序列化的数据对象,当 job 实例对象被执行时,这些参数对象都会传递给它
- JobDataMap 实现了 JDK 的 Map接口,并且添加了非常方便的方法来存取基本数据类型
JobDataMap.java
public class JobDataMap extends StringKeyDirtyFlagMap implements Serializable {
...
}
StringKeyDirtyFlagMap.java
public class StringKeyDirtyFlagMap extends DirtyFlagMap {
...
}
DirtyFlagMap.java
public class DirtyFlagMapimplements Map , Cloneable, Serializable { JobDataMap-> StringKeyDirtyFlagMap -> DirtyFlagMap -> Map
JobDetail 和调度器在定义时,都支持通过usingJobData 方法来传递数据给待执行的Job
通过 JobDetail 或 Trigger传递的JobDataMap可在自定义的Job类里接收
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail jobDetail = context.getJobDetail();
JobKey key = jobDetail.getKey();
JobDataMap map = context.getMergedJobDataMap();
for (Map.Entry entry : map.entrySet()) {
System.out.println("key=" + entry.getKey() + ", value=" + entry.getValue());
}
}
这里发现 JobExecutionContext 对象提供了 getMergedJobDataMap() 方法,也就是获取归并后的JobDataMap数据
注意:这里归并后的JobDataMap会对传递的Key做去重操作,如果JobDetail和Trigger 对 job传递了重复的key,那么将会留下最后的Trigger传递的JobDataMap中的key和value。
如果想要获取各自的所有 jobDataMap参数,那么可以通过 JobExecutionContext 对象获取到JobDetail或Trigger。
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); for (Map.Entryentry : jobDataMap.entrySet()) { System.out.println(entry.getKey() + "," + entry.getValue()); } JobDataMap triggerDataMap = context.getTrigger().getJobDataMap(); for (Map.Entry entry : triggerDataMap.entrySet()) { System.out.println(entry.getKey() + "," + entry.getValue()); }
jobDataMap的简化传递
在之前我们是通过 JobExecutionContext 应用上下文对象去获取 JobDataMap的,除此之外,我们可以在自定义的Job类里定义相应的属性,以及对应的一个set方法(可以用 lombok的@Setter),这样在Job执行时,会自动将 JobDataMap对应的参数的值设置到 Job类中。
- 传递参数
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.usingJobData("task", "任务详情")
.build();
- 获取参数
@Data
public class MyJob implements Job {
private String task ;
public MyJob(){ }
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println(task);
}
}
注:如果这里的Key和Value不匹配,那么就不会执行set方法。
8.5 有状态的Job和无状态的Job● 无状态的Job
○ 默认情况下,每次调用时都会创建一个新的 jobDataMap
● 有状态的 Job
○ 多次调用 job,可以获取上次的 jobDataMap,这些状态信息存储在 jobDataMap中
@PersistjobDataAfterExecution注解: 保存Job执行后的 JobDataMap
案例:使用 Job 实现对 1 + 2 + 3… + 10 的和
自定义 Job
package cn.uni.job;
import lombok.Data;
import lombok.Setter;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.*;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
@Setter
@PersistJobDataAfterExecution
public class MyJob implements Job {
private int i;
private int sum;
public MyJob(){ }
@Override
public void execute(JobExecutionContext context) {
// 获取当前时间
String t = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
// 工作内容
System.out.printf("[%s] %dn", t, sum += (i++));
// 更新 JobDataMap
context.getJobDetail().getJobDataMap().put("i", i);
context.getJobDetail().getJobDataMap().put("sum", sum);
}
}
传参
package cn.uni.main;
import cn.uni.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class HelloSchedulerDemo {
public static void main(String[] args) {
try {
// 1. 调度器 ( Scheduler ) 从工厂中获取调度的实例 (默认: 实例化 new StdSchedulerFactory())
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 任务实例 ( JobDetail )
JobDetail jobDetail = JobBuilder.newJob(MyJob.class) // 加载任务类, 与自定义的Job类进行绑定
.usingJobData("i", 0)
.usingJobData("sum", 0)
.build();
// 3. 触发器 (Trigger)
SimpleTrigger trigger = TriggerBuilder.newTrigger()
// 设置每过 1 秒执行一次, 执行 10 次
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(1)
.withRepeatCount(10))
// 设置立马启动
.startNow()
.build();
// 让调度器关联任务和触发器, 保证按照触发器定义的条件执行任务
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
}
}
运行结果:
[2022-07-08 14:21:07] 0 [2022-07-08 14:21:08] 1 [2022-07-08 14:21:09] 3 [2022-07-08 14:21:10] 6 [2022-07-08 14:21:11] 10 ...后面都差不多,一共执行了10次8.5 Trigger
Quartz 有一些不同的触发器类型,用的最多的是 SimpleTriggerImpl 和 CronTriggerImpl
1) JobKey
表示 Job 实例的表示,触发器被触发时,JobKey指定的 Job 实例会被执行
2)startTime
表示触发器的时间表,第一次开始被触发的时间,它的数据类型是 java.util.Date
3)endTime
指定触发器终止被触发的时间,它的数据类型是 java.util.Date
案例:
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
...
.build();
public void execute(JobExecutionContext context) {
// 获取当前时间
String t = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
System.out.printf("[%s] 任务执行 n", t);
Trigger trigger = context.getTrigger();
// JobKey jobKey = trigger.getJobKey(); // 通过 trigger 可获取到 jobKey
TriggerKey triggerKey = trigger.getKey();
System.out.println("jobKey的名称: " + triggerKey.getName());
System.out.println("jobKey的组名: " + triggerKey.getGroup());
System.out.println("任务开始时间: " + trigger.getStartTime());
System.out.println("任务结束时间: " + trigger.getEndTime());
}
触发器可以自定义控制任务的开始时间、结束时间、执行次数等参数
// 3. 触发器 (Trigger)
Date endDate = new Date();
endDate.setTime(endDate.getTime() + 10000); // 10秒后停止
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.endAt(endDate)
// 设置立马启动
.startNow()
.build();
这里是通过 endAt 来指定任务的结束时间, 这样比较麻烦,而且只能执行一次,除此之外可以使用SchedulerBuilder的实现类来指定任务的时间
.withSchedule(SimpleScheduleBuilder.simpleSchedule().xxx)8.5.1 SimpleTrigger 触发器 使用案例
SimpleTrigger 对于设置和使用是最简单的一种 QuartzTrigger
适合需要在特定的日期/时间启动的场景,且以一个可能的间隔时间重复执行n次的 Job
接下来的案例均以相同的自定义Job类:
package cn.uni.job;
import lombok.Data;
import lombok.Setter;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.*;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
@Setter
@PersistJobDataAfterExecution
public class MyJob implements Job {
private int count;
public MyJob(){ }
@Override
public void execute(JobExecutionContext context) {
// 获取当前时间
String t = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
System.out.printf("[%s] 任务第 %d 次执行 n", t, ++count);
context.getJobDetail().getJobDataMap().put("count", count);
}
}
例1:设置触发器,让任务在两秒后开始执行
package cn.uni.main;
import cn.uni.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Date;
public class HelloSchedulerDemo {
public static void main(String[] args) {
try {
// 1. 调度器 ( Scheduler ) 从工厂中获取调度的实例 (默认: 实例化 new StdSchedulerFactory())
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 任务实例 ( JobDetail )
JobDetail jobDetail = JobBuilder.newJob(MyJob.class) // 加载任务类, 与自定义的Job类进行绑定
.usingJobData("count", 0)
.build();
// 3. 触发器 (Trigger)
Date startDate = new Date();
startDate.setTime(startDate.getTime() + 2000);
Trigger trigger = TriggerBuilder.newTrigger()
// 设置 2秒后开始执行, 执行1次
.startAt(startDate)
.build();
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
}
}
运行结果:
[2022-07-08 15:07:39] 任务第 1 次执行
例2:设置触发器,让任务在两秒后开始执行,并且每次间隔一秒重复执行三次
(代码其他部分跟上述相同,故忽略,下同)
Trigger trigger = TriggerBuilder.newTrigger()
// 设置 2秒后开始执行, 执行1次
.startAt(startDate)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1) // 间隔1秒
.withRepeatCount(2)) // 重复两次
.build();
运行结果:
[2022-07-08 15:13:18] 任务第 1 次执行 [2022-07-08 15:13:19] 任务第 2 次执行 [2022-07-08 15:13:20] 任务第 3 次执行
例3:设置触发器,每3秒执行1次任务,设置重复执行4次,但指定任务在10秒后就结束
Date startDate = new Date();
Date endDate = new Date();
startDate.setTime(startDate.getTime() + 2000);
endDate.setTime(startDate.getTime() + 10000);
Trigger trigger = TriggerBuilder.newTrigger()
// 设置2秒后开始执行
.startAt(startDate)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3) // 间隔3秒
.withRepeatCount(4)) // 重复执行四次
.endAt(endDate)
.build();
运行结果:可以发现,尽管设置重复执行四次,但是结束时间在重复执行第四次前就到了,任务就不会继续执行,这里是以任务的结束时间为准。
[2022-07-08 15:19:32] 任务第 1 次执行 [2022-07-08 15:19:35] 任务第 2 次执行 [2022-07-08 15:19:38] 任务第 3 次执行 [2022-07-08 15:19:41] 任务第 4 次执行
SimpleTrigger 需要注意的点:
● SimpleTrigger的属性有:开始时间、结束时间、重复次数 和 重复的时间间隔
● 重复次数属性的值可以为 0 、正整数、或常量 SimpleTrigger.REPEAT_INDEFINITELY
● 重复的时间间隔属性值必须为0或长整型的正整数,以毫秒作为时间单位,当重复的时间间隔为 0 时,意味着与Trigger同时触发执行。
● 如果有执行结束时间属性值,刚结束时间属性优先于重复次数属性,这样的好处在于:当我们需要创建每一个间隔10秒钟触发一次直到指定的结束时间的Trigger,而无需去计算从开始到结束的所重复的次数,只需要简单的指定结束时间,和 SimpleTrigger.REPEAT_INDEFINITELY 作为重复次数的属性值即可。
● 其中的 withRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY)) 可以用 .repeatForever() 代替,效果都是一样的,指定任务一直执行
适合类似日历那样来按日程触发任务,而不是像 SimpleTrigger 那样每隔特定的间隔时间触发,CronTriggers 通常比 SimpleTrigger 更有效果,因为它是基于日历的作业调度器。
使用 CtronTrigger ,可以指定诸如:“每个周五的中午12点”,或者 “每个工作日的 9:00” 或者 " 从每个周一、周三、周五的上午 9:00 到 上午 10 : 00 之间每隔五分钟" 这样日程安排来触发。
CronTrigger 可以和 SimpleTrigger一样,startTime指定日程开始时间,也有可选的endTime指定日程结束时间。
Cron Expressions —— Cron 表达式
可以直接利用工具,在线生成 在线QuartzCron定时任务表达式在线生成工具 - ToolTT在线工具箱
九、Quartz 任务调度9.1 配置、资源 SchedulerFactory
Quartz 以模块方式构建,因此在运行时几个组件必须配合使用。所有的 Scheduler 实例由 SchedulerFactory 创建
Quartz 的三个核心概念:调度器、任务和触发器,三者之间的关系是:
Job 三大要素:Scheduler、JobDetail、Trigger,而Trigger对于job而言就好比一个驱动器;
没有触发器来定时驱动作业,作业就无法运行;
对于 Job 而言,一个Job可以对应多个Trigger,但对于Trigger而言,一个Trigger只能对应一个Job。
所以一个Trigger 只能被指派给一个Job;如果需要一个更复杂的触发计划,就可以创建多个 Trigger 并指派它们给同一个 Job
Quartz 默认的 SchedulerFactory有以下两个特点
- 使用一组参数(java.util.Properties)来创建和初始化 Quartz 调度器
- 配置参数一般存储在 quartz.properties 文件中
调用该调度器的位置在 org.quartz.impl下的 StdSchedulerFactory.java 类中的 initialize() 方法
● 调用 getSchedular 方法就能创建和初始化调度器对象
SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler();
等同于
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
用法一:输出调度器开始的时间(重要:使得任务和触发器进行关联)
Date scheduleJob(JobDetail jobDetail, Trigger trigger)
// 绑定 job 和 trigger
Date date = scheduler.scheduleJob(jobDetail, trigger);
System.out.println("Scheduler's start time is " + DateFormatUtils.format(date, "yyyy-MM-dd HH:mm:ss"));
用法二:启动任务调度
void start();
scheduler.start();
用法三:任务调度器挂起,即暂停操作
void standby();
scheduler.standby();
用法四:关闭任务调度
void shutdown();
shutdown(true) :表示等待所有正在执行的 job 执行完毕之后,再关闭 Scheduler shutdown(false):表示直接关闭 Scheduler9.2.2 DirectSchedulerFacotry(了解)
DirectSchedulerFacotry 是对 SchedulerFactory的直接实现,通过它可直接构建 Scheduler、Threadpool等
9.3 Quartz 配置文件详解默认路径: quartz-2.3.2.jar包下的 org.quartz目录中,文件名为 quartz.properties
使用时候可在项目的资源目录 resources 中添加 quartz.properties配置文件,去覆盖底层默认的配置文件
org.quartz.scheduler.instanceName: DefaultQuartzScheduler org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
配置文件的组成部分:
● 调度器属性
org.quartz.scheduler.instanceName 属性用来区分特定的调度器实例,可以按照功能用途来给调度器起名。
org.quartz.scheduler.instanceId 属性和前者一样,也允许任何字符串,但这个值必须在所有调度器实例中是唯一的,尤其是在一个集群环境中,作为集群的唯一key,假如想让Quartz帮助生成这个值,可以设置为 AUTO。
● 线程池属性
threadCount
处理 Job 的线程个数, 至少为1,最多建议不超过100,否则容易卡顿
threadPriority
线程的优先级,优先级别高的线程比级别低的线程优先得到执行。最小为1,最大为10,默认为5
org.quartz.threadPool.class
一个实现了 org.quartz.spi.ThreadPool 接口的类,Quartz 自带的线程池实现类是 org.quartz.smpl.SimpleThreadPool
● 作业存储设置
描述了再调度器实例的生命周期中, Job 和 Trigger 信息是如何被存储的。
● 插件配置
满足特定需求用到的Quartz插件的配置
Quartz配置的两种方式:① 配置文件 ② Java配置类
这里配置了 threadCount 线程的个数为负,故一启动就报错了,说明我们配置的内容是有用的。
通过 Properties 对象可设置 Quertz框架的参数。
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Properties;
public class QuartzConfig {
public static void main(String[] args) throws SchedulerException {
// 创建工厂实例
StdSchedulerFactory factory = new StdSchedulerFactory();
// 创建配置工厂的属性的对象
Properties prop = new Properties();
prop.put(StdSchedulerFactory.AUTO_GENERATE_INSTANCE_ID, "AUTO");
try{
// 加载属性
factory.initialize(prop);
Scheduler scheduler = factory.getScheduler();
scheduler.start();
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
}
}
十、Quartz 监听器
QUartz监听器用于当任务调度时,及时获取事件的通知,类似于任务执行过程中的邮件、短信类的提醒。Quartz监听器主要由 JobListener、TriggerListener、SchedulerListener 三种,分别表示任务、触发器和调度器对应的监听器。三者的使用方法类似。
监听器分为 全局监听器 和 非全局监听器,二者的区别在于:
● 全局监听器能够接收到所有的 Job / Trigger 的事件通知,
● 非全局监听器只能接收到其上注册的 Job 或 Trigger 的事件,不在其上注册的 Job 或 Trigger 则不会进行监听。
在任务调度过程中,与任务 Job 相关的事件包括: job 开始要执行的提示; job 执行完成的提示。
自定义监听器,需实现 JobListener 接口
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
public class MyJobListener implements JobListener {
public String getName() {
String name = this.getClass().getSimpleName();
System.out.println("监听器名称: " + name);
return name;
}
public void jobToBeExecuted(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("job名称是: " + jobName + ", Scheduler在JobDetail将被执行时调用的方法");
}
public void jobExecutionVetoed(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("job名称是: " + jobName + ", Scheduler在JobDetail将被执行时, 但又被 TriggerListener 否决时调用的方法");
}
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("job名称是: " + jobName + ", Scheduler在JobDetail被执行后调用这个方法");
}
}
● 通过调度器定义全局的Job监听器
// 创建并注册全局的 JobListener
scheduler.getListenerManager().addJobListener(
new MyJobListener(), EverythingMatcher.allJobs());
● 通过调度器定义局部的Job监听器
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.EverythingMatcher;
import org.quartz.impl.matchers.KeyMatcher;
public class Demo {
public static void main(String[] args) throws SchedulerException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "jobGroup1")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.startNow()
.build();
scheduler.scheduleJob(jobDetail, trigger);
// 创建并注册局局的 JobListener
scheduler.getListenerManager().addJobListener(
new MyJobListener(),
KeyMatcher.keyEquals(JobKey.jobKey("job1", "jobGroup1")));
scheduler.start();
}
}
JobListener 接口各方法作用总结:
● getName方法,用于获取 JobListener 的名称
● jobToBeExecuted方法:Scheduler 在 jobDetail 将要被执行时调用的方法
● jobExecutionVetoed方法:Scheduler 在 JobDetail即将被执行,但又被 TriggerListener 否决时会调用的方法
● jobWasExecuted方法:Scheduler在JobDetail被执行之后调用的方法
任务调度过程中,与触发器 Trigger 相关的事件包括:触发器出发、触发器未正常触发、触发器完成等。
源码:
package org.quartz;
import org.quartz.Trigger.CompletedExecutionInstruction;
public interface TriggerListener {
String getName();
void triggerFired(Trigger trigger, JobExecutionContext context);
boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
void triggerMisfired(Trigger trigger);
void triggerComplete(Trigger trigger, JobExecutionContext context,
CompletedExecutionInstruction triggerInstructionCode);
}
其中:
● getName方法,用于获取触发器的名称
● triggerField方法,当与监听器相关联的 Trigger被触发,Job 上的 execute() 方法被执行时,Scheduler 就调用该方法。
● vetoJobExecution 方法,在 Trigger触发后,Job 将要被执行时由 Scheduler 调用这个方法。
● triggerMisfired 方法,Scheduler 调用这个方法是在 Trigger 错过触发时。 应该关注此方法中持续事件长的逻辑:在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应,应该保持这个上方法尽量小。
● triggerCompelet 方法:Trigger 被触发并且完成了 Job的执行时,Scheduler 调用这个方法。
自定义触发器监听器
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
public class MyTriggerListener implements TriggerListener {
public String getName() {
return this.getClass().getSimpleName();
}
public void triggerFired(Trigger trigger, JobExecutionContext context) {
String name = trigger.getKey().getName();
System.out.println("触发器: " + name + " 被触发. ");
}
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
String name = trigger.getKey().getName();
System.out.println(name + "没有被触发");
return true; // false 表示会执行 Job , true 表示不会执行 Job
}
public void triggerMisfired(Trigger trigger) {
String name = trigger.getKey().getName();
System.out.println(name + "错过触发");
}
public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
String name = trigger.getKey().getName();
System.out.println(name + "完成之后触发");
}
}
定义全局的 Trigger Listener
scheduler.getListenerManager().addTriggerListener(
new MyTriggerListener(), EverythingMatcher.allTriggers());
定义局部的 Trigger Listener
scheduler.getListenerManager().addTriggerListener(
new MyTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "group1")));
10.3 SchedulerListener
SchedulerListener 会在 Scheduler 的生命周期中关键事件发生时被调用。与 Scheduler 有关的事件包括:增加一个 job / trigger ,删除一个 job/ trigger,scheduler 发生严重错误,关闭 scheduler等。
自定义调度器监听器:
import com.sun.xml.internal.fastinfoset.util.StringIntMap;
import org.quartz.*;
public class MySchedulerListener implements SchedulerListener {
// 部署 JobDetail时调用
public void jobScheduled(Trigger trigger) {
String name = trigger.getKey().getName();
System.out.println(name + " jobDetail完成部署");
}
// 卸载 JobDetail时调用
public void jobUnscheduled(TriggerKey triggerKey) {
String name = triggerKey.getName();
System.out.println(name + " 完成卸载");
}
// 当 Trigger不再被触发的时候执行的方法
public void triggerFinalized(Trigger trigger) {
System.out.println("触发器被移除");
}
// 当 一个Trigger被暂停时执行的方法,假如是 Trigger组的话,triggerName参数将为 null
public void triggerPaused(TriggerKey triggerKey) {
String name = triggerKey.getName();
System.out.println(name + " 正在被暂停");
}
// 当一个Trigger组被暂停时执行的方法,假如是 Trigger组的话,triggerName参数将为 null
public void triggersPaused(String triggerGroup) {
System.out.println("触发器组 " + triggerGroup + " 被暂停");
}
// Trigger 从暂停中恢复
public void triggerResumed(TriggerKey triggerKey) {
String name = triggerKey.getName();
System.out.println(name + " 正在从暂停中恢复");
}
// Trigger 组从暂停中恢复
public void triggersResumed(String triggerGroup) {
System.out.println("触发器组: " + triggerGroup + " 正在从暂停中恢复");
}
// 添加任务
public void jobAdded(JobDetail jobDetail) {
System.out.println("添加工作任务: " + jobDetail.getKey().getName());
}
// 删除任务
public void jobDeleted(JobKey jobKey) {
System.out.println("删除工作任务: " + jobKey.getName());
}
// 暂停任务
public void jobPaused(JobKey jobKey) {
System.out.println("暂停工作任务: " + jobKey.getName());
}
// 暂停任务组
public void jobsPaused(String jobGroup) {
System.out.println("暂停工作任务组: " + jobGroup);
}
// 恢复任务
public void jobResumed(JobKey jobKey) {
System.out.println("恢复工作任务: " + jobKey.getName());
}
// 恢复任务组
public void jobsResumed(String jobGroup) {
System.out.println("工作组 " + jobGroup + " 恢复了");
}
// scheduler 发生报错时
public void schedulerError(String msg, SchedulerException cause) {
System.out.println("调度器报错: " + msg + cause.getUnderlyingException());
}
// scheduler 被挂起时
public void schedulerInStandbyMode() {
System.out.println("调度器被挂起");
}
// scheduler 被开启时
public void schedulerStarted() {
System.out.println("调度器开启");
}
// scheduler 正在开启
public void schedulerStarting() {
System.out.println("调度器正在开启");
}
// scheduler 关闭
public void schedulerShutdown() {
System.out.println("调度器被关闭");
}
// scheduler 正在被关闭
public void schedulerShuttingdown() {
System.out.println("调度器正在被关闭");
}
// scheduler 数据被清除
public void schedulingDataCleared() {
System.out.println("调度器数据被清除的时候调用");
}
}
// 添加监听器
scheduler.getListenerManager().addSchedulerListener(
new MySchedulerListener()
);
// 移除监听器
scheduler.getListenerManager().removeSchedulerListener(
new MySchedulerListener()
);



