Quartz
文章目录
1. 快速开始
1.1 依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
1.2 Quartz 配置文件
Quartz 默认会在 classpath 下去找一个 quartz.properties 的配置文件, 该配置文件并不是必须的, 但是通常都是需要的.
org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
- org.quartz.scheduler.instanceName: 指定 Schedule 实例的名称;
- org.quartz.threadPool.threadCount: 线程数, 意味着最多可以同时运行 3 个任务;
- org.quartz.jobStore.class: 指定 Quartz 数据的存放形式. Quartz的所有数据(例如作业和触发器的详细信息)都保存在内存中(而不是数据库中).
1.3 获取并启动 Schedule 实例
// 获取 Schedule 实例, 该实例的信息(比如: 实例名称)将会从 quartz.properties 中获取.
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// and start it off
scheduler.start();
1.4 创建任务(JobDetail)
创建一个类, 实现 org.quartz.Job 接口并在 execute 方法中添加任务的逻辑.
通过 JobBuilder.newJob 的静态方法创建 JobDetail
package com.jackli.schedule.job;
import org.quartz.*;
import java.time.LocalDateTime;
@PersistJobDataAfterExecution // 标记当前 Job 为一个有状态的 Job
public class PrintDateTimeJob implements Job {
public PrintDateTimeJob() {
System.out.println("======PrintDateTimeJob=======");
}
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String dateTime = LocalDateTime.now().toString();
System.out.println("$$The current dateTime is " + dateTime);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("$$PrintDateTimeJob execute success.");
}
/**
* 创建 JobDetail 实例.
* Job 的 name 是必须的, Group 是可选的, 如果不指定, 默认为 DEFAULT
*/
public static JobDetail buildJob(String key) {
return JobBuilder.newJob(PrintDateTimeJob.class)
.withIdentity(key, JOB_GROUP)
.storeDurably() // 如果添加 Job 时没有指定 trigger 需要指定 durably
.build();
}
public static final String JOB_GROUP = "print";
}
1.5 运行 Job
1.5.1 创建触发器
每一个 Job 都需要一个触发器才会执行, 在触发器中将指定何时执行? 多久执行一次? 重复多少次等信息.
创建触发器可以通过 TriggerBuilder.newTrigger 静态方法创建.
Trigger trigger = newTrigger()
.withIdentity("jack-dateTime", PrintDateTimeJob.JOB_GROUP)
.forJob(jobDetail.getKey()) // 仅用 trigger 执行 job, trigger 必须指定 jobKey.
.startNow()
.withSchedule(simpleSchedule().withIntervalInSeconds(1).repeatForever())
.build();
1.5.2 执行 Job
执行 Job 有多种方式:
- 持有 JobDetail 和 Trigger 直接触发执行
scheduleServer.scheduleJob(jobDetail, trigger);
- 先添加 Job, 再用触发器触发
scheduleServer.addJob(jobDetail);
scheduleServer.scheduleJob(trigger);
addJob 方法必须指定 Job 为 durable, 否则将会抛出异常.
scheduleJob 方法如果没指定JobDetail, 那么 Trigger 就必须指定 jobKey, 通过.forJob(key)
1.5.3 关闭 Schedule
关闭 Schedule 有两种方式.
scheduler.shutdown(waitForJobsToComplete)
如果 waitForJobsToComplete 为 true, 则表示等待当前正在运行的 Jobs 运行完成之后再关闭 Schedule 实例; 如果为 false 则表示立即关闭 Schedule 实例.
scheduler.shutdown()
等同于scheduler.shutdown(false)
2. 核心 API
2.1 Trigger
Quartz 提供的触发器有四种, 常用的有两种: SimpleTrigger 和 CronTrigger.
- CalendarIntervalScheduleBuilder
- CronScheduleBuilder
- DailyTimeIntervalScheduleBuilder
- SimpleScheduleBuilder
2.1.1 SimpleTrigger
SimpleTrigger 是由
SimpleScheduleBuilder
构造的简单触发器,SimpleScheduleBuilder
使用 Build 模式构造对象.
SimpleScheduleBuilder 可以实现一些简单的定时触发, 比如: 每几秒执行一次, 重复执行多少次等.
Trigger trigger = newTrigger()
.withIdentity("jack-dateTime", PrintDateTimeJob.JOB_GROUP)
.forJob(jobDetail.getKey())
.startNow()
.withSchedule(simpleSchedule().withIntervalInSeconds(1).repeatForever())
.build();
2.1.2 CronTrigger
Cron 触发器的核心就是 cron 表达式的书写.
- cron 表达式有 7 个值, 最后一个 [年] 为可选参数
- 格式为 [秒] [分] [小时] [日] [月] [周] [年] .
- 其中 [周] 与 [日] 相互冲突, 因此如果设置了其中一项, 则另一项用 ? 表示不设置此值.
cron 表达式:
其中
- , 表示或的意思
- - 表示至的关系
- * 表示任意的关系
- / 表示每的关系
- # 表示第的关系
- L 表示最后的关系
- ? 表示不设置该值, 用于解决 [日] 和 [周] 的冲突.
- 周上的1-7 表示 周日(1) 到周六
一般可以通过在线工具去生成 cron 表达式而不用自己去写.
2.2 JobDetail
JobDetail 代表一个将要执行的任务.
- JobDetail 通过
JobBuilder.newJob
进行构造, 除此之外一般需要指定 name 和 group, name 属性默认为 UUID, group 默认为"DEFAULT"
. - 如果 JobDetail 通过
scheduler.addJob
方法添加到 Schedule, 则 JobDetail 必须指定storeDurably()
. - 同一个 identification(group.name) 必须在 Schedule 中唯一, 如果再次添加已经存在的 JobDetail, 则会抛错
ObjectAlreadyExistsException
. - JobDetail 如果需要传递参数到 job 执行时, 则在构造时通过
.usingJobData()
传递. 如果需要传递引用数据类型, 则需要自己new JobDataMap
然后 put 进去. - 参数的获取通过
jobExecutionContext.getJobDetail()
获取到 JobDataMap, 然后从 Map 中 get.
public static JobDetail buildJob(String key) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("objectKey", new Object());
return JobBuilder.newJob(PrintDateTimeJob.class)
.withIdentity(key, JOB_GROUP)
// 传递基本数据类型
.usingJobData("dKey", "dValue")
// 传递引用数据类型
.usingJobData(jobDataMap)
.storeDurably() // 如果添加 Job 时没有指定 trigger 需要指定 durably
.build();
}
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
String baseValue = jobDataMap.getString("dKey");
Object object = jobDataMap.get("objectKey");
2.3 Listener
Listener 有全局 Listener 和局部 Listener之分.
2.3.1 JobListener
2.3.1.1 创建 JobListener
写一个类实现 JobListener 接口
package com.jackli.schedule.listner;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
public class MyJobListener implements JobListener {
@Override
public String getName() {
// Listener Name. 不能为空.
return this.getClass().getSimpleName();
}
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
System.out.println("Job 将要被执行....");
}
@Override
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
System.out.println("Job 被阻止执行时调用...");
}
@Override
public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
System.out.println("Job 被执行完后调用...");
}
}
2.3.1.2 注册 JobListener
- 注册全局 JobListener
scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
scheduler.getListenerManager().addJobListener(new MyJobListener());
- 注册局部 Listener
scheduler.getListenerManager().addJobListener(new MyJobListener(), NameMatcher.jobNameContains("jack"));
scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("jack-dateTime", PrintDateTimeJob.JOB_GROUP)));
2.3.2 TriggerListener
2.3.2.1 创建 TriggerListener
package com.jackli.schedule.listner;
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
import java.util.Random;
public class MyTriggerListener implements TriggerListener {
@Override
public String getName() {
// Listener Name. 不能为空.
return this.getClass().getSimpleName();
}
@Override
public void triggerFired(Trigger trigger, JobExecutionContext jobExecutionContext) {
// 与监听器相关联的 Trigger 被触发, Job 的 execute 方法将要执行时调用
System.out.println("triggerFired....");
}
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext jobExecutionContext) {
// 阻止 Job 执行. 当 Job 将要执行时如果该方法返回 true, 则 Job 将不会执行本次触发.
boolean result = new Random().nextInt(10) > 5;
System.out.println("vetoJobExecution....." + result);
return result;
}
@Override
public void triggerMisfired(Trigger trigger) {
// 在 Trigger 错过触发时执行.
System.out.println("triggerMisfired....");
}
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {
/**
* Trigger 被触发, 并且完成了 Job 的执行时触发.
* 该方法在 JobListener 的 jobWasExecuted 方法执行后执行.
* 如果 Job 被 vetoJobExecution 阻止, 则该方法不会执行.
*/
System.out.println("triggerComplete...");
}
}
2.3.2.2 注册 TriggerListener
// 创建全局 TriggerListener
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), EverythingMatcher.allTriggers());
// 创建局部 TriggerListener
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), KeyMatcher.keyEquals(new TriggerKey("jack-dateTime", PrintDateTimeJob.JOB_GROUP)));
2.3.3 SchedulerListener
2.3.3.1 创建 SchedulerListener
package com.jackli.schedule.listner;
import org.quartz.*;
public class MyScheduleListener implements SchedulerListener {
@Override
public void jobScheduled(Trigger trigger) {
System.out.println("jobScheduled...部署 JobDetail 时调用...");
}
@Override
public void jobUnscheduled(TriggerKey triggerKey) {
System.out.println("jobUnscheduled...卸载 JobDetail 时调用..." + triggerKey.getName());
}
@Override
public void triggerFinalized(Trigger trigger) {
System.out.println("triggerFinalized...当这个 trigger 再也不会被触发时调用, " +
"除非对应的 JobDetail 被设置成了持久化的, 否则他就会从 Schedule 中移除..."
+ trigger.getKey().getName());
}
@Override
public void triggerPaused(TriggerKey triggerKey) {
System.out.println("triggerPaused....当一个 Trigger 被暂停时调用..." +
triggerKey.getName());
}
@Override
public void triggersPaused(String triggerGroup) {
System.out.println("triggersPaused....当一个 Trigger 组被暂停时调用..." +
"如果是 Trigger 组, 那么 key 的 name 将为 null.." + triggerGroup);
}
@Override
public void triggerResumed(TriggerKey triggerKey) {
System.out.println("triggerResumed....Trigger 被恢复执行时触发..." + triggerKey.getName());
}
@Override
public void triggersResumed(String triggerGroup) {
System.out.println("triggersResumed... Trigger 组被恢复执行时触发..." + triggerGroup);
}
@Override
public void jobAdded(JobDetail jobDetail) {
System.out.println("jobAdded... Job 被添加到 Schedule 时执行..." + jobDetail.getKey());
}
@Override
public void jobDeleted(JobKey jobKey) {
System.out.println("jobDeleted... Job 从 Schedule 移除时执行..." + jobKey);
}
@Override
public void jobPaused(JobKey jobKey) {
System.out.println("jobPaused....job 被暂停执行时触发..." + jobKey);
}
@Override
public void jobsPaused(String jobGroup) {
System.out.println("jobsPaused....job 组被暂停执行时触发..." + jobGroup);
}
@Override
public void jobResumed(JobKey jobKey) {
System.out.println("jobResumed....job 被恢复执行时触发..." + jobKey);
}
@Override
public void jobsResumed(String jobGroup) {
System.out.println("jobsResumed....job 组被恢复执行时触发..." + jobGroup);
}
@Override
public void schedulerError(String msg, SchedulerException cause) {
System.out.println("schedulerError...Schedule 发生错误时执行...msg: " + msg + ", cause: " + cause);
}
@Override
public void schedulerInStandbyMode() {
System.out.println("schedulerInStandbyMode...当 Schedule 处于 StandBy(等待或者备用) 模式时调用...");
}
@Override
public void schedulerStarted() {
System.out.println("schedulerStarted...Schedule 已经启动时调用...");
}
@Override
public void schedulerStarting() {
System.out.println("schedulerStarting...Schedule 正在启动时调用...");
}
@Override
public void schedulerShutdown() {
System.out.println("schedulerShutdown....Schedule 停止时调用...");
}
@Override
public void schedulerShuttingdown() {
System.out.println("schedulerShuttingdown...Schedule 正在关闭时调用...");
}
@Override
public void schedulingDataCleared() {
System.out.println("schedulingDataCleared...当 Schedule 中的数据被清空时调用该方法...");
}
}
2.3.3.2 注册 SchedulerListener
SchedulerListener 没有全局与局部之分
scheduler.getListenerManager().addSchedulerListener(new MyScheduleListener());
3. 配置
3.1 配置文件的加载
- org/quartz/quartz.properties
- classpath/quartz.properties
- 手动指定配置文件的位置和名称
- 手动指定使用的 properties 文件就不能通过
StdSchedulerFactory.getDefaultScheduler()
获取. 需要自己去 new StdSchedulerFactory.
- 手动指定使用的 properties 文件就不能通过
public StdSchedulerFactory(Properties props) throws SchedulerException {
initialize(props);
}
public StdSchedulerFactory(String fileName) throws SchedulerException {
initialize(fileName);
}
3.2 Schedule 主配置
- org.quartz.scheduler.instanceName
可以是任何字符串,并且该值对调度器本身没有意义——而是作为客户机代码的一种机制,以便在同一程序中使用多个实例时区分调度器。如果使用集群特性,则必须对集群中“逻辑上”相同的调度程序的每个实例使用相同的名称。
- org.quartz.scheduler.instanceId
可以是任何字符串,但对于所有工作的调度程序必须是惟一的,就好像它们是集群中相同的“逻辑”调度程序一样。如果希望为自己生成Id,可以使用值“AUTO”作为instanceId。如果希望值来自系统属性“org.quartz.scheduler.instanceId”,则使用“SYS_PROP”。
- org.quartz.scheduler.instanceIdGenerator.class
org.quartz.simpl.SimpleInstanceIdGenerator”,它根据主机名和时间戳生成实例ID。其他IntanceIdGenerator实现包括SystemPropertyInstanceIdGenerator(从系统属性“ org.quartz.scheduler.instanceId”获取实例ID)和HostnameInstanceIdGenerator(使用本地主机名(InetAddress.getLocalHost()。getHostName()))。您也可以实现InstanceIdGenerator接口自己。
- org.quartz.scheduler.threadName
可以是Java线程的有效名称的任何String。如果未指定此属性,则线程将收到调度程序的名称(“ org.quartz.scheduler.instanceName”)追加字符串 “_QuartzSchedulerThread” 作为线程名.
4. Example
没过瘾? 关注我获取更多技术干货
扫一扫关注公众号, 加入 QQ 群
我们
一起进步 !
一个致力于技术交流讨论的技术型 QQ 群
您
要错过这几百个亿吗?