第三章:Hello Quartz

多数读者都较容易从一个简明扼要的例子中明白一个东西。作为写作者,要注意避免把一章的内容精简地几乎什么都没了;作为读者呢,需要有耐心并且要进一步相信其后相关的章节应该去阅读,尽管这个例子看起来是如此之简单。

有了这种初衷,这一章将为你介绍如何用 Quartz 框架创建一个简单的应用程序,它展示了一个典型的应用。这个例子将让你领略到创建和执行一个简单应用的必要步骤。通过本章的学习,为你学习本书的后续章节打下了坚实的基础。 

1. "Hello, World" Quartz 工程
本示例应用比起众所周知的 System.out.println("Hello world from Quartz") 来还是要有趣些。当我们用 Quartz 执行一个作业时,总是希望它能为我们执行一些有趣且有意义的任务。因此,接下来我们就要做一些有用且有用的事情。

本章向您演示如何创建这么一个 Quartz 作业,Quartz 应用通知它要做事情的时候,就会去扫描指定的目录寻找 XML 文件。假如在指定目录中找到了一个或多个 XML 文件的话,它将会打印出文件的一些概要信息。是不是很有意义且有趣的,你说呢?但愿,你还能进一步延伸:作业在检测到某一目录下的特定文件后,还要依据那些文件做其他许多你感兴趣的事。你可能会想把它们 FTP 到一台远程机器上,或者把它们作为电子邮件的附件发送。也许那是些客户发过来的订单文件,我们需要读取它们后插入到数据库中。无限可能性;我们会在本书的后面部分讨论它们。

我们努力让这一部分阐述地直截了当并且只涉及本质要义。然而,我们也会研究到一些会影响到 Quartz 应用程序运行行为的常用配置选项。我们假定你很好的掌握了 Java 语言;我们基本不会花时间去解翻译 Java 语言方面东西。

最后,本章的结束部分会简单的讨论怎么打包这个示例应用。在构建和打包 Java 工程时 Apache Ant 是我们的选择;Quartz  应用程序也不例外。

·建立 Quartz 工程
首要步骤是要建立起本工程的开发环境。你可以选择任何自己喜欢的开发工具或者感觉比较好的IDE;Quartz 并不发固执的要求你用哪一个工具。假如你还是接触Java没多久的开发者,Eclipse 会让你感觉特别的舒适;我们在本书的所有例子都是在 Eclipse 中讲解。

如果你还没有 Eclipse 的话,你可以从 http://eclipse.org 下载。你可以选择下载 3.x 的某个版本。在 http://www.eclipse.org/eclipse/index.html 可查看 Eclipse 的文档;你会找到能帮助你上手 Eclipse 的所有需要的资料。

·在 Eclipse 中配置使用 Quartz
 我们只为本书中的所有例子创建一个 Java 工程;每一章的源代被放在单独的目录中。图 3.1 显示了Eclipse 中的 Quartz 工程。
图 3.1 在 Eclipse 中创建一个 Quartz Java 工程


你必须引入几个 JAR 到工程中才能成功构建它们。首先,你需要 Quartz 的二进制版本,包的名字是 quartz-<version>.jar。Quartz 还需要几个第三方库;这依赖于你要用到框架的什么功能而定,Commons Digester 库可以在 <QUARTZ_HOME>/lib/core<QUARTZ_HOME>/lib/optional 目录中找到。表 3.1 列出了Quartz 依赖包的更多信息。

把 Quartz 源代码加入到 Eclipse 中来是个很好主意。这可以给你带来两方面的益处。其一,它允许你设置断点并跟入到 Quartz 源代码中。其二,它还帮助你深入浅出的学习这一框架。如果你要探察 Quartz 是怎么工作的,或者有时候为什么不能正常工作,那么这时候你真正需要手边有它的一套源代码。这是商业软件无法给你的便利。

