Quartz实现与组件监听
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。Quartz是一个完全由java编写的开源作业调度框架。不要让作业调度这个术语吓着你。尽管Quartz框架整合了许多额外功能, 但就其简易形式看,你会发现它易用得简直让人受不了!。Quartz框架的核心是调度器。调度器负责管理Quartz应用运行时环境。Quartz不仅仅是线程和线程管理。Quartz依赖一套松耦合的线程池管理部件来管理线程环境。本篇文章中,我们会多次提到线程池管理,但Quartz里面的每个对象是可配置的或者是可定制的。例如你想要插进自己线程池管理设施,我猜你一定能!Quartz框架有一个丰富的特征集。事实上,Quartz有太多特性以致不能在一种情况中全部领会,但没时间在此详细讨论。Quartz经常会用到cron表达式,可以使用国外网站cronmaker辅助生成cron表达式。
Quartz的组成及原理
Quartz是基于Java线程池实现的。
四个主要部件
1、Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context)
2、JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
3、Trigger 代表一个调度参数的配置,什么时候去调。
4、Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
最基础的形式
下面演示Quartz编写的步骤。
Gradle依赖库:
// https://mvnrepository.com/artifact/org.quartz-scheduler/quartz
compile group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.1'
1、 定义Job类
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
System.out.println("Hello World! - " + new Date());
}
}
2、创建调度器
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
3、创建JobDetail或者说实例化Job
Date runTime = evenMinuteDate(new Date());
JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
4、创建调度逻辑
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
5、组合Job和trigger,调度器启动,调度器关闭。
sched.scheduleJob(job, trigger);
sched.start();
sched.shutdown(true);
Quartz的使用步骤都是这样的,只不过在不同的应用场景下使用的调度逻辑和Job要变化。
控制Job类的并发
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class BadJob2 implements Job { 。。。}
这两个注解是控制Job类的并发状态的,可以影响 Quartz 的行为。
@DisallowConcurrentExecution 添加到 Job 类后,Quartz 将不会同时执行一个Job 的多个实例。
Quartz定时任务默认都是并发执行的,不会等待上一次任务执行完毕,只要间隔时间到就会执行, 如果定时任执行太长,会长时间占用资源,导致其它任务堵塞。
@PersistJobDataAfterExecution 添加到 Job 类后,表示 Quartz 将会在成功执行 execute() 方法后(没有抛出异常)更新 JobDetail 的 JobDataMap,下一次执行相同的任务(JobDetail)将会得到更新后的值,而不是原始的值。就像@DisallowConcurrentExecution 一样,这个注释基于 JobDetail 而不是 Job 类的实例。
如果你使用了 @PersistJobDataAfterExecution 注释,那么强烈建议你使用 @DisallowConcurrentExecution 注释,这是为了避免出现并发问题,当多个 Job 实例同时执行的时候,到底使用了哪个数据将变得很混乱。
trigger的时间点定义的几种形式:
Date startTime = DateBuilder.nextGivenSecondDate(null, 15);
Date runTime = evenMinuteDate(new Date());
Date startTime = nextGivenSecondDate(null, 15);
Job类创建的形式分为两类:1、无参,2、有参
// 无参Job
JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build();
// 有参Job
JobDetail job = newJob(BadJob1.class).withIdentity("badJob1", "group1").usingJobData("denominator", "0").build();
Trigger的两种形式
Trigger的创建分为:1、函数式,2、cron表达式。每种都有不循环和循环执行。
函数式
// 单次执行
JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
sched.scheduleJob(job, trigger);
// 循环执行
job = newJob(SimpleJob.class).withIdentity("job3", "group1").build();
trigger = newTrigger().withIdentity("trigger3", "group1").startAt(startTime)
.withSchedule(simpleSchedule().withIntervalInSeconds(3).withRepeatCount(10)).build();
// 循环执行3次
// 无限循环用 withRepeatCount().repeatForever()).build();
ft = sched.scheduleJob(job, trigger);
Cron 表达式 Trigger
Cron表达式的语法网上朋友们的文章写得很全面,所以我就不在此赘述了。
JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build();
CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("0/20 * * * * ?"))
.build();
Date ft = sched.scheduleJob(job, trigger);
// ############################################################
job = newJob(SimpleJob.class).withIdentity("job2", "group1").build();
trigger = newTrigger().withIdentity("trigger2", "group1").withSchedule(cronSchedule("15 0/2 * * * ?")).build();
ft = sched.scheduleJob(job, trigger);
Cron trigger 应该都是不用定义 startAt时间点的。
Scheduler
Scheduler的方法还是不少的碍于篇幅不说了,不过它的方法大部分都是能见名知意的。
ft = sched.scheduleJob(job, trigger);
sched.start();
sched.shutdown(false);
SchedulerMetaData metaData = sched.getMetaData();
Job的成员方法execute获取参数
JobKey jobKey = context.getJobDetail().getKey();
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
int denominator = dataMap.getInt("denominator");
Job的execute处理异常
在execute里处理异常通常是:
JobKey jobKey = context.getJobDetail().getKey();
_log.info("---" + jobKey + " executing at " + new Date());
// a contrived example of an exception that
// will be generated by this job due to a
// divide by zero error
try {
int zero = 0;
calculation = 4815 / zero;
} catch (Exception e) {
_log.info("--- Error in job!");
JobExecutionException e2 =
new JobExecutionException(e);
// Quartz will automatically unschedule
// all triggers associated with this job
// so that it does not run again
e2.setUnscheduleAllTriggers(true);
throw e2;
}
_log.info("---" + jobKey + " completed at " + new Date());
}
JobKey jobKey = context.getJobDetail().getKey();
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
int denominator = dataMap.getInt("denominator");
System.out.println("---" + jobKey + " executing at " + new Date() + " with denominator " + denominator);
// a contrived example of an exception that
// will be generated by this job due to a
// divide by zero error (only on first run)
try {
calculation = 4815 / denominator;
} catch (Exception e) {
System.out.println("--- Error in job!");
JobExecutionException e2 = new JobExecutionException(e);
// fix denominator so the next time this job run
// it won't fail again
dataMap.put("denominator", "1");
// this job will refire immediately
e2.setRefireImmediately(true);
throw e2;
}
System.out.println("---" + jobKey + " completed at " + new Date());
}
Quartz与监听器
Quartz有三种监听器:
- SchedulerListener
- JobListener
- TriggerListener
SchedulerListener 是在 Scheduler 级别的事件产生时得到通知,不管是增加还是移除 Scheduler 中的 Job、Trigger,Scheduler的start和shutdown或者是 Scheduler 遭遇到了严重的错误时。那些事件不光是是关于对 Scheduler 管理的,还关注 Job 或 Trigger 的相关事件。SchedulerListener 接口包含了一系列的回调方法,它们会在 Scheduler 的生命周期中有关键事件发生时被调用。
其它两个Listener都是对应具体组件。
具体事件Quartz开发者都定义的比较清晰就不解释了,有几个注意的点在下面的代码里有注释。
可以在具体组件上加监听器。日志记录Quartz的状态。
SchedulerListener接口
public class SchedulerListenerExample implements SchedulerListener {
@Override
public void jobScheduled(Trigger trigger) {
}
@Override
public void jobUnscheduled(TriggerKey triggerKey) {
}
@Override
public void triggerFinalized(Trigger trigger) {
}
@Override
public void triggerPaused(TriggerKey triggerKey) {
}
@Override
public void triggersPaused(String s) {
}
@Override
public void triggerResumed(TriggerKey triggerKey) {
}
@Override
public void triggersResumed(String s) {
}
@Override
public void jobAdded(JobDetail jobDetail) {
}
@Override
public void jobDeleted(JobKey jobKey) {
}
@Override
public void jobPaused(JobKey jobKey) {
}
@Override
public void jobsPaused(String s) {
}
@Override
public void jobResumed(JobKey jobKey) {
}
@Override
public void jobsResumed(String s) {
}
@Override
public void schedulerError(String s, SchedulerException e) {
}
@Override
public void schedulerInStandbyMode() {
}
@Override
public void schedulerStarted() {
}
@Override
public void schedulerStarting() {
}
@Override
public void schedulerShutdown() {
}
@Override
public void schedulerShuttingdown() {
}
@Override
public void schedulingDataCleared() {
}
}
TriggerListener接口
public class TriggerListenerExample implements TriggerListener {
@Override
public String getName() { // !!! 不得返回null !!!
return "TriggerListenerExampleName";
}
@Override
public void triggerFired(Trigger trigger, JobExecutionContext jobExecutionContext) {
}
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext jobExecutionContext) {
return false;
}
@Override
public void triggerMisfired(Trigger trigger) {
}
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {
}
}
JobListener接口
public class JobListenerShopJ implements JobListener {
@Override
public String getName() { // !!! 不得返回null !!!
return "JobListenerShopJ";
}
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
// Job 启动时的事件
}
@Override
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
}
@Override
public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
// Job 结束 和 出现异常时的事件
// 正常 结束 e 为 null
}
}
注入监听器
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
sched.getListenerManager().addSchedulerListener(new SchedulerListenerExample());
sched.getListenerManager().addTriggerListener(new TriggerListenerExample());
sched.getListenerManager().addJobListener(new JobListenerShopJ());
Quartz配置文件
quartz.properties
默认配置:
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
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
可以对一些改变一些设置做匹配业务、场景优化等。
基本配置有:线程数量、线程优先级、线程池类等。
将修改过的 quartz.properties 文件放到 ClassPath路径(如:Resource路径 下),应用重启时会自动加载配置文件。在改动配置文件时要把默认配置的所有内容带上,不能有缺省。
Quartz的一个异常“Job 创建失败”
Maven使用
<exclusions>
<exclusion>
<groupId>quartz</groupId>
<artifactId>quartz</artifactId>
</exclusion>
</exclusions>
消除传递依赖可以解决。