在某个所关注事件发生时,监听器提供了一种方便且非侵入性的机制来获得这一通知。Quartz 提供了三种类型的监听器:监听 Job 的,监听 Trigger 的,和监听 Scheduler 自已的。本章解释如何应用每一种类型来更好的管理你的 Quartz 应用,并获悉到什么事件正在发生。
官方参考:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-07.html
要创建一个Listener, 只需要创建一个实现了org.quartz.TriggerListener
或org.quartz.JobListener
接口的对象即可。
在运行的时候,将Listeners
注册进scheduler
, 而且必须给一个name(可以通过他们的getName()方法获取Listener的name)。
除了继承接口,类也可以继承JobListenerSupport
或TriggerListenerSupport
,重写你感兴趣的event。
Listener通过scheduler的ListenerManager
来注册,其中的Matcher 里描述哪个Jobs、Triggers需要被监听。
Listeners在运行的时候被注册进scheduler, 而不是保存在JobStore。Listener是和你的应用集成在一起的,这样每次你的应用运行的时候,都会在scheduler中重新注册listeners。
全局之于非全局监听器
JobListener 和 TriggerListener 可被注册为全局或非全局监听器。一个全局监听器能接收到所有的 Job/Trigger 的事件通知。而一个非全局监听器(或者说是一个标准的监听器) 只能接收到那些在其上已注册了监听器的 Job 或 Triiger 的事件。
你要注册你的监听器为全局或非全局的需依据你特定的应用需要。我们在以下章节中提供了两种方式的例子。从另一方面来认识全局和非全局的监听器是来自于 Quartz 框架的创建者。James House 在描述全局和非全局监听器时是这样的:
全局监听器是主动意识的,它们为了执行它们的任务而热切的去寻找每一个可能的事件。通常全局监听器要做的工作不用指定到特定的 Job 或 Trigger。非全局监听器一般是被动意识的,它们在所关注的 Trigger 激发之前或是 Job 执行之前什么事也不做。因此,非全局的监听器比起全局监听器而言更适合于修改或增加 Job 执行的工作。这有点像知名的装饰设计模式的装饰器。
全局
scheduler.addGlobalTriggerListener(new SimpleMyTriggerListener());
局部
scheduler.addTriggerListener( triggerListener ); trigger.addTriggerListener( triggerListener.getName() );
JobListener 任务监听器
查看源码
public interface JobListener { String getName(); void jobToBeExecuted(JobExecutionContext var1); void jobExecutionVetoed(JobExecutionContext var1); void jobWasExecuted(JobExecutionContext var1, JobExecutionException var2); }
getName() :返回一个字符串用以说明 JobListener 的名称。对于注册为全局的监听器,getName() 主要用于记录日志,对于由特定 Job 引用的 JobListener,注册在 JobDetail 上的监听器名称必须匹配从监听器上 getName() 方法的返回值。在你看完一些例子之后就会很清楚了。
jobToBeExecuted() :Scheduler 在 JobDetail 将要被执行时调用这个方法。
obExecutionVetoed() :Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决了时调用这个方法。
obWasExecuted() :Scheduler 在 JobDetail 被执行之后调用这个方法。
自定义监听器
import org.quartz.*; public class MyJobListener implements JobListener { @Override public String getName() { return "MyJobListener"; } @Override public void jobExecutionVetoed(JobExecutionContext arg0) { System.out.println("Job监听器:MyJobListener.jobExecutionVetoed()"); } @Override public void jobToBeExecuted(JobExecutionContext arg0) { System.out.println("Job监听器:MyJobListener.jobToBeExecuted()"); } @Override public void jobWasExecuted(JobExecutionContext arg0, JobExecutionException arg1) { System.out.println("Job监听器:MyJobListener.jobWasExecuted()"); } }
注册监听器
MyJobListener myJobListener=new MyJobListener(); // 添加一个特定的job scheduler.getListenerManager().addJobListener(myJobListener, KeyMatcher.jobKeyEquals(new JobKey("myJobName", "myJobGroup")));
上面的代码就可以变成:
scheduler.getListenerManager().addJobListener(myJobListener, jobKeyEquals(jobKey("myJobName", "myJobGroup"))); // 添加特定组的所有jobs scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup")); // 添加多个特定组的所有jobs scheduler.getListenerManager().addJobListener(myJobListener, or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup"))); // 添加所有jobs scheduler.getListenerManager().addJobListener(myJobListener, allJobs());
有了Listeners以后,当应用需要在某些事件发生以后去通知你的应用,这时就不需要Job去明确地去告知你的应用了。
完整示例代码
添加一个jobListener,监听到job1执行后,再触发一个job2任务
job1.java
import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import java.text.SimpleDateFormat; import java.util.Date; public class SimpleJob1 implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { JobKey jobKey = context.getJobDetail().getKey(); System.out.println("\nJob1 - 任务key " + jobKey + "执行时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } }
job2.java
import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import java.text.SimpleDateFormat; import java.util.Date; public class SimpleJob2 implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { JobKey jobKey = context.getJobDetail().getKey(); System.out.println("\nJob2 - 任务key " + jobKey + "执行时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } }
myJobListener.java
import org.quartz.*; public class MyJobListener implements JobListener { @Override public String getName() { return "MyJobListener"; } @Override public void jobExecutionVetoed(JobExecutionContext arg0) { System.out.println("Job监听器:MyJobListener.jobExecutionVetoed()"); } @Override public void jobToBeExecuted(JobExecutionContext arg0) { System.out.println("Job监听器:MyJobListener.jobToBeExecuted()"); } @Override public void jobWasExecuted(JobExecutionContext inContext, JobExecutionException inException) { // 设置另外一个job执行 JobDetail job2 = JobBuilder.newJob(SimpleJob2.class).withIdentity("job2").build(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("job2Trigger").startNow().build(); try { inContext.getScheduler().scheduleJob(job2, trigger); } catch (SchedulerException e) { System.err.println("无法安排job2!"); e.printStackTrace(); } } }
man方法
import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.KeyMatcher; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; public class ListenerExample { public static void main(String[] args) throws Exception { System.out.println("------- 初始化 ----------------------"); SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); // 设置一个job JobDetail job = newJob(SimpleJob1.class).withIdentity("job1", "group1").build(); Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startNow().build(); // 设置监听器 JobListener listener = new MyJobListener(); Matcher<JobKey> matcher = KeyMatcher.keyEquals(job.getKey()); sched.getListenerManager().addJobListener(listener, matcher); // 将job任务加入到调度器 sched.scheduleJob(job, trigger); System.out.println("------- 开始执行调度器 Scheduler ----------------"); sched.start(); try { System.out.println("------- 等待 30 秒... --------------"); Thread.sleep(30L * 1000L); } catch (Exception e) { } sched.shutdown(true); System.out.println("------- 关闭调度器 -----------------"); SchedulerMetaData metaData = sched.getMetaData(); System.out.println("~~~~~~~~~~ 执行了 " + metaData.getNumberOfJobsExecuted() + " 个 jobs."); } }
执行结果
------- 开始执行调度器 Scheduler ---------------- [INFO] 13 九月 07:28:25.071 下午 main [org.quartz.core.QuartzScheduler] Scheduler MyScheduler_$_NON_CLUSTERED started. ------- 等待 30 秒... -------------- Job监听器:MyJobListener.jobToBeExecuted() Job1 - 任务key group1.job1执行时间:2017-09-13 19:28:25 Job2 - 任务key DEFAULT.job2执行时间:2017-09-13 19:28:25 [INFO] 13 九月 07:28:55.075 下午 main [org.quartz.core.QuartzScheduler] Scheduler MyScheduler_$_NON_CLUSTERED shutting down. [INFO] 13 九月 07:28:55.075 下午 main [org.quartz.core.QuartzScheduler] Scheduler MyScheduler_$_NON_CLUSTERED paused. [INFO] 13 九月 07:28:55.286 下午 main [org.quartz.core.QuartzScheduler] Scheduler MyScheduler_$_NON_CLUSTERED shutdown complete. ------- 关闭调度器 ----------------- ~~~~~~~~~~ 执行了 2 个 jobs.
TriggerListener 触发器监听器
与 JobListener 有所不同的是, TriggerListener 接口还有关于 Trigger 实例生命周期的方法。
查看源码
import org.quartz.Trigger.CompletedExecutionInstruction; public interface TriggerListener { String getName(); void triggerFired(Trigger var1, JobExecutionContext var2); boolean vetoJobExecution(Trigger var1, JobExecutionContext var2); void triggerMisfired(Trigger var1); void triggerComplete(Trigger var1, JobExecutionContext var2, CompletedExecutionInstruction var3); }
getName():和前面的 JobListener 一样,TriggerListner 接口的 getName() 返回一个字符串用以说明监听器的名称。对于非全局的 TriggerListener,在 addTriggerListener() 方法中给定的名称必须与监听器的 getName() 方法返回值相匹配。
triggerFired() :当与监听器相关联的 Trigger 被触发,Job 上的 execute() 方法将要被执行时,Scheduler 就调用这个方法。在全局 TriggerListener 情况下,这个方法为所有 Trigger 被调用。
vetoJobExecution():在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方法。TriggerListener 给了一个选择去否决 Job 的执行。假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行。
triggerMisfired():Scheduler 调用这个方法是在 Trigger 错过触发时。如这个方法的 JavaDoc 所指出的,你应该关注此方法中持续时间长的逻辑:在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应。你应当保持这上方法尽量的小。
triggerComplete():Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法。这不是说这个 Trigger 将不再触发了,而仅仅是当前 Trigger 的触发(并且紧接着的 Job 执行) 结束时。这个 Trigger 也许还要在将来触发多次的。
以下代码 展示了一个很简单的 TriggerListener 实现
代码示例
import org.quartz.JobExecutionContext; import org.quartz.Trigger; import org.quartz.TriggerListener; /** * 触发器监听 */ public class MyTriggerListener implements TriggerListener { @Override public String getName() { return "MyTriggerListener"; } /** * (1) Trigger被激发 它关联的job即将被运行 * * @param trigger * @param context */ @Override public void triggerFired(Trigger trigger, JobExecutionContext context) { System.out.println("MyTrigger监听器:" + trigger.getJobKey() + ",进入方法 triggerFired()"); } /** * (2) Trigger被激发 它关联的job即将被运行,先执行(1),在执行(2) 如果返回TRUE 那么任务job会被终止 * * @param trigger * @param context * @return */ @Override public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { System.out.println("MyTrigger监听器:" + trigger.getJobKey() + ",vetoJobExecution()"); return false; } /** * (3) 当Trigger错过被激发时执行,比如当前时间有很多触发器都需要执行,但是线程池中的有效线程都在工作,那么有的触发器就有可能超时,错过这一轮的触发。 * * @param trigger */ @Override public void triggerMisfired(Trigger trigger) { System.out.println("MyTrigger监听器:" + trigger.getJobKey() + ",triggerMisfired()"); } /** * (4) 任务完成时触发 * * @param trigger * @param jobExecutionContext * @param completedExecutionInstruction */ @Override public void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) { System.out.println("MyTrigger监听器:" + trigger.getJobKey() + ",triggerComplete()"); } }
job1类不变,main方法测试将jobListener修改如下
// 设置监听器 TriggerListener triggerListener = new MyTriggerListener(); Matcher<TriggerKey> matcher = KeyMatcher.keyEquals(trigger.getKey()); sched.getListenerManager().addTriggerListener(triggerListener, matcher); // 将job任务加入到调度器 sched.scheduleJob(job, trigger);
执行结果
------- 等待 30 秒... -------------- MyTrigger监听器:group1.job1,进入方法 triggerFired() MyTrigger监听器:group1.job1,vetoJobExecution() Job1 - 任务key group1.job1执行时间:2017-09-13 22:32:39 MyTrigger监听器:group1.job1,triggerComplete()
SchedulerListener 调度器监听器
org.quartz.SchedulerListener 接口包含了一系列的回调方法,它们会在 Scheduler 的生命周期中有关键事件发生时被调用。
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 schedulerShutdown(); }
SchedulerListener 是在 Scheduler 级别的事件产生时得到通知,不管是增加还是移除 Scheduler 中的 Job,或者是 Scheduler 遭遇到了严重的错误时。那些事件多是关于对 Scheduler 管理的,而不是专注于 Job 或 Trigger 的。
jobScheduled() 和 jobUnscheduled():Scheduler 在有新的 JobDetail 部署或卸载时调用这两个中的相应方法。
triggerFinalized() :当一个 Trigger 来到了再也不会触发的状态时调用这个方法。除非这个 Job 已设置成了持久性,否则它就会从 Scheduler 中移除。
triggersPaused():Scheduler 调用这个方法是发生在一个 Trigger 或 Trigger 组被暂停时。假如是 Trigger 组的话,triggerName 参数将为 null。
triggersResumed():Scheduler 调用这个方法是发生成一个 Trigger 或 Trigger 组从暂停中恢复时。假如是 Trigger 组的话,triggerName 参数将为 null。
jobsPaused():当一个或一组 JobDetail 暂停时调用这个方法。
jobsResumed():当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组,jobName 参数将为 null。
schedulerError():在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。错误的类型会各式的,但是下面列举了一些错误例子:
初始化 Job 类的问题
试图去找到下一 Trigger 的问题
JobStore 中重复的问题
数据存储连接的问题
你可以使用 SchedulerException 的 getErrorCode() 或者 getUnderlyingException() 方法或获取到特定错误的更详尽的信息。
schedulerShutdown():Scheduler 调用这个方法用来通知 SchedulerListener Scheduler 将要被关闭。
代码示例
job1.java不变,修改监听如下
SchedulerListener schedulerListener = new MySchedulerListener(); sched.getListenerManager().addSchedulerListener(schedulerListener);
执行结果
Quartz scheduler version: 2.2.3 MySchedulerListener监听器:group1.job1,进入方法 jobAdded() MySchedulerListener监听器:group1.job1,进入方法 jobScheduled() ------- 开始执行调度器 Scheduler ---------------- MySchedulerListener监听器,进入方法 schedulerStarting() [INFO] 14 九月 09:30:11.093 上午 main [org.quartz.core.QuartzScheduler] Scheduler MyScheduler_$_NON_CLUSTERED started. MySchedulerListener监听器,进入方法 schedulerStarted() ------- 等待 30 秒... -------------- Job1 - 任务key group1.job1执行时间:2017-09-14 09:30:11 MySchedulerListener监听器:group1.job1,进入方法 triggerFinalized() MySchedulerListener监听器:group1.job1,进入方法 jobDeleted() [INFO] 14 九月 09:30:41.090 上午 main [org.quartz.core.QuartzScheduler] Scheduler MyScheduler_$_NON_CLUSTERED shutting down. [INFO] 14 九月 09:30:41.090 上午 main [org.quartz.core.QuartzScheduler] Scheduler MyScheduler_$_NON_CLUSTERED paused. MySchedulerListener监听器,进入方法 schedulerInStandbyMode() MySchedulerListener监听器,进入方法 schedulerShuttingdown() MySchedulerListener监听器,进入方法 schedulerShutdown() [INFO] 14 九月 09:30:41.261 上午 main [org.quartz.core.QuartzScheduler] Scheduler MyScheduler_$_NON_CLUSTERED shutdown complete. ------- 关闭调度器 ----------------- ~~~~~~~~~~ 执行了 1 个 jobs.