基本介绍
一、简介
Quartz是一个优秀的开源任务调度框架,完全基于Java实现,作用相当于一个定时器,可以在指定的时间点或时间间隔执行任务。可以使用在如”月底总结”,”每日结算”等需要在指定时间执行任务的需求中。
二、特点
- 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求。
- 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式。
- 分布式和集群能力,quartz能够使用多个单独quartz程序作为节点共用一套数据库表来实现集群
核心接口
先来一波Quartz核心关系图
一、任务调度器-Scheduler
1.作用
Scheduler
可以进行增加、删除及显示任务Job
与触发器Trigger
,并且可以进行如开始、暂停一个触发器Trigger
等一系列与调度相关的工作。
2.创建
创建Scheduler
需要通过StdSchedulerFactory
或DirectSchedulerFactory
工厂来创建。由于DirectSchedulerFactory
需要详细的手工编码来进行设置,使用起来不太方便,所以一般情况下会通过StdSchedulerFactory
来创建任务调度器。
3.生命周期
一个调度器的生命周期为通过SchedulerFactory
创建,直到执行其shutdown()
方法。
4工厂配置
StdSchedulerFactory
用于创建Scheduler
,其依赖于一系列的属性来决定如何产生Scheduler
,而为其配置属性的常用方式有以下两种
- quartz.properties配置文件
通过使用quartz.properties配置文件来为StdSchedulerFactory
提供配置信息,这也是推荐使用的一种方式,方便后期维护。
当调用StdSchedulerFactory
的无参initialize
方法,StdSchedulerFactory
会试图从quartz.properties文件中加载。有一定的加载顺序:首先会检查System.getProperty("org.quartz.properties")
中是否有设置其他属性文件名,如果没有,尝试从当前工作目录中加载quartz.properties文件,如果没找到则会到项目的classpath中加载quartz.properties文件。如果还是没有,Quartz将自动加载jar包中默认的quartz.properties文件。简而言之一般直接将quartz.properties文件放到资源文件目录下即可。
关于quartz.properties配置文件的详细说明在第六章《配置详解》中
Properties
对象
调用StdSchedulerFactory
的有参initialize(properties)
方法,通过java提供的Properties
对象来完成对调度工厂的属性配置,劣势在于属性都用硬编码的方式写死在代码中,难于维护
- 其他方法
此外还能通过属性文件或者属性文件的InputStream来进行属性配置,不常用就不说了
5.代码示例
这里为了方便,使用Properties
对象的方式进行演示
public static Scheduler createScheduler(JobDetail jobDetail, Trigger trigger){
//创建调度工厂
StdSchedulerFactory factory = new StdSchedulerFactory();
//创建属性对象
Properties props = new Properties();
//线程池定义
props.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool");
//设置Scheduler的线程数
props.put("org.quartz.threadPool.threadCount", "10");
Scheduler scheduler = null;
try {
//进行属性初始化
factory.initialize(props);
//创建Scheduler
scheduler = factory.getScheduler();
//设置触发器和执行的任务详情
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
e.printStackTrace();
}
return scheduler;
}
之后通过获取到的Scheduler
对象调用start()
和shutdown()
进行任务的开始和结束
二、触发器-Trigger
Trigger
是用于定义调度时间的元素,即按照什么时间规则去执行任务。Quartz中主要提供了四种类型的Trigger
:SimpleTrigger
,CronTirgger
,DateIntervalTrigger
,和NthIncludedDayTrigger
。项目中使用较多的是SimpleTrigger
和CronTirgger
,其中SimpleTrigger
与java中提供的Timer
类似,无法指定精确的执行日期,只能指定从某一个时间开始,以一定的时间间隔执行任务。而CronTirgger
即可以指定简单的时间间隔,也可以指定精确日期,基本上满足一般的业务需求。
1.屬性
Trigger
中有一些共同属性,这些属性可以通过TriggerBuilder
来进行设置。主要有以下共同属性:
- triggerKey:触发器的唯一标识,由名称和组名联合组成,便于调度器查找、调用。
- jobKey:任务的唯一标识,当触发器被触发时,确定应该执行哪个任务
- startTime:触发器第一次触发的开始时间
- endTime:触发器结束时间
2.Calendars
Quartz提供了与Trigger
关联的Calendars
对象,当你希望在某一时间不想去触发触发器时,可以使用Calendars
对象。Calendars
需要在Scheduler
定义过程中,通过scheduler.addCalendar()
进行初始化和注册。
3.未启动指令
未启动指令用于当Trigger
未正常触发时,是否恢复执行的场景,默认会使用smart policy
指令。
当Scheduler
开启时,它将搜索所有未启动的持久化的触发器,然后基于触发器各自配置的”未启动指令”来更新触发器。
4.优先级
当在同一时间存在多个触发器时,Quartz可能没有足够的资源立即触发所有的触发器,我们可以通过设置每个Trigger
的优先级来指定优先触发哪个触发器,默认的优先级为5,可取任意的整型值,包括正数或负数。
5.SimpleTrigger
SimpleTrigger
用于规定时间执行一次或在规定的时间段以一定的时间间隔重复触发执行任务,主要属性有:开始时间、结束时间(优先于重复次数)、重复次数、重复时间间隔。
6.CronTrigger
CronTrigger
支持复杂的调度。基于cron表达式,CronTrigger
支持类似日历的重复间隔,而非单一的时间间隔。
7.代码示例
public class MyTriggers {
/**
* 示例
* 设置公共属性
*/
Trigger trigger = newTrigger()
//设置触发器标识
.withIdentity("myTrigger")
//设置触发器优先级
.withPriority(1)
//设置触发时间,dailyAtHourAndMinute为cronTriggerBuilder的方法,每天09:30
.withSchedule(dailyAtHourAndMinute(9,30)
//设置未启动指令
.withMisfireHandlingInstructionFireAndProceed())
//设置执行的任务
.forJob("myJob")
//排除myHolidays中设置的日期
.modifiedByCalendar("myHolidays")
//创建触发器
.build();
/**
* 示例
* 通过TriggerBuilder创建SimpleTrigger
*/
Trigger simpleTrigger = newTrigger()
//通过名字、组名唯一标识
.withIdentity("simpleTrigger","groupOne")
//设置开始时间,可为0,表示触发则执行
.startAt(futureDate(5, DateBuilder.IntervalUnit.MINUTE))
//设置SimpleTrigger调度属性
.withSchedule(simpleSchedule()
//设置间隔时间为1分
.withIntervalInSeconds(1)
//设置重复次数20次
.withRepeatCount(20))
//设置结束时间
.endAt(dateOf(11, 0, 0))
//指定执行的任务
.forJob("simpleJob", "groupOne")
//创建触发器
.build();
/**
* 示例
* 通过TriggerBuilder创建CronTrigger
*/
Trigger cronTrigger = newTrigger()
//设置触发器唯一标识
.withIdentity("cronTrigger", "groupOne")
//设置开始时间
.startNow()
//设置CronTrigger属性
.withSchedule(cronSchedule("0 42 10 * * ?")
//设置时间区域
.inTimeZone(TimeZone.getTimeZone("Asia/Shanghai")))
//设置执行任务
.forJob("cronJob", "groupOne")
//创建触发器
.build();
}
三、任务-Job
Job
即为调度任务中需要执行的任务类,将需要调度的Java类继承Job
并将需要实现的功能放在其execute
方法中即可将该Java类作为一个Job
使用
1.execute
Job
接口中提供了一个execute(JobExecutionContext context)
方法,其中JobExecutionContext
对象让Job
能访问Quartz运行环境的所有信息和Job本身的明细数据,即Scheduler
上与该Job
相关联的JobDetail
和Trigger
2.种类&属性
Job
主要有两种类型的Job
:无状态的和有状态的。对于同一个trigger
来说,有状态的job
不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。有状态的Job可以理解为多次Job
调用期间可以持有一些状态信息,这些状态信息存储在JobDataMap
中,而默认的无状态Job
每次调用时都会创建一个新的JobDataMap
。
Job
主要有两种属性:volatility
和durability
,其中volatility
表示任务是否被持久化到数据库存储,而durability
表示在没有trigger
关联的时候任务是否被保留。两者都是在值为true的时候任务被持久化或保留。一个job
可以被多个trigger
关联,但是一个trigger
只能关联一个job
。
3.注解
继承了Job
的类上常用的注解为@PersistJobDataAfterExecution
和@DisallowConcurrentExecution
。
@PersistJobDataAfterExecution
的作用在于持久化保存在JobDataMap
中的传递参数,使得多次执行Job
,可以获取传递参数的状态信息。@DisallowConcurrentExecution
的作用是同一个时刻,同一个任务只能执行一次,不能并行执行多个同一任务。但多个不同的任务是可以同时执行的。
4.代码示例
public class MyJob implements Job {
private static final Logger logger = LoggerFactory.getLogger(MyJob.class);
public void execute(JobExecutionContext context){
//获取任务名和任务组名
JobDetail jobDetail = context.getJobDetail();
logger.info("Job name and group:" + jobDetail.getKey());
//获取Job状态信息
Integer count = (Integer)jobDetail.getJobDataMap().get("count");
logger.info("Count:" + count);
//修改状态信息,测试@PersistJobDataAfterExecution带来的持久化效果
jobDetail.getJobDataMap().put("count", count + 1);
//获取调度器名
Scheduler scheduler = context.getScheduler();
try {
logger.info("Scheduler name:" + scheduler.getSchedulerName());
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
四、任务详情-JobDetail
JobDetail
是作为Job
实例进行定义的,部署在Scheduler
上的每一个Job
只创建一个JobDetail
实例。
注意!注册到Scheduler
上的不是Job
对象,而是JobDetail
实例。Job
的实例要到该执行它们的时候才会实例化出来。每次Job
被执行,一个新的Job
实例会被创建。
1.JobDataMap
可以使用JobDataMap
来定义Job
的状态,JobDataMap
中可以存入key-value对,这些数据可以在Job
实现类中进行传递和访问。这是向Job
传送配置信息的便捷方法。
Job
能通过JobExecutionContext
对象获取JobDetail
对象访问JobDataMap
。
2.代码示例
public static JobDetail createJobDetail() {
//通过JobBuilder创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
//设置任务名和任务组名组成任务唯一标识
.withIdentity("myJob", "groupOne")
//使用Job状态
.usingJobData("count", 0)
//构建JobDetail
.build();
return jobDetail;
}