Quartz Application 和 Job 的区别
我们在这里打断一下,有必要解释这个容易搞混的概念。我们用述语“Quartz Application”来指代任何使用到 Quartz 框架的软件程序。通常一个应用程序中会使用许多库,只要用上了 Quartz,我们认为这就是我们在本书上所讨论的 Quartz Application。另一方面,我们所说的 Quartz Job,是指执行一些作业的特定的 Java 类。正常地,在一个 Quartz Application 中会包括许多不同类型的 Job,每一个作业会对应有一个具体 Java 类。这两个概念不能交换使用。

·创建一个 Quartz Job 类
每一个 Quartz Job 必须有一个实现了 org.quartz.Job 接口的具体类。这个接口仅有一个要你在 Job 中实现的方法,execute(),方法 execute() 的原型如下:

public void execute(JobExecutionContext context) throws JobExecutionException;

当 Quartz 调度器确定到时间要激发一个 Job 的时候,它就会生成一个 Job 实例,并调用这个实例的 execute() 方法。调度器只管调用 execute() 方法,而不关心执行的结果,除了在作业执行中出问题抛出的 org.quartz.JobExecutionException 异常。

你可以在 execute() 方法中执行你的业务逻辑:例如,也许你会调用其他构造的实例上的方法,发送一个电子邮件、FTP 传一个文件、调用一个 Web 服务、调用一个EJB、执行一个工作流,或者像我们的例子中那样,检查某个特定的目录下是否存在文件。

代码 3.1 是我们的第一个 Quartz job,它被设计来扫描一个目录中的文并显示文件的详细信息。

代码 3.1. ScanDirectoryJob 例子
  1. package org.cavaness.quartzbook.chapter3;   
  2.   
  3. import java.io.File;   
  4. import java.util.Date;   
  5.   
  6. import org.apache.commons.logging.Log;   
  7. import Org.apache.commons.logging.LogFactory;   
  8. import org.quartz.Job;   
  9. import org.quartz.JobDataMap;   
  10. import org.quartz.JobDetail;   
  11. import org.quartz.JobExecutionContext;   
  12. import org.quartz.JobExecutionException;   
  13.   
  14. /**  
  15.  * <p>  
  16.  * A simple Quartz job that, once configured, will scan a  
  17.  * directory and print out details about the files found  
  18.  * in the directory.  
  19.  * </p>  
  20.  * Subdirectories will filtered out by the use of a  
  21.  * <code>{@link FileExtensionFileFilter}</code>.  
  22.  *  
  23.  * @author Chuck Cavaness  
  24.  * @see java.io.FileFilter  
  25.  */  
  26. public class ScanDirectoryJob implements Job {   
  27.      static Log logger = LogFactory.getLog(ScanDirectoryJob.class);   
  28.   
  29.      public void execute(JobExecutionContext context)   
  30.                throws JobExecutionException {   
  31.   
  32.           // Every job has its own job detail   
  33.           JobDetail jobDetail = context.getJobDetail();   
  34.   
  35.           // The name is defined in the job definition   
  36.           String jobName = jobDetail.getName();   
  37.   
  38.           // Log the time the job started   
  39.           logger.info(jobName + " fired at " + new Date());   
  40.   
  41.           // The directory to scan is stored in the job map   
  42.           JobDataMap dataMap = jobDetail.getJobDataMap();   
  43.                     String dirName = dataMap.getString("SCAN_DIR");   
  44.   
  45.           // Validate the required input   
  46.           if (dirName == null) {   
  47.                throw new JobExecutionException( "Directory not configured" );   
  48.           }   
  49.   
  50.           // Make sure the directory exists   
  51.           File dir = new File(dirName);   
  52.           if (!dir.exists()) {   
  53.            throw new JobExecutionException( "Invalid Dir "+ dirName);   
  54.           }   
  55.   
  56.           // Use FileFilter to get only XML files   
  57.           FileFilter filter = new FileExtensionFileFilter(".xml");   
  58.   
  59.           File[] files = dir.listFiles(filter);   
  60.   
  61.           if (files == null || files.length <= 0) {   
  62.                logger.info("No XML files found in " + dir);   
  63.   
  64.                // Return since there were no files   
  65.                return;   
  66.           }   
  67.   
  68.           // The number of XML files   
  69.           int size = files.length;   
  70.   
  71.           // Iterate through the files found   
  72.           for (int i = 0; i < size; i++) {   
  73.   
  74.                File file = files[i];   
  75.   
  76.                // Log something interesting about each file.   
  77.                File aFile = file.getAbsoluteFile();   
  78.                long fileSize = file.length();   
  79.                String msg = aFile + " - Size: " + fileSize;   
  80.                logger.info(msg);   
  81.           }   
  82.      }   
  83. }   

