概述
Quartz 是开源任务调度框架中的翘首,是 OpenSymphony 开源组织在 Job scheduling 领域又一个开源项目,完全由 Java 开发,可以用来执行定时任务,类似于 java.util.Timer。但是相较于 Timer, Quartz 增加了很多功能:
- Quartz 提供了强大任务调度机制,同时保持了使用的简单性。Quartz 允许开发人员灵活地定义触发器的调度时间表,并可以对触发器和任务进行关联映射。
- Quartz 提供了调度运行环境的持久化机制,可以保存并恢复调度现场,即使系统因故障关闭,任务调度现场数据并不会丢失。
- Quartz 还提供了组件式的侦听器、各种插件、线程池等功能。
大部分公司都会用到定时任务这个功能。拿火车票购票来说:
- 当下单购票后,后台就会插入一条待支付的 task(job),一般是30分钟,超过30min后就会执行这个 job,去判断是否支付,未支付就会取消此次订单;
- 当支付完成之后,后台拿到支付回调后就会再插入一条待消费的 task(job),Job 触发日期为火车票上的出发日期,超过这个时间就会执行这个job,判断是否使用等。
Quartz 的核心类有以下三部分:
- 任务 Job : 需要实现 org.quartz.Job 接口的任务类,实现
execute()
方法,执行后完成任务。 - 触发器 Trigger :
实现触发任务去执行的触发器,触发器 Trigger 最基本的功能是指定 Job 的执行时间,执行间隔,运行次数等。
包括 SimpleTrigger(简单触发器)和 CronTrigger。
- 调度器 Scheduler : 任务调度器,负责基于
Trigger
触发器,来执行 Job任务。
主要关系如下:
入门案例
依赖
SpringBoot集成依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
原生依赖
<!-- 核心包 -->
<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>
代码
自定义任务类
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;
@Slf4j
public class QuartzJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//先得到任务,之后就得到map中的名字
Object name = context.getJobDetail().getJobDataMap().get("name");
log.info(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " "+ name + "搞卫生");
}
}
任务调度方法
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
@Slf4j
@Component
public class QuartzSheduleTask {
// @PostConstruct // 实例化类后执行该方法
public void startDheduleTask() throws Exception {
// 创建任务
JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class)
.withIdentity("任务-A", "任务分组-A")
.withDescription("开年大扫除")
.usingJobData("name", "王阿姨")
.build();
// 创建触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("触发器-A", "触发器分组-A")
// .startAt(new Date())
.startNow()
.withSchedule(
simpleSchedule()
.withIntervalInSeconds(5) // 任务执行间隔
// .withRepeatCount(10) // 任务重复执行次数,-1 为一直执行,缺省值为0
.repeatForever() // 任务一直执行
)
// .withSchedule(
// CronScheduleBuilder.cronSchedule("0/5 * * * * ?")
// )
.withDescription("大扫除触发器")
.build();
// 实例化调度器工厂
SchedulerFactory factory = new StdSchedulerFactory();
// 得到调度器
Scheduler scheduler = factory.getScheduler();
// 将触发器和任务绑定到调度器中去
scheduler.scheduleJob(jobDetail, trigger);
// 启动调度器
scheduler.start();
}
Quartz API
官方API:http://www.quartz-scheduler.org/api/2.3.0/index.html
主要 API 接口概述
Quartz API 的主要接口有:
- Job :任务逻辑类需要实现的接口,定义被调度的任务内容
- JobDetail(任务详情):又称任务实例,用于绑定 Job,为 Job 提供了许多设置属性
调度器需要借助 Jobdetail 对象来添加 Job 实例。
- JobBuilder :用于构建 JobDetail 实例
- Trigger(触发器):定义任务的调度计划
- TriggerBuilder :用于构建触发器实例
- Scheduler(任务调度控制器):与调度程序交互的主要API
- SchedulerFactory(调度器工厂):创建调度器实例
- DateBuilder :可以很方便地构造表示不同时间点的 java.util.Date 实例
- Calendar:一些日历特定时间点的集合。一个 trigger 可以包含多个 Calendar,以便排除或包含某些时间点
Job(自定义任务逻辑类)
实现 Job 接口
任务类是一个实现 org.quartz.Job 接口的类,且任务类必须含有空构造器。
当关联这个任务实例的的一个 trigger(触发器)被触发后,execute() 方法会被 scheduler(调度器)的一个工作线程调用。
代码示例:
public class QuartzJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 任务的具体逻辑
}
}
JobExecutionContext
传递给 execute() 方法的 JobExecutionContext 对象
- 保存着该 job 运行时的一些信息 ,包括执行 job 的 scheduler 对象的引用,触发 job 的 trigger 对象的引用,JobDetail 对象的引用等。
API 方法:
Scheduler getScheduler(); Trigger getTrigger(); Calendar getCalendar(); boolean isRecovering(); TriggerKey getRecoveringTriggerKey(); int getRefireCount(); JobDataMap getMergedJobDataMap(); JobDetail getJobDetail(); Job getJobInstance(); Date getFireTime(); Date getScheduledFireTime(); Date getPreviousFireTime(); Date getNextFireTime(); String getFireInstanceId(); Object getResult(); void setResult(Object var1); long getJobRunTime();
获取 JobDataMap 的键值
- 方法1:在 Job 实现类中通过 context 先获取 JobDetail 和 Trigger 对象,再从这两个对象中获取 JobDataMap 对象
- 方法2:在 Job 实现类中通过 context 直接获取 MergedJobDataMap(存储着 jobdetail 和 trigger 合并后的键值)
需保证 jobDetail 和 Trigger 的 JobDataMap 中无重复 key。
因为当有相同的 key 时,是先获取 jobdetail 中的,再获取 trigger 中的,同名 key 的值会被覆盖
- 方法3:Job 实现类中定义 JobDataMap 中 key 同名的属性,并添加 Setter 方法
类自动注入,底层将使用反射直接将 scheduler 中的 jobdetail 和 trigger 中的 jobDataMap 中的键值,通过 Setter 方法注入对应字段(名称要相同)
需保证 jobDetail 和 Trigger 的 JobDataMap 中无重复 key。原因同上
JobDetail(任务详情)
包含 job 的各种属性设置,以及用于存储 job 对象数据的 JobDataMap。
其实现类的属性:
private String name; // 任务名称,同分组必须唯一
private String group="DEFAULT"; // 任务分组
private String description; // 任务描述
private Class<? extends Job> jobClass; // 任务类
private JobDataMap jobDataMap; // 任务数据
private boolean durability=false; // 在没有 Trigger 关联的条件下是否保留
private boolean shouldRecover=false; // 当任务执行期间,程序被异常终止,程序再次启动时是否再次执行该任务
private transient JobKey key; // 任务唯一标识,包含任务名称和任务分组信息
接口 API 方法:
JobKey getKey();
String getDescription();
Class<? extends Job> getJobClass();
JobDataMap getJobDataMap();
boolean isDurable(); // 获取 durability 的值
boolean isPersistJobDataAfterExecution(); // 任务调用后是否保存任务数据(JobData)
boolean isConcurrentExectionDisallowed(); // 获取任务类是否标注了 @DisallowConcurrentExecution 注解
boolean requestsRecovery(); // 获取 shouldRecover 的值
Object clone(); // 克隆一个 JobDetail 对象
JobBuilder getJobBuilder();
JobBuilder
JobBuilder 用于构建 JobDetail 实例
API 方法:
// 创建一个 JobBuilder 对象
public static JobBuilder newJob()
public static JobBuilder newJob(Class<? extends Job> jobClass)
// 设置任务的名称、分组、唯一标识。不指定则会自动生成任务的名称、唯一标识,分组默认DEFUALT
public JobBuilder withIdentity(String name)
public JobBuilder withIdentity(String name, String group)
public JobBuilder withIdentity(JobKey jobKey)
public JobBuilder withDescription(String jobDescription) // 设置任务描述
public JobBuilder ofType(Class<? extends Job> jobClazz) // 设置任务类
public JobBuilder requestRecovery() // 设置 shouldRecover=true
public JobBuilder requestRecovery(boolean jobShouldRecover)
public JobBuilder storeDurably() // 设置 durability=true
public JobBuilder storeDurably(boolean jobDurability)
public JobBuilder usingJobData(String dataKey, String value) // jobDataMap.put(dataKey, value)
public JobBuilder usingJobData(JobDataMap newJobDataMap) // jobDataMap.putAll(newJobDataMap)
public JobBuilder setJobData(JobDataMap newJobDataMap) // jobDataMap=newJobDataMap
public JobDetail build()
Trigger(触发器)
触发器有很多属性,这些属性都是在使用 TriggerBuilder 构建触发器时设置的。
Trigger 公有属性:
/* Trigger 接口中定义 */
int MISFIRE_INSTRUCTION_SMART_POLICY = 0; // 默认Misfire策略-智能
int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1; // Misfire策略-忽略错过的触发
int DEFAULT_PRIORITY = 5; // 默认触发器优先级
/* AbstractTrigger 抽象类中定义 */
private String name; // 触发器名称,同分组必须唯一
private String group = "DEFAULT"; // 触发器分组
private String jobName; // 任务名称
private String jobGroup = "DEFAULT"; // 任务分组
private String description; // 触发器描述
private JobDataMap jobDataMap; // 任务数据,用于给 Job 传递一些触发相关的参数
private boolean volatility = false; // 表示该触发器是否被持久化到数据库存储
private String calendarName;
private String fireInstanceId;
private int misfireInstruction = 0;
private int priority = 5; // 触发器优先级
private transient TriggerKey key; // 触发器唯一标识,在一个 Scheduler 中必须是唯一的。
// 多个触发器可以指向同一个工作,但一个触发器只能指向一个工作。
/* 各触发器实现类中定义 */
private Date startTime; // 触发器的首次触时间
private Date endTime; // 触发器的末次触时间,设置了结束时间则在这之后,不再触发
private Date nextFireTime; // 触发器的下一次触发时间
private Date previousFireTime; // 触发器的本次触发时间
Trigger 接口 API 方法:
TriggerKey getKey();
JobKey getJobKey();
String getDescription();
String getCalendarName();
JobDataMap getJobDataMap();
int getPriority();
boolean mayFireAgain();
Date getStartTime();
Date getEndTime();
Date getNextFireTime();
Date getPreviousFireTime();
Date getFireTimeAfter(Date var1);
Date getFinalFireTime();
int getMisfireInstruction();
int compareTo(Trigger var1);
TriggerBuilder<? extends Trigger> getTriggerBuilder();
ScheduleBuilder<? extends Trigger> getScheduleBuilder();
TriggerBuilder
TriggerBuilder 用于构建 Trigger 实例
TriggerBuilder(以及 Quartz 的其它 builder)会为那些没有被显式设置的属性选择合理的默认值。
例如:
- 如果没有调用 withIdentity(..) 方法,TriggerBuilder 会为 trigger 生成一个随机的名称
- 如果没有调用 startAt(..) 方法,则默认使用当前时间,即trigger立即生效
public static TriggerBuilder<Trigger> newTrigger()
// 设置触发器的名称、分组、唯一标识,不指定则会自动生成触发器的名称、唯一标识,分组默认DEFUALT
public TriggerBuilder<T> withIdentity(String name)
public TriggerBuilder<T> withIdentity(String name, String group)
public TriggerBuilder<T> withIdentity(TriggerKey triggerKey)
public TriggerBuilder<T> withDescription(String triggerDescription)
public TriggerBuilder<T> withPriority(int triggerPriority)
public TriggerBuilder<T> modifiedByCalendar(String calName)
// 开始时间默认 new Date(),结束时间默认 null
public TriggerBuilder<T> startAt(Date triggerStartTime)
public TriggerBuilder<T> startNow()
public TriggerBuilder<T> endAt(Date triggerEndTime)
// 设置触发器的类别,默认是只执行一次的 SimpleTrigger
public <SBT extends T> TriggerBuilder<SBT> withSchedule(ScheduleBuilder<SBT> schedBuilder)
public TriggerBuilder<T> forJob(JobKey keyOfJobToFire)
public TriggerBuilder<T> forJob(String jobName)
public TriggerBuilder<T> forJob(String jobName, String jobGroup)
public TriggerBuilder<T> forJob(JobDetail jobDetail)
public TriggerBuilder<T> usingJobData(String dataKey, String value)
public TriggerBuilder<T> usingJobData(JobDataMap newJobDataMap)
public T build()
Scheduler(任务调度控制器)
接口常用 API 方法:
void start() // 调度器开始工作
void shutdown() // 关闭调度器
void shutdown(boolean waitForJobsToComplete) // 等待所有正在执行的 job 执行完毕后才关闭调度器
void pauseAll() // 暂停调度器
void resumeAll() // 恢复调度器
// 绑定/删除触发器和任务
Date scheduleJob(Trigger trigger)
Date scheduleJob(JobDetail jobDetail, Trigger trigger)
void scheduleJob(JobDetail jobDetail, Set<? extends Trigger> triggersForJob, boolean replace)
void scheduleJobs(Map<JobDetail, Set<? extends Trigger>> triggersAndJobs, boolean replace)
void addJob(JobDetail jobDetail, boolean replace)
boolean deleteJobs(List<JobKey> jobKeys)
boolean unscheduleJob(TriggerKey triggerKey)
SchedulerFactory(调度器工厂)
常用 API 方法:
// 构造方法
public StdSchedulerFactory()
public StdSchedulerFactory(Properties props)
public StdSchedulerFactory(String fileName)
// 获取一个默认的标准调度器
public static Scheduler getDefaultScheduler()
// 获取一个调度器。若调度器工厂未初始化,则获取一个默认的标准调度器
public Scheduler getScheduler()
// 初始化调度器工厂
public void initialize() // 使用quartz的jar包中默认的quartz.properties文件初始化(默认的标准调度器)
public void initialize(Properties props)
public void initialize(String filename)
public void initialize(InputStream propertiesStream)
核心模块详解
参考:
Job类
Job 类型 有两种:
- 无状态的(stateless):默认,每次调用时都会创建一个新的 JobDataMap
- 有状态的(stateful):Job 实现类上标注 @PersistJobDataAfterExecution 注解
多次 job 调用期间可以持有一些状态信息,这些状态信息存储在 JobDataMap 中
Job 实例的生命周期
- 每次当 scheduler 执行 job 时,在调用其 execute(…) 方法之前会创建该类的一个新的实例;
- 任务执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;
基于这种执行策略,job 必须有一个无参的构造函数(当使用默认的 JobFactory 时创建 job 实例的调用)另外,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。
若想 job 实例增加属性或配置,在 job 的多次执行中跟踪 job 的状态,则可以使用 JobDataMap( JobDetail 对象的一部分)
@DisallowConcurrentExecution 注解:禁止并发执行
- Quartz 定时任务默认都是并发执行的,不会等待上一次任务执行完毕,只要间隔时间到就会执行,如果定时任执行太长,会长时间占用资源,导致其它任务堵塞。
- 通过在 Job 实现类上标注 @DisallowConcurrentExecution 注解来实现阻止并发。
@DisallowConcurrentExecution 注解禁止并发执行多个相同定义的 JobDetail, 这个注解是加在 Job 类上的, 但意思并不是不能同时执行多个 Job, 而是不能并发执行同一个 Job Definition(由JobDetail定义), 但是可以同时执行多个不同的 JobDetail。
举例说明:有一个Job类,叫做 SayHelloJob, 并在这个 Job 上加了这个注解, 然后在这个 Job 上定义了很多个 JobDetail, 如sayHelloToJoeJobDetail, sayHelloToMikeJobDetail, 那么当 scheduler 启动时, 不会并发执行多个 sayHelloToJoeJobDetail 或者sayHelloToMikeJobDetail, 但可以同时执行 sayHelloToJoeJobDetail 跟 sayHelloToMikeJobDetail。
测试代码:设定的时间间隔为3秒,但job执行时间是5秒,设置 @DisallowConcurrentExecution以 后程序会等任务执行完毕以后再去执行,否则会在3秒时再启用新的线程执行。
@PersistJobDataAfterExecution 注解:保存 JobDataMap
- 该注解加在 Job 实现类上。表示当正常执行完 Job 后, JobDataMap 中的数据应该被改动, 以被下一次调用时用。
- 注意:当使用 @PersistJobDataAfterExecution 注解时, 为了避免并发时, 存储数据造成混乱, 强烈建议把 @DisallowConcurrentExecution 注解也加上。
JobDetail(任务详情)
可以只创建一个 job 类,然后创建多个与该 job 关联的 JobDetail 实例,每一个实例都有自己的属性集和 JobDataMap,最后,将所有的实例都加到 scheduler 中。
当一个 trigger 被触发时,与之关联的 JobDetail 实例会被加载,JobDetail 引用的 job 类通过配置在 Scheduler 上的 JobFactory 进行初始化。
- 默认的 JobFactory 实现,仅仅是调用 job 类的 newInstance() 方法,然后尝试调用 JobDataMap 中的 key 的 setter 方法
- 也可以自定义 JobFactory 实现,比如让 IOC 或 DI 容器可以创建/初始化 job 实例
Job 和 JobDetail 的描述名称
- 在 Quartz 的描述语言中,通常将保存后的 JobDetail 称为” job 定义”或者“ JobDetail 实例”,将一个正在执行的 job 称为“ job 实例”或者“ job 定义的实例”。
- 当使用 “job” 时,一般指代的是 job 定义或者 JobDetail
- 当提到实现 Job 接口的类时,通常使用“job类”
通过 JobDetail 对象,可以给 job 实例配置的其它属性有:
- durability 属性:表示在没有 Trigger 关联的条件下是否保留,boolean 类型
如果一个 JobDetail 是非持久的,当没有活跃的 trigger 与之关联的时候,会被自动地从 scheduler 中删除。
也就是说,非持久的 JobDetail 的生命周期是由 trigger 的存在与否决定的
- requestsRecovery 属性:标识当任务执行期间,程序被异常终止,程序再次启动时是否再次执行该任务,boolean 类型
如果一个 job 是可恢复的,并且在其执行的时候,scheduler发生硬关闭(比如运行的进程崩溃了,或者关机了),则当 scheduler 重新启动的时候,该 job 会被重新执行。
Trigger(触发器)
概述
用于触发 Job 的执行。当准备调度一个 job 时,需要创建一个 Trigger 的实例,并设置调度相关的属性。
注意:一个 Trigger 只能绑定一个 JobDetail,而一个 JobDetail 可以和多个 Trigger 绑定。
触发器的优先级(priority)
如果 trigger 很多(或者 Quartz 线程池的工作线程太少),Quartz 可能没有足够的资源同时触发所有的 trigger,这种情况下,可以通过设置 trigger 的 priority(优先级) 属性来优先使用 Quartz 的工作线程。
- 优先级只有触发器的触发时间一样的时候才有意义
- 触发器的优先级值默认为5
- 当一个任务请求恢复执行时,它的优先级和原始优先级是一样的
错过触发(misfire Instructions)
- 如果 scheduler 关闭了,或者 Quartz 线程池中没有可用的线程来执行 job,此时持久性的 trigger 就会错过(miss)其触发时间,即错过触发(misfire)。
- 不同类型的 trigger,有不同的 misfire 机制。默认都使用 “智能机制(smart policy)”,即根据 trigger 的类型和配置动态调整行为。
- 当 scheduler 启动的时候,查询所有错过触发(misfire)的持久性 trigger;然后根据它们各自的 misfire 机制更新 trigger 的信息。
触发器的分类
Quartz 自带了各种不同类型的 Trigger,最常用的主要是 SimpleTrigger 和 CronTrigger。
触发器实例通过 TriggerBuilder 设置主要的属性,通过 TriggerBuilder.withSchedule() 方法设置各种不同类型触发器的特有属性。
SimpleTrigger(简单触发器)
一种最基本的触发器。在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。
SimpleTrigger 的属性包括开始时间、结束时间、重复次数以及重复的间隔
- repeatInterval 属性:重复间隔,默认为0
- repeatCount 属性:重复次数,默认为0,实际执行次数是 repeatCount + 1
注意:
- 如果重复间隔为0,trigger 将会以重复次数并发执行(或者以 scheduler 可以处理的近似并发数)
- endTime 属性的值会覆盖设置重复次数的属性值
即 可以创建一个 trigger,在终止时间之前每隔10秒执行一次,并不需要去计算在开始时间和终止时间之间的重复次数,只需要设置终止时间并将重复次数设置为 REPEAT_INDEFINITELY(无期限重复)
Trigger trigger = TriggerBuilder.newTrigger() .withSchedule( SimpleScheduleBuilder.simpleSchedule() //.withIntervalInHours(1) // 每小时执行一次 //.withIntervalInMinutes(1) // 每分钟执行一次 .repeatForever() // 次数不限 .withRepeatCount(10) // 次数为10次 ) .build();
SimpleTrigger 的 Misfire 策略常量:
// 触发器默认Misfire策略常量,SimpleTrigger会根据实例的配置及状态,在所有MISFIRE策略中动态选择一种Misfire策略 int MISFIRE_INSTRUCTION_SMART_POLICY = 0; int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1; int MISFIRE_INSTRUCTION_FIRE_NOW = 1; int MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT = 2; int MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT = 3; int MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT = 4; int MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT = 5;
CronTrigger(基于 cron 表达式的触发器)
Trigger trigger = TriggerBuilder.newTrigger() .withSchedule(CronScheduleBuilder.cronSchedule("2 * * * * *")) .build();
CronTrigger 的 Misfire 策略常量:
// 触发器默认Misfire策略常量,由CronTrigger解释为MISFIRE_INSTRUCTION_FIRE_NOW int MISFIRE_INSTRUCTION_SMART_POLICY = 0; int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1; int MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1; int MISFIRE_INSTRUCTION_DO_NOTHING = 2;
CalendarIntervalTrigger(日历时间间隔触发器)
与 SimpleTrigger 类似, 但不同的是,SimpleTrigger 指定的时间间隔底层为毫秒,CalendarIntervalTrigger 支持的间隔单位有秒,分钟,小时,天,月,年,星期。
CalendarIntervalTrigger的属性有:
- interval :执行间隔
- intervalUnit :执行间隔的单位(秒,分钟,小时,天,月,年,星期)
Trigger trigger = TriggerBuilder.newTrigger() .withSchedule( calendarIntervalSchedule() .withIntervalInDays(1) //每天执行一次 //.withIntervalInWeeks(1) //每周执行一次 ) .build();
DailyTimeIntervalTrigger(日常时间间隔触发器)
支持时间间隔类型以及指定星期
常用属性:
- startTimeOfDay :每天开始时间
- endTimeOfDay :每天结束时间
- daysOfWeek :需要执行的星期
- interval :执行间隔
- intervalUnit :执行间隔的单位(秒,分钟,小时,天,月,年,星期)
- repeatCount: 重复次数
Trigger trigger = TriggerBuilder.newTrigger() .withSchedule( dailyTimeIntervalSchedule() .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) // 第天9:00开始 .endingDailyAt(TimeOfDay.hourAndMinuteOfDay(15, 0)) // 15:00 结束 .onDaysOfTheWeek(MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY) // 周一至周五执行 .withIntervalInHours(1) // 每间隔1小时执行一次 .withRepeatCount(100) // 最多重复100次(实际执行100+1次) ) .build();
调度计划中排除时间段
org.quartz.Calendar 对象用于从 trigger 的调度计划中排除时间段。
比如,可以创建一个 trigger,每个工作日的上午 9:30 执行,然后增加一个 Calendar,排除掉所有的商业节日。
任何实现了 org.quartz.Calendar 接口的可序列化对象都可以作为 Quartz Calendar 对象。
Quartz 提供的 Calendar 接口实现类:
- BaseCalender :为高级的 Calender 实现了基本的功能,实现了 org.quartz.Calender 接口
- WeeklyCalendar :排除星期中的一天或多天,例如,可用于排除周末
- MonthlyCalendar :排除月份中的数天,例如,可用于排除每月的最后一天
- AnnualCalendar :排除一年中一天或多天
- HolidayCalendar :特别的用于从 Trigger 中排除节假日
使用方式
- 实例化 Calendar,并加入要排除的日期
- 通过 addCalendar() 方法注册 Calendar 实例到 scheduler
- 在定义 trigger 时,进行 Calendar 实例与 trigger 实例的关联
示例:
// 2014-8-15这一天不执行任何任务
AnnualCalendar cal = new AnnualCalendar();
cal.setDayExcluded(new GregorianCalendar(2014, 7, 15), true);
// 注册日历到调度器
scheduler.addCalendar("excludeCalendar", cal, false, false);
TriggerBuilder.newTrigger().modifiedByCalendar("excludeCalendar")....
Scheduler(调度器)
概述
调度器(Scheduler)是 Quartz 框架的心脏,用来管理触发器和 Job,并保证 Job 能被触发执行。程序员与框架内部之间的调用都是通过 org.quartz.Scheduler 接口来完成的。
对于 Scheduler 接口的实现,其实只是核心调度(org.quartz.core.QuartzScheduler)的一个代理,对代理的方法进行调用时会传递到底层核心调度实例上。QuartzScheduler 处于 Quartz 框架的根位置,驱动着整个 Quartz 框架。
Schedule 种类
有三种:
- StdScheduler(标准默认调度器):最常用
默认值加载是当前工作目录下的”quartz.properties”属性文件。如果加载失败,会去加载 org/quartz 包的”quartz.properties”属性文件。
- RemoteScheduler (远程调度器)
- RemoteMBeanScheduler
Scheduler 有两个重要组件:
- ThreadPool :调度器线程池,所有的任务都会被线程池执行
- JobStore :储运行时信息的,包括Trigger,Scheduler,JobDetail,业务锁等。
JobStore 的实现有 RAMJob(内存实现),JobStoreTX( JDBC,事务由 Quartz 管理),JobStoreCMT( JDBC,使用容器事务),ClusteredJobStore(集群实现)等
注意:Job 和 Trigger 需要存储下来才可以被使用
Schedule 工厂
用于创建 Schedule 实例
种类有两种:
- StdSchedulerFactory(最常用)
- DirectSchedulerFactory
Scheduler 的生命周期
- 从 SchedulerFactory 创建它时开始,到 Scheduler 调用 shutdown() 方法时结束
- Scheduler 被创建后,可以增加、删除和列举 Job 和 Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。
但是,Scheduler 只有在调用 start() 方法后,才会真正地触发 trigger(即执行 job)。
Quartz 线程分类
Scheduler 调度线程
主要有:
- 执行常规调度的线程(Regular Scheduler Thread):轮询查询存储的所有触发器,到达触发时间,就从线程池获取一个空闲的线程,执行与触发器关联的任务
- 执行错失调度的线程(Misfire Scheduler Thread):Misfire 线程扫描所有的触发器,检查是否有 misfired 的线程,也就是没有被执行错过的线程,有的话根据 misfire 的策略分别处理
- 任务执行线程
创建调度器
StdScheduler 只提供了一个带参构造方法,此构造需要传递 QuartzScheduler 和 SchedulingContext 两个实例参数。
一般不使用构造方法去创建调度器,而是通过调度器工厂来创建。
调度器工厂接口 org.quartz.SchedulerFactory 提供了两种不同类型的工厂实现,分别是 org.quartz.impl.DirectSchedulerFactoryh 和 org.quartz.impl.StdSchedulerFactory。
使用 StdSchedulerFactory 工厂创建
此工厂是依赖一系列的属性来决定如何创建调度器实例的。
获取默认的标准调度器:
此工厂提供了无参数的 initialize() 方法进行初始化,此方法本质是加载 quartz 的 jar 包中默认的quartz.properties 属性文件,具体的加载步骤:
- 检查系统属性中是否设置了文件名,通过System.getProperty("org.quartz.properties")
- 如果没有设置,使用默认的quartz.properties作为要加载的文件名
- 然后先从当前工作目录中加载这个文件,如果没有找到,再从系统 classpath 下加载这个文件
- StdSchedulerFactory 工厂还可以不主动调用 initialize() 方法进行初始化,而是直接使用 StdSchedulerFactory 的静态方法 getDefaultScheduler() 获取一个默认的标准调度器。
获取自定义调度器:
自定义属性的提供方式有三种:
- 通过 java.util.Properties 属性实例
- 通过外部属性文件提供
- 通过有属性文件内容的 java.io.InputStream 文件流提供
public static void main(String[] args) { try { StdSchedulerFactory schedulerFactory = new StdSchedulerFactory(); // 第一种方式 通过Properties属性实例创建 Properties props = new Properties(); props.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool"); props.put("org.quartz.threadPool.threadCount", 5); schedulerFactory.initialize(props); // 第二种方式 通过传入文件名 // schedulerFactory.initialize("my.properties"); // 第三种方式 通过传入包含属性内容的文件输入流 // InputStream is = new FileInputStream(new File("my.properties")); // schedulerFactory.initialize(is); // 获取调度器实例 Scheduler scheduler = schedulerFactory.getScheduler(); } catch (Exception e) { e.printStackTrace(); } }
- 第一种方式向工厂传入了两个属性,分别是线程池的类名和线程池大小,这两个属性是必须的,因为若使用自定义Properties 属性初始化,工厂是没有给它们指定默认值的。
- 第二种方式是通过定义一个外部属性文件
底层实现是:首先通过Thread.currentThread().getContextClassLoader().getResourceAsStream(filename) 获取文件流,然后使用 Properties 实例的 load 方法加载文件流形成属性实例,最后在通过 initialize(props) 初始化完成。
- 第三种方式就是直接使用 Properties 实例的 load 方法加载文件流形成属性实例,再在通过 initialize(props) 初始化完成。
使用 DirectSchedulerFactory 工厂创建
此工厂方式创建适用于想绝对控制Scheduler实例的场景。
public static void main(String[] args) { try { DirectSchedulerFactory schedulerFactory = DirectSchedulerFactory.getInstance(); // 表示以3个工作线程初始化工厂 schedulerFactory.createVolatileScheduler(3); Scheduler scheduler = schedulerFactory.getScheduler(); } catch (SchedulerException e) { e.printStackTrace(); } }
创建步骤:
- 通过DirectSchedulerFactory的getInstance方法得到拿到实例
- 调用createXXX方法初始化工厂
- 调用工厂实例的getScheduler方法拿到调度器实例
DirectSchedulerFactory 是通过 createXXX 方法传递配置参数来初始化工厂,这种初始化方式是一种硬编码,在工作中用到的情况会很少。
管理调度器
获取调度器实例后,在调度的生命周期中可以做以下工作,例如:启动调度器,设置调度器为 standby 模式,继续或停止调度器。
- 启动 Scheduler
当调度器初始化完成,并且 Job 和 Trigger 也注册完成,此时就可以调用 scheduler.start() 启动调度器了。
start()方法一旦被调用,调度器就开始搜索需要执行的 Job。
- 设置 standby(待机)模式
设置 Scheduler 为 standby 模式会让调度器暂停寻找 Job 去执行。
应用场景举例:当需要重启数据库时可以先将调度器设置为 standby 模式,待数据库启动完成后再通过 start() 启动调度器。
- 关闭调度器
调用 shutdown() 或 shutdown(boolean waitForJobsToComplete) 方法停止调度器。
shutdown 方法调用后,就不能再调用 start 方法了,因为 shutdown 方法会销毁 Scheduler 创建的所有资源(线程、数据库连接等)。
一般情况下,调度器启动后不需要做其他任何事情。
Job Stores(任务存储)
Quartz 提供两种基本作业存储类型:
- RAMJobStore(内存任务存储):默认情况下 Quartz 会将任务调度存在内存中
这种方式性能是最好的,因为内存的速度是最快的
缺点是数据缺乏持久性,当程序崩溃或者重新发布的时候,所有运行信息都会丢失
- JDBCJobStore(数据库任务存储)
存到数据库之后,可以做单点也可以做集群
当任务多了之后,可以统一进行管理(停止、暂停、修改任务)
关闭或者重启服务器,运行的信息都不会丢失
缺点是运行速度快慢取决于连接数据库的快慢
Quartz 自带有数据库模式
数据库脚本地址:
- https://gitee.com/qianwei4712/code-of-shiva/blob/master/quartz/quartz.sql(带注释,gitee)
- https://lqcoder.com/quartz.sql(不带注释,下载链接)
Table Name | Description |
---|---|
QRTZ_CALENDARS | 存储Quartz的Calendar信息 |
QRTZ_CRON_TRIGGERS | 存储CronTrigger,包括Cron表达式和时区信息 |
QRTZ_FIRED_TRIGGERS | 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息 |
QRTZ_PAUSED_TRIGGER_GRPS | 存储已暂停的Trigger组的信息 |
QRTZ_SCHEDULER_STATE | 存储少量的有关Scheduler的状态信息,和别的Scheduler实例 |
QRTZ_LOCKS | 存储程序的悲观锁的信息 |
QRTZ_JOB_DETAILS | 存储每一个已配置的Job的详细信息 |
QRTZ_JOB_LISTENERS | 存储有关已配置的JobListener的信息 |
QRTZ_SIMPLE_TRIGGERS | 存储简单的Trigger,包括重复次数、间隔、以及已触的次数 |
QRTZ_BLOG_TRIGGERS | Trigger作为Blob类型存储 |
QRTZ_TRIGGER_LISTENERS | 存储已配置的TriggerListener的信息 |
QRTZ_TRIGGERS | 存储已配置的Trigger的信息 |
Listeners(事件监听器)
Listeners:用于根据调度程序中发生的事件执行操作。
Trigger、Job、Scheduler 监听接口
TriggerListeners:接收到与触发器(trigger)相关的事件
发相关的事件包括:触发器触发,触发失灵,触发完成(触发器关闭)
org.quartz.TriggerListener 接口:
public interface TriggerListener { public String getName(); //触发器触发 public void triggerFired(Trigger trigger, JobExecutionContext context); public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context); //触发失灵 public void triggerMisfired(Trigger trigger); //触发器完成 public void triggerComplete(Trigger trigger, JobExecutionContext context, int triggerInstructionCode); }
JobListeners: 接收与 jobs 相关的事件
job 相关事件包括:job 即将执行的通知,以及 job 完成执行时的通知。
org.quartz.JobListener 接口:
public interface JobListener { public String getName(); public void jobToBeExecuted(JobExecutionContext context); public void jobExecutionVetoed(JobExecutionContext context); public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException); }
SchedulerListeners
与计划程序相关的事件包括:添加job/触发器,删除job/触发器,调度程序中的严重错误,关闭调度程序的通知等。
org.quartz.SchedulerListener 接口:
public interface SchedulerListener { public void jobScheduled(Trigger trigger); public void jobUnscheduled(String triggerName, String triggerGroup); public void triggerFinalized(Trigger trigger); public void triggersPaused(String triggerName, String triggerGroup); public void triggersResumed(String triggerName, String triggerGroup); public void jobsPaused(String jobName, String jobGroup); public void jobsResumed(String jobName, String jobGroup); public void schedulerError(String msg, SchedulerException cause); public void schedulerStarted(); public void schedulerInStandbyMode(); public void schedulerShutdown(); public void schedulingDataCleared(); }
自定义 Listeners
步骤:
- 自定义一个实现 org.quartz.TriggerListener 或 JobListener、SchedulerListener 接口的类
为了方便,自定义监听器也可以继承其子接口 JobListenerSupport 或 TriggerListenerSupport、SchedulerListenerSupport 的实现类,并覆盖感兴趣的事件。
- 向调度程序注册监听器,并配置 listener 希望接收事件的 job 或 trigger 的 Matcher
注:listener 不与 jobs 和触发器一起存储在 JobStore 中。这是因为听众通常是与应用程序的集成点,因此,每次运行应用程序时,都需要重新注册该调度程序。
示例:
添加对所有 job 感兴趣的 JobListener:
scheduler.getListenerManager().addJobListener(myJobListener, allJobs());
添加对特定 job 感兴趣的 JobListener:
scheduler.getListenerManager().addJobListener( myJobListener,KeyMatcher.jobKeyEquals(new JobKey("myJobName","myJobGroup")));
添加对特定组的所有 job 感兴趣的 JobListener:
scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));
添加对两个特定组的所有 job 感兴趣的 JobListener:
scheduler.getListenerManager().addJobListener( myJobListener, or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup")));
添加 SchedulerListener:
scheduler.getListenerManager().addSchedulerListener(mySchedListener);
删除 SchedulerListener:
scheduler.getListenerManager().removeSchedulerListener(mySchedListener);
SchedulerFactoryBean(Spring集成)
参考:
Quartz 的 SchedulerFactory 是标准的工厂类,不太适合在 Spring 环境下使用。
为了保证 Scheduler 能够感知 Spring 容器的生命周期,完成自动启动和关闭的操作,必须让 Scheduler 和 Spring 容器的生命周期相关联。以便在 Spring 容器启动后,Scheduler 自动开始工作,而在 Spring 容器关闭前,自动关闭 Scheduler。为此,Spring 提供了 SchedulerFactoryBean,这个 FactoryBean 大致拥有以下的功能:
- 以更具 Bean 风格的方式为 Scheduler 提供配置信息
- 让 Scheduler 和 Spring 容器的生命周期建立关联,相生相息
- 通过属性配置部分或全部代替 Quartz 自身的配置文件
SchedulerFactoryBean 属性介绍:
- autoStartup 属性:在 SchedulerFactoryBean 在初始化后是否马上启动 Scheduler,默认为 true
如果设置为 false,需要手工启动 Scheduler
- startupDelay 属性:在 SchedulerFactoryBean 初始化完成后,延迟多少秒启动 Scheduler,默认为0,表示马上启动。
如果并非马上拥有需要执行的任务,可通过 startupDelay 属性让 Scheduler 延迟一小段时间后启动,以便让 Spring 能够更快初始化容器中剩余的Bean
- triggers 属性:类型为 Trigger[] ,可以通过该属性注册多个 Trigger
- calendars 属性:类型为 Map,通过该属性向Scheduler注册 Calendar
- jobDetails 属性:类型为 JobDetail[],通过该属性向 Scheduler 注册 JobDetail
- schedulerContextMap 属性:可以通过该属性向 Scheduler context 中存一些数据
spring 容器中的 bean 只能通过 SchedulerFactoryBean 的 setSchedulerContextAsMap() 方法放到 SchedulerContext 里面传入 job 中,在 job 中通过 JobExecutionContext.getScheduler().getContext() 获取存入的 bean
SchedulerFactoryBean 的一个重要功能是允许将 Quartz 配置文件中的信息转移到 Spring 配置文件中
带来的好处是,配置信息的集中化管理,同时开发者不必熟悉多种框架的配置文件结构。回忆一下 Spring 集成 JPA、Hibernate 框架,就知道这是 Spring 在集成第三方框架经常采用的招数之一。
SchedulerFactoryBean 通过以下属性代替框架的自身配置文件:
dataSource 属性:当需要使用数据库来持久化任务调度数据时,可以在 Quartz 中配置数据源
也可以直接在 Spring 中通过 setDataSource() 方法指定一个 Spring 管理的数据源
如果指定了该属性,即使 quartz.properties 中已经定义了数据源,也会被此 dataSource 覆盖
配置好数据源 dataSource 并启动应用后,会自动在 Quartz 的QRTZ_LOCKS表中插入以下数据:
INSERT INTO QRTZ_LOCKS values('TRIGGER_ACCESS'); INSERT INTO QRTZ_LOCKS values('JOB_ACCESS');
- transactionManager 属性:可以通过该属性设置一个 Spring 事务管理器。
在设置 dataSource 时,Spring 强烈推荐使用一个事务管理器,否则数据表锁定可能不能正常工作;
- nonTransactionalDataSource 属性:在全局事务的情况下,如果不希望 Scheduler 执行化数据操作参与到全局事务中,则可以通过该属性指定数据源。
在 Spring 本地事务的情况下,使用 dataSource 属性就足够了
- quartzProperties 属性:类型为 Properties,允许开发者在 Spring 中定义 Quartz 的属性。
其值将覆盖 quartz.properties 配置文件中的设置,这些属性必须是 Quartz 能够识别的合法属性,在配置时,可以需要查看 Quartz 的相关文档。
SchedulerFactoryBean 实现了 InitializingBean 接口
因此在初始化 bean 的时候,会执行 afterPropertiesSet() 方法,该方法将会调用 SchedulerFactory(DirectSchedulerFactory 或者 StdSchedulerFactory) 方法创建 Scheduler,通常用 StdSchedulerFactory 。
SchedulerFactory 在创建 quartzScheduler 的过程中,将会读取配置参数,初始化各个组件,关键组件如下:
ThreadPool :一般是使用 SimpleThreadPool
SimpleThreadPool 创建了一定数量的 WorkerThread 实例来使得 Job 能够在线程中进行处理。
- WorkerThread 是定义在 SimpleThreadPool 类中的内部类,它实质上就是一个线程
在 SimpleThreadPool 中有三个 list:
- workers :存放池中所有的线程引用
- availWorkers :存放所有空闲的线程
- busyWorkers :存放所有工作中的线程
线程池的配置参数如下所示:
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount=3 org.quartz.threadPool.threadPriority=5
JobStore :分为存储在内存的 RAMJobStore 和存储在数据库的 JobStoreSupport
JobStoreSupport 包括 JobStoreTX 和 JobStoreCMT 两种实现
- JobStoreCMT 是依赖于容器来进行事务的管理
- JobStoreTX 是自己管理事务
若要使用集群则要使用 JobStoreSupport 的方式
- QuartzSchedulerThread :用来进行任务调度的线程
在初始化的时候 paused=true,halted=false。即 虽然线程开始运行了,但是 paused=true,线程会一直等待,直到调用 start() 方法将 paused 设置为false
SchedulerFactoryBean 实现了 SmartLifeCycle 接口
因此初始化完成后,会执行 start() 方法,该方法将主要会执行以下的几个动作:
- 创建 ClusterManager 线程并启动线程:该线程用来进行集群故障检测和处理
- 创建 MisfireHandler 线程并启动线程:该线程用来进行 misfire 任务的处理
- 设置 QuartzSchedulerThread 的 paused=false,调度线程才真正开始调度
SchedulerFactoryBean 整个启动流程如下图: