Quartz Job Scheduling Framework[翻译]第七章. 实现 Quartz 监听器 (第一部分)

译者:Unmi(隔叶黄莺)
http://www.blogjava.net/Unmi/archive/2008/06/28/211295.html

第七章. 实现 Quartz 监听器

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

一. 监听器作为扩展点

术语 "扩展点" 在软件开发中用于指示框架或应用的某个位置,在这一位置在创建者期望用户扩展或定制这一框架来适合于他们的需要。(你也将会听到 hook(钩子) 一词,是一样意思的)

Quartz 监听器是某一类型的扩展点,在这里你,作为一个 Quartz 用户,可以扩展这框架并定制它来做些新的事情。定制化发生成监听类的实现当中,我们会在本章中告诉你如何构建它。

监听器并非框架中仅有的扩展点。还有插件和一些其他的定制选项,不过监听器提供了一个简单的方式来定制框架,使之做你需要它做的事情。因为针对于监听器的扩展点是通过公有化接口来支持,所以你用不着担心创建了你自己的分支代码,到后来又不被支持的情况。

二. 实现监听

在接下来的讨论中,实现监听器的方法通用于所有的三种类型。可以分成以下步骤:

1. 创建一个 Java 类,实现监听器接口

2. 用你的应用中特定的逻辑实现监听器接口的所有方法

3. 注册监听器

·创建监听器类

监听器是一个 Java 接口,它必须由一个具体类来实现。你也不需要只为这个目的创建一个专门的类;它可以是任何一个你希望能接收到方法回调的类。为符合良好的程序设计,你应当注意保持高内聚和松耦合性。认真的考虑哪个类你决定用于实现监听器类;这从总体设计的视角来看是很重要的。

·实现监听器方法

因为监听器是普通的 Java 接口,每个方法都必须在你的监听器实现类中实现。假如有一些监听器接口方法你不感兴趣,允许你使用空的方法体;只是,你仍必须提供一个有效的方法实现它。例如,下面的代码片断显示了 SchedulerListerner 中一个方法的空方法体:

SchedulerListener methods:

... rest of the SchedulerListener not shown

public void schedulerShutdown(){
  // Don't care about the shutdown event
}

·注册监听器

要接收到方法回调,Scheduler 必须能获知监听实例。图 7.1 描绘了用 Scheduler 注册一个监听器和接受回调的过程。

图 7.1. 监听器被注册并接收 Scheduler 的回调



全局之于非全局监听器

JobListener 和 TriggerListener 可被注册为全局或非全局监听器。一个全局监听器能接收到所有的 Job/Trigger 的事件通知。而一个非全局监听器(或者说是一个标准的监听器) 只能接收到那些在其上已注册了监听器的 Job 或 Triiger 的事件。

你要注册你的监听器为全局或非全局的需依据你特定的应用需要。我们在以下章节中提供了两种方式的例子。从另一方面来认识全局和非全局的监听器是来自于 Quartz 框架的创建者。James House 在描述全局和非全局监听器时是这样的:

全局监听器是主动意识的,它们为了执行它们的任务而热切的去寻找每一个可能的事件。通常,全局监听器要做的工作不用指定到特定的 Job 或 Trigger。非全局监听器一般是被动意识的,它们在所关注的 Trigger 激发之前或是 Job 执行之前什么事也不做。因此,非全局的监听器比起全局监听器而言更适合于修改或增加 Job 执行的工作。这有点像知名的装饰设计模式的装饰器。

三. 监听 Job 事件

org.quartz.JobListener 接口包含一系列的方法,它们会由 Job 在其生命周期中产生的某些关键事件时被调用。JobListener 可用的方法显示在代码 7.1 中。

代码 7.1. org.quartz.JobListener 接口中的方法
 
public interface JobListener {
  public String getName();
  public void jobToBeExecuted(JobExecutionContext context);
  public void jobExecutionVetoed(JobExecutionContext context);
  public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException);
}


JobListener 接口中的方法用途是十分明了的。然后,我们还是要对他们加以简单说明。

·getName() 方法

getName() 方法返回一个字符串用以说明 JobListener 的名称。对于注册为全局的监听器,getName() 主要用于记录日志,对于由特定 Job 引用的 JobListener,注册在 JobDetail 上的监听器名称必须匹配从监听器上 getName() 方法的返回值。在你看完一些例子之后就会很清楚了。

·jobToBeExecuted() 方法

Scheduler 在 JobDetail 将要被执行时调用这个方法。

·jobExecutionVetoed() 方法

Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决了时调用这个方法。

·jobWasExecuted() 方法

Scheduler 在 JobDetail 被执行之后调用这个方法。

代码 7.2 展示了一个很简单的 JobListener 实现。

代码 7.2. 一个简单的 JobListner 实现
 
package org.cavaness.quartzbook.chapter7;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
public class SimpleJobListener implements JobListener {
  Log logger = LogFactory.getLog(SimpleJobListener.class);
  public String getName() {
    return getClass().getSimpleName();
  }
  public void jobToBeExecuted(JobExecutionContext context) {
    String jobName = context.getJobDetail().getName();
    logger.info(jobName + " is about to be executed");
  }
  public void jobExecutionVetoed(JobExecutionContext context) {
    String jobName = context.getJobDetail().getName();
    logger.info(jobName + " was vetoed and not executed()");
  }
  public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
    String jobName = context.getJobDetail().getName();
    logger.info(jobName + " was executed");
  }
}



代码 7.2 中的 JobListener 打印一个日志消息,很明显,只是监听器最基本的用法。你要实现的逻辑完全由你和你的应用需要而定。你也许想在 Job 成功完成后发送一个电子邮件,或者在 Job 被否决后部署另一个。你有在回调方法中执行几乎任何动作的自由。

前面,我们提到过 JobListener (和 TriggerListener) 能注册为全局或非全局的。注意了,我们并不需要事先知道在代码 7.2 中的 JobListener 是一个全局或是非全局的;我们仅仅是实现了接口和提供了监听器方法。代码 7.3 描绘了如何使用代码 7.2 中的 SimpleJobListner 使之注册为一个全局的 JobListener。

代码 7.3. 使用 SimpleJobListener 作为一个全局 JobListener

 
package org.cavaness.quartzbook.chapter7;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cavaness.quartzbook.common.PrintInfoJob;
import org.quartz.JobDetail;
import org.quartz.JobListener;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;
public class Listing_7_3 {
  static Log logger = LogFactory.getLog(Listing_7_3.class);
  public static void main(String[] args) {
    Listing_7_3 example = new Listing_7_3();
    try {
      example.startScheduler();
    } catch (SchedulerException ex) {
      logger.error(ex);
    }
  }
  public void startScheduler() throws SchedulerException {
    // Create an instance of the factory
    Scheduler scheduler = null;
    // Create the scheduler and JobDetail
    scheduler = StdSchedulerFactory.getDefaultScheduler();
    JobDetail jobDetail = new JobDetail("PrintInfoJob", Scheduler.DEFAULT_GROUP, PrintInfoJob.class);
    /*
     * Set up a trigger to start firing now, with no end date/time, repeat forever and have 10 secs (10000 ms) between each
     * firing.
     */
    Trigger trigger = TriggerUtils.makeSecondlyTrigger(10);
    trigger.setName("SimpleTrigger");
    trigger.setStartTime(new Date());
    // Register the JobDetail and Trigger
    scheduler.scheduleJob(jobDetail, trigger);
    // Create and register the global job listener
    JobListener jobListener = new SimpleJobListener("SimpleJobListener");
    scheduler.addGlobalJobListener(jobListener);
    // Start the scheduler
    scheduler.start();
    logger.info("Scheduler was started at " + new Date());
  }
}



代码 7.3 中的代码现在看来是相当直截的。创建了一个 JobDetail 和 Trigger 并注册到了 Scheduler 实例上,这在前面我们已是做过许多次了。

代码 7.2 中的 SimpleJobListener 初始化后通过 Scheduler 调用 addGlobalJobListener() 方法注册为一个全局的 JobListener。最后,启动 Scheduler。

因为我们只配置了单个 Job (PrintInfoJob),我们获得回调也只是那个 JobDetail。不过,假如我们部署了其他 Job,我们也能看到第二个 Job 的回调日志信息,因为这个监听顺是配置为全局的。

·注册非全局的 JobListener

你还能使用代码 7.2 中的 SimpleJobListener 作为一个非全局的 JobListener。要做到这点,你仅需要修改代码 7.3 的 startScheduler() 方法中的代码。代码 7.4 显示了这一需要做的小小的改变。

代码 7.4. 使用 SimpleJobListener 作为非全局的 JobListener

 
package org.cavaness.quartzbook.chapter7;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cavaness.quartzbook.common.PrintInfoJob;
import org.quartz.JobDetail;
import org.quartz.JobListener;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;
public class Listing_7_4 {
  static Log logger = LogFactory.getLog(Listing_7_4.class);
  public static void main(String[] args) {
    Listing_7_4 example = new Listing_7_4();
    try {
      example.startScheduler();
    } catch (SchedulerException ex) {
      logger.error(ex);
    }
  }
  public void startScheduler() throws SchedulerException {
    Scheduler scheduler = null;
    try {
      // Create the scheduler and JobDetail
      scheduler = StdSchedulerFactory.getDefaultScheduler();
      JobDetail jobDetail = new JobDetail("PrintInfoJob", Scheduler.DEFAULT_GROUP, PrintInfoJob.class);
      /*
       * Set up a trigger to start firing now, with no end date/time, repeat forever and have 10 secs (10000 ms) between each
       * firing.
       */
      Trigger trigger = TriggerUtils.makeSecondlyTrigger(10);
      trigger.setName("SimpleTrigger");
      trigger.setStartTime(new Date());
      // Create the job listener
      JobListener jobListener = new SimpleJobListener("SimpleJobListener");
      // Register Listener as a nonglobal listener
      scheduler.addJobListener(jobListener);
      // Listener set on JobDetail before scheduleJob()
      jobDetail.addJobListener(jobListener.getName());
      // Register the JobDetail and Trigger
      scheduler.scheduleJob(jobDetail, trigger);
      // Start the scheduler
      scheduler.start();
      logger.info("Scheduler started at " + new Date());
    } catch (SchedulerException ex) {
      logger.error(ex);
    }
  }
}



代码 7.4 很类似于代码 7.4 中的代码。因为 JobListener 是要注册为一个非全局的监听器,你就要调用 Scheduler 的 addJobListener() 方法而不是 addGlobalJobListener() 方法了。对于非全局的 JobListener,它应于任何引用到它的 JobDetail 使用 schedulerJob() 或 addJob() 方法注册之前被注册。

接下来,JobListener 的名字要设置给 JobDetail。注意,设置的不是 JobListener 实例,仅仅是它的名称。这是通过调用 addJobListener() 方法并传入名称来完成的。传递给 addJobListener() 方法的名称必须匹配从监听器的 getName() 方法返回的名称。如果 Scheduler 不能根据名称找到监听器,它会抛出一个 SchedulerException 异常。

最后,启动 Scheduler。

非全局 JobListener 相关步骤的顺序

加入一个非全局 JobListener 的步骤必须是依序完成。JobListener 必须首先加入到 Scheduler 中。接着,JobListener 才能够设置给 JobDetail 对象。之后,你就能使用 scheduleJob() 方法安全的把 JobDetail 加入到 Scheduler 中。

四. 监听 Trigger 事件

正如 JobListener, org.quartz.TriggerListener 接口也包含一系列给 Scheduler 调用的方法。然而,与 JobListener 有所不同的是, TriggerListener 接口还有关于 Trigger 实例生命周期的方法。代码 7.5 列出了 TriggerListener 接口的方法。

代码 7.5. org.quartz.TriggerListener 接口的方法

 
public interface TriggerListener {
  public String getName();
  public void triggerFired(Trigger trigger, JobExecutionContext context);
  public boolean vetoJobExecution(Trigger trigger, JobExecutidonContext context);
  public void triggerMisfired(Trigger trigger);
  public void triggerComplete(Trigger trigger, JobExecutionContext context, int triggerInstructionCode);
}


·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 也许还要在将来触发多次的。

代码 7.6 展示了一个很简单的 TriggerListener 实现

代码 7.6. 一个简单的 TriggerListener 实现

 
package org.cavaness.quartzbook.chapter7;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
public class SimpleTriggerListener implements TriggerListener {
  Log logger = LogFactory.getLog(SimpleTriggerListener.class);
  private String name;
  public SimpleTriggerListener(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
  public void triggerFired(Trigger trigger, JobExecutionContext context) {
    String triggerName = trigger.getName();
    logger.info(triggerName + " was fired");
  }
  public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
    String triggerName = trigger.getName();
    logger.info(triggerName + " was not vetoed");
    return false;
  }
  public void triggerMisfired(Trigger trigger) {
    String triggerName = trigger.getName();
    logger.info(triggerName + " misfired");
  }
  public void triggerComplete(Trigger trigger, JobExecutionContext context, int triggerInstructionCode) {
    String triggerName = trigger.getName();
    logger.info(triggerName + " is complete");
  }
}


正如代码7.2 中的 JobListener 一样,代码 7.6 中的 TriggerListener 也是初步的。 它不过是在 Scheduler 调用它的方法时打印了一条日志信息。代码 7.7 中代码测试了这个简单的 TriggerListener。

代码 7.7. 使用 SimpleTriggerListener 作为一个全局的 TriggerListener

 
package org.cavaness.quartzbook.chapter7;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cavaness.quartzbook.common.PrintInfoJob;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;
public class Listing_7_7 {
  static Log logger = LogFactory.getLog(Listing_7_7.class);
  public static void main(String[] args) {
    Listing_7_7 example = new Listing_7_7();
    try {
      example.startScheduler();
    } catch (SchedulerException ex) {
      logger.error(ex);
    }
  }
  public void startScheduler() throws SchedulerException {
    // Create an instance of the factory
    Scheduler scheduler = null;
    // Create the scheduler and JobDetail
    scheduler = StdSchedulerFactory.getDefaultScheduler();
    JobDetail jobDetail = new JobDetail("PrintInfoJob", Scheduler.DEFAULT_GROUP, PrintInfoJob.class);
    // Create and register the global job listener
    TriggerListener triggerListener = new SimpleTriggerListener("SimpleTriggerListener");
    scheduler.addGlobalTriggerListener(triggerListener);
    /*
     * Set up a trigger to start firing now, with no end date/time, repeat forever and have 10 secs (10000 ms) between each
     * firing.
     */
    Trigger trigger = TriggerUtils.makeSecondlyTrigger(10);
    trigger.setName("SimpleTrigger");
    trigger.setStartTime(new Date());
    // Register the JobDetail and Trigger
    scheduler.scheduleJob(jobDetail, trigger);
    // Start the scheduler
    scheduler.start();
    logger.info("Scheduler was started at " + new Date());
  }
}


代码 7.7 显示了如何注册 SimpleTriggerListener 为一个全局的 TriggerListener。它看起来与代码 7.3 中用来注册一个全局 JobListener 的代码完全相似。你只需要调用 addGloabelTriggerListener() 方法并传入这个 TriggerListener 实例。

·注册为非全局的 TriggerListener

要注册为一个非全局的 TriggerListener,你必须调用 addTriggerListener() 方法并传入这个 TriggerListener 实例。接着调用 Trigger 实例的 addTriggerListener() 方法并传入这个 TriggerListener 的名称。

在代码 7.8 中展示了这一过程。

代码 7.8. 使用一个非全局的 TriggerListener

 
package org.cavaness.quartzbook.chapter7;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cavaness.quartzbook.common.PrintInfoJob;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;
public class Listing_7_8 {
  static Log logger = LogFactory.getLog(Listing_7_8.class);
  public static void main(String[] args) {
    Listing_7_8 example = new Listing_7_8();
    try {
      example.startScheduler();
    } catch (SchedulerException ex) {
      logger.error(ex);
    }
  }
  public void startScheduler() throws SchedulerException {
    // Create an instance of the factory
    Scheduler scheduler = null;
    // Create the scheduler and JobDetail
    scheduler = StdSchedulerFactory.getDefaultScheduler();
    JobDetail jobDetail = new JobDetail("PrintInfoJob", Scheduler.DEFAULT_GROUP, PrintInfoJob.class);
    // Create and register the nonglobal job listener
    TriggerListener triggerListener = new SimpleTriggerListener("SimpleTriggerListener");
    scheduler.addTriggerListener(triggerListener);
    /*
     * Set up a trigger to start firing now, with no end date/time, repeat forever and have 10 secs (10000 ms) between each
     * firing.
     */
    Trigger trigger = TriggerUtils.makeSecondlyTrigger(10);
    trigger.setName("SimpleTrigger");
    trigger.setStartTime(new Date());
    // Set the listener name for the trigger
    trigger.addTriggerListener(triggerListener.getName());
    // Register the JobDetail and Trigger
    scheduler.scheduleJob(jobDetail, trigger);
    // Start the scheduler
    scheduler.start();
    logger.info("Scheduler was started at " + new Date());
  }
}


针对于前面的非全局 JobListener 提到的相同的警告可以应用到这里来;你必须在把它设置给 Trigger 实例并存储了 Trigger 之前把 TriggerListener 加入到 Scheduler 中。

QuartzFigure7_1.jpg
快乐渡过每一天,减肥坚持每一天