让我们来细细看看代码 3.1 中做了些什么。

当 Quartz 调用 execute() 方法,会传递一个 org.quartz.JobExecutionContext 上下文变量,里面封装有 Quartz 的运行时环境和当前正执行的 job。通过 JobexecutionContext,你可以访问到调度器的信息,作业和作业上的触发器的信息,还有更多更多的信息。在代码 3.1 中,JobExecutionContext 被用来访问 org.quartz.JobDetail 类,JobDetail 类持有 Job 的详细信息,包括为 job 实例指定的名称,job 所属组,Job 是否被持久化(易失性),和许多其他感兴趣的属性。

JobDetail 又持有一个指向 org.quartz.JobDataMap 的引用。JobDataMap 中有为指定 job 配置的自定义属性。例如,在代码 3.1中,我们从 JobDataMap 中获得欲扫描的目录名,我们可以在 ScanDirectoryJob 中硬编码这个目录名,但是这样的话我们难以重用这个 Job 来扫描别的目录了。在后面有一节“编程方式调度一个 Quartz Job”,你将会看到目录是如何配置到 JobDataMap 的。

execute() 方法中剩下的就是标准 Java 代码了:获得目录名并创建一个 java.io.File 对象。它还对目录名作为简单的校验,确保是一个有效且存在的目录。接着调用 File 对象的 listFiles() 方法得到目录下的文件。还创建了一个 java.io.FileFilter 对象作为参数传递给 listFiles() 方法。org.quartzbook.cavaness.FileExtensionFileFilter 实现了 java.io.FileFilter 接口,它的作用是过滤掉目录仅返回 XML 文件。默认情况下,listFiles() 方法是返回目录中所有内容,不管是文件还是子目录,所以我们必须过滤一下,因为我们只对 XML 文件感兴趣。

 

注:
FileExtensionFileFilter 并非 Quartz 框架的一部分;它是 java.io.FileFilter 的子类,而是 Java 核心的一部分。FileExtensionFileFilter 被创建为我们例子的一部分,用来滤除其他内容而只保留 XML 文件。它相当有用,你可以考虑为你的应用建一系列的文件过滤器,然后在你的 Quartz jobs 中重用。

 

 

 

 

 

代码 3.2 是 FileExtensionFileFilter

  1. package  org.cavaness.quartzbook.chapter3;   
  2.   
  3. import  java.io.File;   
  4. import  java.io.FileFilter;   
  5.   
  6. /**  
  7.  * A FileFilter that only passes Files of the specified extension.  
  8.  * <p>  
  9.  * Directories do not pass the filter.  
  10.  *  
  11.  * @author Chuck Cavaness  
  12.  */   
  13. public   class  FileExtensionFileFilter  implements  FileFilter {   
  14.   
  15.       private  String extension;   
  16.   
  17.       public  FileExtensionFileFilter(String extension) {   
  18.            this .extension = extension;   
  19.      }   
  20.   
  21.       /*  
  22.       * Pass the File if it has the extension.  
  23.       */   
  24.       public   boolean  accept(File file) {   
  25.            // Lowercase the filename for easier comparison   
  26.           String lCaseFilename = file.getName().toLowerCase();   
  27.   
  28.            return  (file.isFile() &&   
  29.                        (lCaseFilename.indexOf(extension) >  0 )) ?  true : false ;   
  30.      }   
  31. }  

