Quartz学习笔记
简介
Quartz在结构上由三部分组成:工作(Job),触发器(Trigger),调度器(Scheduler)。
- Job指定工作内容 。
- Trigger指定工作的触发机制 。
- Scheduler = Job + Trigger 即 根据触发机制调用Job。
Job
public class MyJob implement Job{
@Override
public void execute(JobExecutionContext context) throws JobExecutionException{
}
}
Job接口只有一个“execute”方法需要实现,该方法指定工作内容。该方法有一个JobExecutionContext参数,这个参数保存着该Job运行时的一些信息,比如:
- 调度Job的scheduler引用 。
- 触发Job的trigger引用 。
- JobDetail对象的引用(稍后详解)。
- 以及一些其他信息。
execute方法中仅允许抛出一种类型的异常即JobExecutionException,你的Job可以使用异常告诉scheduler,你希望如何来处理发生的异常。
注解
在Job类上可以加一些注解,这些注解会影响到Job的状态和并发性:
- @DisallowConcurrentExecution:告诉Quartz不要并发地执行同一个Job定义的多个实例。(如果Job类只有一个实例,可以并发地执行该实例)。
- @PersistJobDataAfterExecution:告诉Quartz在成功执行了Job类的execute方法后,更新JobDetail中JobDataMap的数据。此注解最好与上面的注解一起使用,从而避免并发读写的相关问题。
其他特性
通过JobDetail对象,可以给Job实例配置的其他属性有:
- Durability:如果一个Job是非持久的,当没有活跃的trigger与之关联的时候,会自动地从scheduler中删除。
- RequestsRecovery:如果一个Job是可恢复的,并且在其执行的时候,scheduler发生硬关闭,则当scheduler重启时,该Job会被重新执行。
Scheduler
首先创建Scheduler工厂
SchedulerFactory factory = new StdSchedulerFactory();
通过Scheduler工厂获得Scheduler实例
Scheduler scheduler = factory.getScheduler();
Scheduler实例化后,拥有三种状态:启动(start),暂停(stand-by),停止(shutdown)。
Scheduler被停止后,除非重新实例化,否则不能重新启动.只有当Scheduler启动后(即便处于暂停状态也不行),trigger才会被触发(job才会被执行)。
JobDetail
JobDetail对象用来将Job加入到scheduler:
JobDetail jobdetail = JobBuilder.newJob(Myjob.class)
.withIdentity(“myjob”,”group1”) //name = myjob, group = group1
.usingJobDate(“jobSays”,”HelloWorld”)
.build();
这里的JobBuilder用于构建JobDetail实例。
JobDetail对象中可以包含对Job的各种属性设置,以及用于存储Job实例状态信息的JobDataMap:
- 上面的withIdentity()指定了该Job的Key(由name和group组成)。
- 上面的usingJobDate()为该Job添加JobDataMap。
JobDataMap
JobDataMap是Java Map接口的一个实现。
应用场景:首先来了解一下Job实例的生命期,我们传给scheduler一个JobDetail实例,我们在创建JobDetail时,将要执行的job类名传给了JobDetail。所以scheduler知道要执行何种类型的job;每当scheduler执行job时,在调用其execute(…)方法之前会创建一个该类的新实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,在Job类中,不应该定义有状态的数据属性,因为在Job的多次执行中,这些属性的值不会保留。
为了给Job增加属性和配置呢,在Job的多次执行中跟踪Job的状态。我们可以使用JobDataMap。下面来演示一下如何在Job中取出由JobDetail配置的JobDataMap:
public class MyJob implement Job{
@Override
public void execute(JobExecutionContext context) throws JobExecutionException{
JobKey key = context.getJobDetail().getkey(); //取Key
JobDataMap datamap = context.getJobDetail().getJobDataMap();
/*
JobDataMap datamap = context.getMergedJobDataMap();
JobExecutionContext中的JobDataMap为我们提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。
*/
String jobSays = dataMap.getString(“jobsays”);
System.out.println(jobSays);
}
}
如果你希望使用JobFactory实现数据的自动注入,则示例代码为:
public class MyJob implement Job{
String jobSays;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException{
JobKey key = context.getJobDetail().getkey(); //取Key
JobDataMap datamap = context.getMergedJobDataMap();
System.out.println(jobSays);
}
public void setJobSays(String jobSays){
This.jobSays = jobSays;
}
}
Trigger
通过TriggerBuilder构建Trigger实例的时候可以定义一些Trigger的公共属性:
- jobKey属性:当Trigger触发时被执行的Job的身份。
- startTime属性:设置Trigger第一次触发的时间。假设现在是一月,设定Trigger每个月五号执行,startTime设定为4月1号,则第一次执行的日期应为4月5号。
- endTime属性:表示trigger失效的时间点。如果设定Trigger每个月五号执行,endTime设定为5月1号,则最后一次执行的日期应为4月5日。
还有一些其他的属性:
优先级priority
如果你的Trigger很多但是Quartz线程池的工作线程太少,Quartz可能没有足够的资源同时触发所有Trigger。可以通过设置priority来指定Trigger的优先级。
注意:只有同时触发的Trigger之间存在优先级差异,10:01总比10:02的Trigger先执行。
错过触发misfire
在scheduler关闭或没有足够的工作线程来执行Job的情况下,持久性的Trigger就会错过其触发时间 即 错过触发(misfire)。当scheduler启动时会查询所有misfire的持久性Trigger,并根据它们各自的misfire属性更新Trigger信息。
日历示例calendar
Quartz的calendar用于从Trigger的调度计划中排除时间段,比如创建一个Trigger,每个工作日的上午9.30执行,然后增加一个celendar,排除掉所有的商业节日。
任何实现了Calendar接口的可序列化对象都可以作为Calendar对象,Quartz在这里提供了org.quartz.impl.HolidayCalendar类实现排除某些天。实例化HolidayCalendar后,需要调用addExcludedDate(Date date)方法指定需要排除的日期,示例:
HolidayCalendar cal - new HolidayCalendar();
Cal.addExcludedDate(someDate);
scheduler.addCalendar(“myHolidays”,cal,false); //把calendar对象注册到scheduler
Trigger t = newTrigger()
.withIdentity("myTrigger")
.forJob("myJob")
.withSchedule(dailyAtHourAndMinute(9, 30)) // execute job daily at 9:30
.modifiedByCalendar("myHolidays") // but not on holidays
.build();
Simple Trigger
SimpleTrigger可以满足的需求是:
- 在具体时间执行一次。
- 在具体时间执行,以指定间隔重复执行若干次。
SimpleTrigger的属性包括:开始时间,结束时间,重复次数以及重复的间隔。
重复的次数可以是0、正整数以及常量。
重复的间隔可以是0或者long型的正数,表示毫秒。如果重复间隔是0,将会以重复的次数并发执行(或者以scheduler可以处理的近似并发数执行)。
示例:
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime) // some Date
.forJob("job1", "group1") // identify job with name, group strings
.build();
// 指定时间开始触发,不重复
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
.forJob(myJob) // identify job with handle to its JobDetail itself
.build();
// 指定时间触发,每隔10秒执行一次,重复10次
Cron Trigger
CronTrigger通常比SimpleTrigger更实用,它基于日历的概念去制定计划。
通过cron表达式指定调度计划,cron表达式不做阐述,网上有在线cron表达式生成器。
TriggerListeners和JobListeners
Listeners是您创建的对象,用于根据调度程序中发生的事件执行操作。顾名思义前者接收到与触发器(trigger)相关的事件,后者接收到与jobs相关的事件。
与触发相关的事件包括:触发器触发,触发失灵,触发完成。
org.quartz.TriggerListeners接口:
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); //触发完成
}
与Job相关的事件包括:Job即将执行的通知 以及 Job完成执行时的通知。
org.quartz.JobListeners接口:
public interface JobListener {
public String getName();
public void jobToBeExecuted(JobExecutionContext context); //即将执行
public void jobExecutionVetoed(JobExecutionContext context);
public void jobWasExecuted(JobExecutionContext context,JobExecutionException jobException); //完成执行
}
使用自己的Listeners
要创建自己的listener,只需要实现上述两个接口。为了方便我们可以扩展JobListenerSupport类和TriggerListenerSupport类,只需要覆盖我们感兴趣的事件。
Listener与调度程序的ListenerManager一起注册,并可以配置listener希望接收事件的job/触发器的Matcher。Listener不与Job和触发器一起存储在JobStore中,因为听众通常是与应用程序的集成点。因此,每次运行应用程序时,都需要重新注册该调度程序。
scheduler.getListenerManager().addJobListener(myJobListener,
KeyMatcher.jobKeyEquals(new JobKey(“myJobName”,“myJobGroup”)));
还有一些其他的匹配方式,在这里先不做阐述了,详情可以查看官方文档。
SchedulerListeners
非常类似于TriggerListeners和JobListeners,它还可以在Scheduler本身中接收到事件的通知。
与调度程序相关的事件包括:添加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();
}
添加SchedulerListener:
Scheduler.getListenerManager().addSchedulerListener(mySchedListener);
删除SchedulerListener:
Scheduler.getListenerManager().removeSchedulerListener(mySchedListener);
Job Stores
JobStore负责跟踪我们提供给调度程序的所有“工作数据”:jobs,triggers,日历等。
在SchedulerFactory的属性文件(对象)中指定您的调度程序应使用哪个JobStore(以及它的配置设置)。
切勿在代码中直接使用JobStore实例。
RAMJobStore
RAMJobStore是使用最简单的JobStore,也是性能最高的,它将所有数据保存在内存中。
优点:快! 配置简单!
缺点:当应用程序结束(或崩溃)时,所有调度信息都将丢失。
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
JDBCJobStore
JDBCJobStore通过JDBC将其所有的数据保存到数据库中。因此配置起来会比较麻烦,但是性能下降的不是很糟糕。具体的配置操作可以查看官方文档。
TerracottaJobStore
TerracottaJobStore提供了一种缩放和鲁棒性的手段,而不使用数据库。性能比JDBCJobStore好,但比RAMJobStore慢。
org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
org.quartz.jobStore.tcConfigUrl = localhost:9510 //添加一个额外的行配置来指定Terracotta服务器的位置
配置,资源使用和SchedulerFactory
Quartz需要配置的组件主要包括:
- 线程池
- JobStore
- DataSources(如有必要)
- 计划程序本身
ThreadPool接口在org.quartz.spi包中定义,可以通过实现该接口自定义自己的线程池。Quartz自带一个非常令人满意的线程池,几乎所有用户都在使用该线程池。
StdSchedulerFactory
StdSchedulerFactory是org.quartz.SchedulerFactory接口的一个实现。它使用一组属性来创建和初始化Quartz Scheduler。属性通常存储在文件中并从文件中加载,也可以由程序创建并直接传递给工厂。详细属性配置请查看官方文档。
DirectSchedulerFactory
DirectSchedulerFactory是另一个SchedulerFactory的实现。对于希望以更加程序化的方式创建Scheduler实例的用户是有用的。通常不鼓励使用它,原因如下:
(1)要求用户了解他们正在做什么。
(2)它不允许声明性配置。换句话说,你最终会硬编辑所有调度程序的设置。
记录
Quartz使用SLF4J框架来满足所有的日志记录需求。
Quartz高级(企业)功能
Clustering
Clustering目前与JDBCJobStore和TerracottaJobStore一起使用。功能包括负载均衡和job故障转移。详细操作查看官方文档。
JTA交易
Job在JTA事务中执行。详细操作查看官方文档。
Quartz其他功能
插件
Quartz提供了一个用于插入附加功能的接口(org.quartz.spi.SchedulerPlugin)。
与Quartz一起提供各种实用功能的插件可以在org.quartz.plugins包中找到。诸如在调度程序启动时自动调用Job,记录Job和触发事件的历史记录,并确保当JVM退出时,调度程序将彻底关闭。
JobFactory
当trigger触发时,通过Scheduler上配置的JobFactory去实例化与之关联的Jobs,默认的JobFactory只是在Job类上调用newInstance()。也可以创建自己的JobFactory实现,以完成诸如让应用程序的IOC或DI容器生成/初始化Job示例之类的操作。
请参阅org.quartz.spi.JobFactory接口以及相关的Scheduler.setJobFactory(fact)方法。
‘Factory-Shipped’ Jobs
Quartz还提供了许多实用的Jobs,我们可以在应用程序中用于执行诸如发送电子邮件和调用EJB等工作,可以在org.quartz.jobs包中找到。