在某个所关注事件发生时,监听器提供了一种方便且非侵入性的机制来获得这一通知。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.
未经允许请勿转载:程序喵 » Quartz 定时任务使用 —— JobListener、Triggerlistener、SchedulerListener(十三)
 程序喵
程序喵