FileExtensionFileFilter 被用来屏蔽名称中不含字符串 “.xml” 的文件。它还屏蔽了子目录--这些子目录原本会让 listFiles() 方法正常返回。过滤器提供了一种很便利的方式选择性的向你的 Quartz 作业提供它能接受的作为输入的文件。

 

声明式之于编程式配置
在 Quartz 中,我们有两种途径配置应用程序的运行时属性:声明式和编程式。有一些框架是使用外部配置文件的方式;我们都知道,在软件中硬编码设置有它的局限性。

从其他方面来讲,你将要根据具体的需求和功能来选择用哪一种方式。下一节强调了何时用声明式何时选择编程式。因为多数的 Java 行业应用都偏向于声明的方式,这也是我们所推荐的。

 

 

 

 

 

 

 

在下一节中,我们讨论如何为调度器配置 Job 和运行 ScanDirectoryJob

[译者注] 翻译中还得细细考量,那些词要转换成中文,那些词保留成英文。例如,前面的 Job->作业、Application->应用、Task->任务、Scheduler之于调度器;具体下来 Quartz Application 可能比 Quartz 应用好理解,Quart Job 也没有 Quart 作业读来生硬。平时不细究,只是阅读英文资料的话,一眼掠过是不会太在意的,本人翻译中也有混用,还没能太明晰。


2. 调度 Quartz ScanDirectoryJob

到目前为止,我们已经创建了一个 Quartz job,但还没有决定怎么处置它--明显地,我们需以某种方式为这个 Job 设置一个运行时间表。时间表可以是一次性的事件,或者我们可能会安装它在除周日之外的每个午夜执行。你即刻将会看到,Quartz Schduler 是框架的心脏与灵魂。所有的 Job 都通过 Schduler 注册;必要时,Scheduler 也会创建 Job 类的实例,并执行实例的 execute() 方法。
Scheduler 会为每一次执行创建新的 Job 实例

Scheduler 在每次执行时都会为 Job 创建新的实例。这就意味着 Job 的任何实例变量在执行结束之后便会丢失。与此相反概念则可用述语有状态的(J2EE世界里常见语)来表达,但是应用 Quartz  ,一个有状态的 job 并不用多少开销,而且很容易的配置。当你创建一个有状态的 job 时,有一些东西对于 Quartz 来说是独特的。最主要的就是不会出现两个有着相同状态的 Job 实例并发执行。这可能会影响到程序的伸缩性。这些或更多的问题将在以后的章节中详细讨论。

·创建并运行 Quartz Scheduler

在具体谈论 ScanDirectoryJob 之前,让我们大略讨论一下如何实例化并运行 Quartz Scheduler 实例。代码 3.3 描述了创建和启动一个 Quartz Scheduler 实例的必要且基本的步骤。

代码 3.3 运行一个简单的 Quartz 调度器
  1. package org.cavaness.quartzbook.chapter3;   
  2.   
  3. package org.cavaness.quartzbook.chapter3;   
  4.   
  5. import java.util.Date;   
  6.   
  7. import org.apache.commons.logging.Log;   
  8. import org.apache.commons.logging.LogFactory;   
  9. import org.quartz.Scheduler;   
  10. import org.quartz.SchedulerException;   
  11. import org.quartz.impl.StdSchedulerFactory;   
  12.   
  13. public class SimpleScheduler {   
  14.      static Log logger = LogFactory.getLog(SimpleScheduler.class);   
  15.   
  16.      public static void main(String[] args) {   
  17.           SimpleScheduler simple = new SimpleScheduler();   
  18.           simple.startScheduler();   
  19.      }   
  20.   
  21.      public void startScheduler() {   
  22.           Scheduler scheduler = null;   
  23.   
  24.           try {   
  25.                // Get a Scheduler instance from the Factory   
  26.                scheduler = StdSchedulerFactory.getDefaultScheduler();   
  27.   
  28.                // Start the scheduler   
  29.                scheduler.start();   
  30.                logger.info("Scheduler started at " + new Date());   
  31.   
  32.           } catch (SchedulerException ex) {   
  33.                // deal with any exceptions   
  34.                logger.error(ex);   
  35.           }   
  36.      }   
  37. }   
