Quartz 定时任务使用 —— JobListener、Triggerlistener、SchedulerListener(十三)

在某个所关注事件发生时,监听器提供了一种方便且非侵入性的机制来获得这一通知。Quartz 提供了三种类型的监听器:监听 Job 的,监听 Trigger 的,和监听 Scheduler 自已的。本章解释如何应用每一种类型来更好的管理你的 Quartz 应用,并获悉到什么事件正在发生。

官方参考:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-07.html

要创建一个Listener, 只需要创建一个实现了org.quartz.TriggerListenerorg.quartz.JobListener接口的对象即可。

在运行的时候,将Listeners注册进scheduler, 而且必须给一个name(可以通过他们的getName()方法获取Listener的name)。

除了继承接口,类也可以继承JobListenerSupportTriggerListenerSupport,重写你感兴趣的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.


赞(52) 打赏
未经允许不得转载:优客志 » JAVA开发
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