运行上面 3.3 的代码,会有日志输出,你会看到类似如下的输出:

INFO [main] (SimpleScheduler.java:30) - Scheduler started at Mon Sep 05 13:06:38 EDT 2005

关闭 Quartz Info 级别的日志信息

假如你搭配着 Log4J 使用 Commons Logging 日志框架,就像本书的例子那样,你也许需要把除本书例子外,其他的所有 Info 级别以上的日志信息关闭掉。这是因为 Quartz 中 Debug 和 Info 级别的日志信息数量上大体相当。当你明白了 Quartz 在做什么的时候,你真正关注的信息却淹没在大量的日志信息中。为了不至于这样,你可以创建一个文件 log4j.properties 指定只输出 ERROR 级别信息,但是要为本书中的例子设置定显示 INFO 级别的信息。这里有一个 log4j.properties 的例子文件来达到这个目的:

  1. # Create stdout appender   
  2. log4j.rootLogger=error, stdout  
  3. # Configure the stdout appender to go to the Console   
  4. log4j.appender.stdout=org.apache.log4j.ConsoleAppender  
  5. # Configure stdout appender to use the PatternLayout   
  6. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  
  7. # Pattern output the caller's filename and line #   
  8. log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n  
  9. # Print messages of level INFO or above for examples   
  10. log4j.logger.org.cavaness.quartzbook=INFO  

文件 log4j.properties 配置了默认向标准输出只输出 ERROR 级别以上的日志信息,但是任何在包 org.cavaness.quartzbook 中的 INFO 以上级别的信息也会输出。这有赖于以上属性文件最后一行配置。

代码 3.3 展示了启动一个 Quartz 调度器是那么的简单。当调度器起来之后,你可以利用它做很多事情或者获取到它的许多信息。例如,你也许需要安排一些 Job  或者改变又安排在调度器上 Job 的执行次数。你也许需要让调度器处于暂停模式,接着再次启动它以便重新执行在其上安排的作业。当调度器处于暂停模式时,不执行任何作业,即使是作业到了它所期待的执行时间。代码 3.4 展示了怎么把调度器置为暂停模式然后又继续运行,这样调度器会从中止处继续执行。

代码 3.4 设置调度器为暂停模式
  1. private void modifyScheduler(Scheduler scheduler) {   
  2.   
  3.     try {   
  4.          if (!scheduler.isInStandbyMode()) {   
  5.               // pause the scheduler   
  6.               scheduler.standby();   
  7.          }   
  8.   
  9.          // Do something interesting here   
  10.   
  11.          // and then restart it   
  12.          scheduler.start();   
  13.   
  14.     } catch (SchedulerException ex) {   
  15.          logger.error(ex);   
  16.     }   
  17. }   

代码 3.4 中的片断仅仅是一个最简单的例子,说明了当你执有一个 Quartz 调度器的引用,你可以利用它做一些你有感兴趣的事情。当然了,并非说 Scheduler 只有处于暂停模式才能很好的利用它。例如,你能在调度器处于运行状态时,安排新的作业或者是卸下已存在的作业。我们将通过本书的一个调度器尽可能的去掌握关于 Quartz 更多的知识。

上面的例子看起来都很简单,但千万不要被误导了。我们还没有指定任何作业以及那些作业的执行时间表。虽然代码 3.3 中的代码确实能启动运行,可是我们没有指定任何作业来执行。这就是我们下一节要讨论的。

·编程式安排一个 Quartz Job

所有的要 Quartz 来执行的作业必须通过调度器来注册。大多情况下,这会在调度器启动前做好。正如本章前面说过,这一操作也提供了声明式与编程式两种实现途径的选择。首先,我们讲解如何用编程的方式;接下来在本章,我们会用声明的方式重做这个练习。

因为每一个 Job 都必须用 Scheduler 来注册,所以先定义一个 JobDetail,并关联到这个 Scheduler 实例。见代码 3.5。

代码 3.5. 编程式安排一个 Job

  1. package org.cavaness.quartzbook.chapter3;   
  2.   
  3. import java.util.Date;   
  4.   
  5. import org.apache.commons.logging.Log;   
  6. import org.apache.commons.logging.LogFactory;   
  7. import org.quartz.JobDetail;   
  8. import org.quartz.Scheduler;   
  9. import org.quartz.SchedulerException;   
  10. import org.quartz.Trigger;   
  11. import org.quartz.TriggerUtils;   
  12. import org.quartz.impl.StdSchedulerFactory;   
  13.   
  14. public class Listing_3_5 {   
  15.      static Log logger = LogFactory.getLog(Listing_3_5.class);   
  16.   
  17.      public static void main(String[] args) {   
  18.           Listing_3_5 example = new Listing_3_5();   
  19.   
  20.           try {   
  21.                // Create a Scheduler and schedule the Job   
  22.                Scheduler scheduler = example.createScheduler();   
  23.                example.scheduleJob(scheduler);   
  24.   
  25.                // Start the Scheduler running   
  26.                scheduler.start();   
  27.   
  28.                logger.info( "Scheduler started at " + new Date() )   
  29.   
  30.           } catch (SchedulerException ex) {   
  31.                logger.error(ex);   
  32.           }   
  33.     }   
  34.   
  35.     /*  
  36.      * return an instance of the Scheduler from the factory  
  37.      */  
  38.     public Scheduler createScheduler() throws SchedulerException {   
  39.          return StdSchedulerFactory.getDefaultScheduler();   
  40.     }   
  41.   
  42.     // Create and Schedule a ScanDirectoryJob with the Scheduler   
  43.     private void scheduleJob(Scheduler scheduler)   
  44.          throws SchedulerException {   
  45.   
  46.          // Create a JobDetail for the Job   
  47.          JobDetail jobDetail =   
  48.                        new JobDetail("ScanDirectory",   
  49.                    Scheduler.DEFAULT_GROUP,   
  50.                              ScanDirectoryJob.class);   
  51.   
  52.          // Configure the directory to scan   
  53.          jobDetail.getJobDataMap().put("SCAN_DIR",   
  54.                    "c:\\quartz-book\\input");   
  55.   
  56.          // Create a trigger that fires every 10 seconds, forever   
  57.          Trigger trigger = TriggerUtils.makeSecondlyTrigger(10);   
  58.          trigger.setName("scanTrigger");   
  59.          // Start the trigger firing from now   
  60.          trigger.setStartTime(new Date());   
  61.   
  62.          // Associate the trigger with the job in the scheduler   
  63.          scheduler.scheduleJob(jobDetail, trigger);   
  64.     }   
  65. }  

上面程序提供了一个理解如何编程式安排一个 Job 很好的例子。代码首先调用 createScheduler() 方法从 Scheduler 工厂获取一个 Scheduler 的实例。得到 Scheduler 实例之后,把它传递给 schedulerJob() 方法,由它把 job 同 Scheduler 进行关联。

首先,创建了我们想要运行的 job 的 JobDetail 对象。JobDetail 构造器的参数中包含指派给 job 的名称,逻辑组名,和实现 org.quartz.Job 接口的全限类名称。我们可以使用 JobDetail 的别的构造器。

public JobDetail();
public JobDetail(String name, String group, Class jobClass);
public JobDetail(String name, String group, Class jobClass,
     boolean volatility, boolean durability, boolean recover);




一个 job 在同一个 Scheduler 实例中通过名称和组名能唯一被标识。假如你增加两个具体相同名称和组名的 job,程序会抛出 ObjectAlreadyExistsException 的异常。

在本章前面有说过,JobDetail 扮演着某一 job 定义的角色。它带有 Job 实例的属性,能在运行时被所关联的 Job 访问到。其中在使用 JobDetail 时,的一个最重要的东西就是 JobDataMap,它被用来存放 Job 实例的状态和参数。在代码 3.5 中,待扫描的目录名称就是通过  scheduleJob() 方法存入到 JobDataMap 中的。

·理解和使用 Quartz Trigger

Job 只是一个部分而已。注意到代码 3.5,我们没有在 JobDetail 对象中为 job 设定执行日期和次数。这是 Quartz trigger 该做的事。顾名思义,Trigger 的责任就是触发一个 Job 去执行。当用 Scheduler 注册一个 Job 的时候要创建一个 Trigger 与这个 Job 相关联。Quartz 提供了四种类型的 Trigger,但其中两种是最为常用的,它们就是在下面章节中要用到的 SimpleTrigger 和  CronTrigger.

SimpleTrigger 是两个之中简单的那个,它主要用来激发单事件的 Job,Trigger 在指定时间激发,并重复 n 次--两次激发时间之间的延时为 m,然后结束作业。CronTrigger 非常复杂且强大。它是基于通用的公历,当需要用一种较复杂的时间表去执行一个 Job 时用到。例如,四月至九月的每个星期一、星期三、或星期五的午夜。

为更简单的使用 Trigger,Quartz 包含了一个工具类,叫做 org.quartz.TriggerUtils. TriggerUtils 提供了许多便捷的方法简化了构造和配置 trigger. 本章的例子中有用的就是 TriggerUtils 类;SimpleTriggerCronTrigger 会在后面章节中用到。

正如你从代码3.5中看到的那样,调用了 TriggerUtils 的方法 makeSecondlyTrigger() 来创建一个每10秒种激发一次的 trigger(实际是由 TriggerUtils 生成了一个 SimpleTrigger 实例,但是我们的代码并不想知道这些)。我们同样要给这个 trigger 实例一个名称并告诉它何时激发相应的 Job;在代码3.5 中,与之关联的 Job 会立即启动,因为由方法 setStartTime() 设定的是当前时间。

代码 3.5 演示的是如何向 Scheduler 注册单一 job。假如你有不只一个个 job (你也许就是),你将需要为每一个 job 创建各自的 JobDetail。每一个 JobDetail 必须通过 scheduleJob() 方法一一注册到 Scheduler 上。



回到代码  3.1 中,我们从代码中看到要扫描的目录名属性是从 JobDataMap 中获取到的。再看代码 3.5,你能发现这个属性是怎么设置的。

如果你想重用了一个 job 类,让它产生多个实例运行,那么你需要为每个实例都创建一个 JobDetail。例如,假如你想重用 ScanDirectoryJob 让它检查两个不同的目录,你需要创建并注册两个 JobDetail 实例。代码 3.6 显示了是如何做的。

代码 3.6. 运行 ScanDirectoryJob 的多个实例

  1. package org.cavaness.quartzbook.chapter3;   
  2.   
  3. import java.util.Date;   
  4.   
  5. import org.apache.commons.logging.Log;   
  6. import org.apache.commons.logging.LogFactory;   
  7. import org.quartz.JobDetail;   
  8. import org.quartz.Scheduler;   
  9. import org.quartz.SchedulerException;   
  10. import org.quartz.Trigger;   
  11. import org.quartz.TriggerUtils;   
  12. import org.quartz.impl.StdSchedulerFactory;   
  13.   
  14. public class Listing_3_6 {   
  15.      static Log logger = LogFactory.getLog(Listing_3_6.class);   
  16.   
  17.      public static void main(String[] args) {   
  18.           Listing_3_6 example = new Listing_3_6();   
  19.   
  20.           try {   
  21.                // Create a Scheduler and schedule the Job   
  22.                Scheduler scheduler = example.createScheduler();   
  23.   
  24.                // Jobs can be scheduled after Scheduler is running   
  25.                scheduler.start();   
  26.   
  27.                logger.info("Scheduler started at " + new Date());   
  28.   
  29.                // Schedule the first Job   
  30.                example.scheduleJob(scheduler, "ScanDirectory1",   
  31.                          ScanDirectoryJob.class,   
  32.                                      "c:\\quartz-book\\input"10);   
  33.   
  34.                // Schedule the second Job   
  35.                example.scheduleJob(scheduler, "ScanDirectory2",   
  36.                          ScanDirectoryJob.class,   
  37.                                "c:\\quartz-book\\input2"15);   
  38.   
  39.           } catch (SchedulerException ex) {   
  40.                logger.error(ex);   
  41.           }   
  42.      }   
  43.   
  44.      /*  
  45.       * return an instance of the Scheduler from the factory  
  46.       */  
  47.      public Scheduler createScheduler() throws SchedulerException {   
  48.           return StdSchedulerFactory.getDefaultScheduler();   
  49.      }   
  50.   
  51.      // Create and Schedule a ScanDirectoryJob with the Scheduler   
  52.      private void scheduleJob(Scheduler scheduler, String jobName,   
  53.                Class jobClass, String scanDir, int scanInterval)   
  54.                throws SchedulerException {   
  55.   
  56.            // Create a JobDetail for the Job   
  57.           JobDetail jobDetail =   
  58.                        new JobDetail(jobName,   
  59.                               Scheduler.DEFAULT_GROUP, jobClass);   
  60.   
  61.           // Configure the directory to scan   
  62.           jobDetail.getJobDataMap().put("SCAN_DIR", scanDir);   
  63.   
  64.           // Trigger that repeats every "scanInterval" secs forever   
  65.           Trigger trigger =   
  66.                       TriggerUtils.makeSecondlyTrigger(scanInterval);   
  67.   
  68.           trigger.setName(jobName + "-Trigger");   
  69.   
  70.           // Start the trigger firing from now   
  71.           trigger.setStartTime(new Date());   
  72.   
  73.           // Associate the trigger with the job in the scheduler   
  74.           scheduler.scheduleJob(jobDetail, trigger);   
  75.      }   
  76. }  

代码 3.6 和代码 3.5 非常的类似,只存在一点小小的区别。主要的区别是代码 3.6 中重构了允许多次调用 schedulerJob() 方法。在设置上比如 Job 名称和扫描间隔名称通过参数传。因此从 createScheduler() 方法获取到 Scheduler 实例后,两个 job(同一个类) 用不同的参数就被安排到了 Scheduler 上了。(译者注:当用调 createScheduler() 方法得到 Scheduler 实例后,都还没有往上注册 Job,何来两个 job 呢)。

在 Scheduler 启动之前还是之后安排 Job 代码

3.5 中,我们在安排 job 之前就调用了 Scheduler 的 start() 方法。回到代码 3.5 中,采用了另一种方式:我们是在 job 安排了之后调用了 start() 方法。Job 和 Trigger 可在任何时候在 Scheduler 添加或删除 (除非是调用了它的 shutdown()方法)。


·运行代码 3.6 中的程序

如果我们执行类 Listing_3_6,会得到类似如下的输出:

INFO [main] (Listing_3_6.java:35) - Scheduler started at Mon Sep 05 15:12:15 EDT 2005
 INFO [QuartzScheduler_Worker-0] ScanDirectory1 fired at Mon Sep 05 15:12:15 EDT 2005
 INFO [QuartzScheduler_Worker-0] - c:\quartz-book\input\order-145765.xml - Size: 0
 INFO [QuartzScheduler_Worker-0] - ScanDirectory2 fired at Mon Sep 05 15:12:15 EDT 2005
 INFO [QuartzScheduler_Worker-0] - No XML files found in c:\quartz-book\input2
 INFO [QuartzScheduler_Worker-1] - ScanDirectory1 fired at Mon Sep 05 15:12:25 EDT 2005
 INFO [QuartzScheduler_Worker-1] - c:\quartz-book\input\order-145765.xml - Size: 0
 INFO [QuartzScheduler_Worker-3] - ScanDirectory2 fired at Mon Sep 05 15:12:30 EDT 2005
 INFO [QuartzScheduler_Worker-3] - No XML files found in c:\quartz-book\input2