第三章:Hello Quartz

多數(shù)讀者都較容易從一個簡明扼要的例子中明白一個東西。作為寫作者,要注意避免把一章的內(nèi)容精簡地幾乎什么都沒了;作為讀者呢,需要有耐心并且要進(jìn)一步相信其后相關(guān)的章節(jié)應(yīng)該去閱讀,盡管這個例子看起來是如此之簡單。

有了這種初衷,這一章將為你介紹如何用 Quartz 框架創(chuàng)建一個簡單的應(yīng)用程序,它展示了一個典型的應(yīng)用。這個例子將讓你領(lǐng)略到創(chuàng)建和執(zhí)行一個簡單應(yīng)用的必要步驟。通過本章的學(xué)習(xí),為你學(xué)習(xí)本書的后續(xù)章節(jié)打下了堅(jiān)實(shí)的基礎(chǔ)。 

1. "Hello, World" Quartz 工程
本示例應(yīng)用比起眾所周知的 System.out.println("Hello world from Quartz") 來還是要有趣些。當(dāng)我們用 Quartz 執(zhí)行一個作業(yè)時,總是希望它能為我們執(zhí)行一些有趣且有意義的任務(wù)。因此,接下來我們就要做一些有用且有用的事情。

本章向您演示如何創(chuàng)建這么一個 Quartz 作業(yè),Quartz 應(yīng)用通知它要做事情的時候,就會去掃描指定的目錄尋找 XML 文件。假如在指定目錄中找到了一個或多個 XML 文件的話,它將會打印出文件的一些概要信息。是不是很有意義且有趣的,你說呢?但愿,你還能進(jìn)一步延伸:作業(yè)在檢測到某一目錄下的特定文件后,還要依據(jù)那些文件做其他許多你感興趣的事。你可能會想把它們 FTP 到一臺遠(yuǎn)程機(jī)器上,或者把它們作為電子郵件的附件發(fā)送。也許那是些客戶發(fā)過來的訂單文件,我們需要讀取它們后插入到數(shù)據(jù)庫中。無限可能性;我們會在本書的后面部分討論它們。

我們努力讓這一部分闡述地直截了當(dāng)并且只涉及本質(zhì)要義。然而,我們也會研究到一些會影響到 Quartz 應(yīng)用程序運(yùn)行行為的常用配置選項(xiàng)。我們假定你很好的掌握了 Java 語言;我們基本不會花時間去解翻譯 Java 語言方面東西。

最后,本章的結(jié)束部分會簡單的討論怎么打包這個示例應(yīng)用。在構(gòu)建和打包 Java 工程時 Apache Ant 是我們的選擇;Quartz  應(yīng)用程序也不例外。

·建立 Quartz 工程
首要步驟是要建立起本工程的開發(fā)環(huán)境。你可以選擇任何自己喜歡的開發(fā)工具或者感覺比較好的IDE;Quartz 并不發(fā)固執(zhí)的要求你用哪一個工具。假如你還是接觸Java沒多久的開發(fā)者,Eclipse 會讓你感覺特別的舒適;我們在本書的所有例子都是在 Eclipse 中講解。

如果你還沒有 Eclipse 的話,你可以從 http://eclipse.org 下載。你可以選擇下載 3.x 的某個版本。在 http://www.eclipse.org/eclipse/index.html 可查看 Eclipse 的文檔;你會找到能幫助你上手 Eclipse 的所有需要的資料。

·在 Eclipse 中配置使用 Quartz
 我們只為本書中的所有例子創(chuàng)建一個 Java 工程;每一章的源代被放在單獨(dú)的目錄中。圖 3.1 顯示了Eclipse 中的 Quartz 工程。
圖 3.1 在 Eclipse 中創(chuàng)建一個 Quartz Java 工程


你必須引入幾個 JAR 到工程中才能成功構(gòu)建它們。首先,你需要 Quartz 的二進(jìn)制版本,包的名字是 quartz-<version>.jar。Quartz 還需要幾個第三方庫;這依賴于你要用到框架的什么功能而定,Commons Digester 庫可以在 <QUARTZ_HOME>/lib/core<QUARTZ_HOME>/lib/optional 目錄中找到。表 3.1 列出了Quartz 依賴包的更多信息。

把 Quartz 源代碼加入到 Eclipse 中來是個很好主意。這可以給你帶來兩方面的益處。其一,它允許你設(shè)置斷點(diǎn)并跟入到 Quartz 源代碼中。其二,它還幫助你深入淺出的學(xué)習(xí)這一框架。如果你要探察 Quartz 是怎么工作的,或者有時候?yàn)槭裁床荒苷9ぷ鳎敲催@時候你真正需要手邊有它的一套源代碼。這是商業(yè)軟件無法給你的便利。

Quartz Application 和 Job 的區(qū)別
我們在這里打斷一下,有必要解釋這個容易搞混的概念。我們用述語“Quartz Application”來指代任何使用到 Quartz 框架的軟件程序。通常一個應(yīng)用程序中會使用許多庫,只要用上了 Quartz,我們認(rèn)為這就是我們在本書上所討論的 Quartz Application。另一方面,我們所說的 Quartz Job,是指執(zhí)行一些作業(yè)的特定的 Java 類。正常地,在一個 Quartz Application 中會包括許多不同類型的 Job,每一個作業(yè)會對應(yīng)有一個具體 Java 類。這兩個概念不能交換使用。

·創(chuàng)建一個 Quartz Job 類
每一個 Quartz Job 必須有一個實(shí)現(xiàn)了 org.quartz.Job 接口的具體類。這個接口僅有一個要你在 Job 中實(shí)現(xiàn)的方法,execute(),方法 execute() 的原型如下:

public void execute(JobExecutionContext context) throws JobExecutionException;

當(dāng) Quartz 調(diào)度器確定到時間要激發(fā)一個 Job 的時候,它就會生成一個 Job 實(shí)例,并調(diào)用這個實(shí)例的 execute() 方法。調(diào)度器只管調(diào)用 execute() 方法,而不關(guān)心執(zhí)行的結(jié)果,除了在作業(yè)執(zhí)行中出問題拋出的 org.quartz.JobExecutionException 異常。

你可以在 execute() 方法中執(zhí)行你的業(yè)務(wù)邏輯:例如,也許你會調(diào)用其他構(gòu)造的實(shí)例上的方法,發(fā)送一個電子郵件、FTP 傳一個文件、調(diào)用一個 Web 服務(wù)、調(diào)用一個EJB、執(zhí)行一個工作流,或者像我們的例子中那樣,檢查某個特定的目錄下是否存在文件。

代碼 3.1 是我們的第一個 Quartz job,它被設(shè)計(jì)來掃描一個目錄中的文并顯示文件的詳細(xì)信息。

代碼 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. }   

讓我們來細(xì)細(xì)看看代碼 3.1 中做了些什么。

當(dāng) Quartz 調(diào)用 execute() 方法,會傳遞一個 org.quartz.JobExecutionContext 上下文變量,里面封裝有 Quartz 的運(yùn)行時環(huán)境和當(dāng)前正執(zhí)行的 job。通過 JobexecutionContext,你可以訪問到調(diào)度器的信息,作業(yè)和作業(yè)上的觸發(fā)器的信息,還有更多更多的信息。在代碼 3.1 中,JobExecutionContext 被用來訪問 org.quartz.JobDetail 類,JobDetail 類持有 Job 的詳細(xì)信息,包括為 job 實(shí)例指定的名稱,job 所屬組,Job 是否被持久化(易失性),和許多其他感興趣的屬性。

JobDetail 又持有一個指向 org.quartz.JobDataMap 的引用。JobDataMap 中有為指定 job 配置的自定義屬性。例如,在代碼 3.1中,我們從 JobDataMap 中獲得欲掃描的目錄名,我們可以在 ScanDirectoryJob 中硬編碼這個目錄名,但是這樣的話我們難以重用這個 Job 來掃描別的目錄了。在后面有一節(jié)“編程方式調(diào)度一個 Quartz Job”,你將會看到目錄是如何配置到 JobDataMap 的。

execute() 方法中剩下的就是標(biāo)準(zhǔn) Java 代碼了:獲得目錄名并創(chuàng)建一個 java.io.File 對象。它還對目錄名作為簡單的校驗(yàn),確保是一個有效且存在的目錄。接著調(diào)用 File 對象的 listFiles() 方法得到目錄下的文件。還創(chuàng)建了一個 java.io.FileFilter 對象作為參數(shù)傳遞給 listFiles() 方法。org.quartzbook.cavaness.FileExtensionFileFilter 實(shí)現(xiàn)了 java.io.FileFilter 接口,它的作用是過濾掉目錄僅返回 XML 文件。默認(rèn)情況下,listFiles() 方法是返回目錄中所有內(nèi)容,不管是文件還是子目錄,所以我們必須過濾一下,因?yàn)槲覀冎粚?XML 文件感興趣。

 

注:
FileExtensionFileFilter 并非 Quartz 框架的一部分;它是 java.io.FileFilter 的子類,而是 Java 核心的一部分。FileExtensionFileFilter 被創(chuàng)建為我們例子的一部分,用來濾除其他內(nèi)容而只保留 XML 文件。它相當(dāng)有用,你可以考慮為你的應(yīng)用建一系列的文件過濾器,然后在你的 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 作業(yè)提供它能接受的作為輸入的文件。

 

聲明式之于編程式配置
在 Quartz 中,我們有兩種途徑配置應(yīng)用程序的運(yùn)行時屬性:聲明式和編程式。有一些框架是使用外部配置文件的方式;我們都知道,在軟件中硬編碼設(shè)置有它的局限性。

從其他方面來講,你將要根據(jù)具體的需求和功能來選擇用哪一種方式。下一節(jié)強(qiáng)調(diào)了何時用聲明式何時選擇編程式。因?yàn)槎鄶?shù)的 Java 行業(yè)應(yīng)用都偏向于聲明的方式,這也是我們所推薦的。

 

 

 

 

 

 

 

在下一節(jié)中,我們討論如何為調(diào)度器配置 Job 和運(yùn)行 ScanDirectoryJob

[譯者注] 翻譯中還得細(xì)細(xì)考量,那些詞要轉(zhuǎn)換成中文,那些詞保留成英文。例如,前面的 Job->作業(yè)、Application->應(yīng)用、Task->任務(wù)、Scheduler之于調(diào)度器;具體下來 Quartz Application 可能比 Quartz 應(yīng)用好理解,Quart Job 也沒有 Quart 作業(yè)讀來生硬。平時不細(xì)究,只是閱讀英文資料的話,一眼掠過是不會太在意的,本人翻譯中也有混用,還沒能太明晰。


2. 調(diào)度 Quartz ScanDirectoryJob

到目前為止,我們已經(jīng)創(chuàng)建了一個 Quartz job,但還沒有決定怎么處置它--明顯地,我們需以某種方式為這個 Job 設(shè)置一個運(yùn)行時間表。時間表可以是一次性的事件,或者我們可能會安裝它在除周日之外的每個午夜執(zhí)行。你即刻將會看到,Quartz Schduler 是框架的心臟與靈魂。所有的 Job 都通過 Schduler 注冊;必要時,Scheduler 也會創(chuàng)建 Job 類的實(shí)例,并執(zhí)行實(shí)例的 execute() 方法。
Scheduler 會為每一次執(zhí)行創(chuàng)建新的 Job 實(shí)例

Scheduler 在每次執(zhí)行時都會為 Job 創(chuàng)建新的實(shí)例。這就意味著 Job 的任何實(shí)例變量在執(zhí)行結(jié)束之后便會丟失。與此相反概念則可用述語有狀態(tài)的(J2EE世界里常見語)來表達(dá),但是應(yīng)用 Quartz  ,一個有狀態(tài)的 job 并不用多少開銷,而且很容易的配置。當(dāng)你創(chuàng)建一個有狀態(tài)的 job 時,有一些東西對于 Quartz 來說是獨(dú)特的。最主要的就是不會出現(xiàn)兩個有著相同狀態(tài)的 Job 實(shí)例并發(fā)執(zhí)行。這可能會影響到程序的伸縮性。這些或更多的問題將在以后的章節(jié)中詳細(xì)討論。

·創(chuàng)建并運(yùn)行 Quartz Scheduler

在具體談?wù)?ScanDirectoryJob 之前,讓我們大略討論一下如何實(shí)例化并運(yùn)行 Quartz Scheduler 實(shí)例。代碼 3.3 描述了創(chuàng)建和啟動一個 Quartz Scheduler 實(shí)例的必要且基本的步驟。

代碼 3.3 運(yùn)行一個簡單的 Quartz 調(diào)度器
  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. }   
運(yùn)行上面 3.3 的代碼,會有日志輸出,你會看到類似如下的輸出:

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

關(guān)閉 Quartz Info 級別的日志信息

假如你搭配著 Log4J 使用 Commons Logging 日志框架,就像本書的例子那樣,你也許需要把除本書例子外,其他的所有 Info 級別以上的日志信息關(guān)閉掉。這是因?yàn)?Quartz 中 Debug 和 Info 級別的日志信息數(shù)量上大體相當(dāng)。當(dāng)你明白了 Quartz 在做什么的時候,你真正關(guān)注的信息卻淹沒在大量的日志信息中。為了不至于這樣,你可以創(chuàng)建一個文件 log4j.properties 指定只輸出 ERROR 級別信息,但是要為本書中的例子設(shè)置定顯示 INFO 級別的信息。這里有一個 log4j.properties 的例子文件來達(dá)到這個目的:

  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 配置了默認(rèn)向標(biāo)準(zhǔn)輸出只輸出 ERROR 級別以上的日志信息,但是任何在包 org.cavaness.quartzbook 中的 INFO 以上級別的信息也會輸出。這有賴于以上屬性文件最后一行配置。

代碼 3.3 展示了啟動一個 Quartz 調(diào)度器是那么的簡單。當(dāng)調(diào)度器起來之后,你可以利用它做很多事情或者獲取到它的許多信息。例如,你也許需要安排一些 Job  或者改變又安排在調(diào)度器上 Job 的執(zhí)行次數(shù)。你也許需要讓調(diào)度器處于暫停模式,接著再次啟動它以便重新執(zhí)行在其上安排的作業(yè)。當(dāng)調(diào)度器處于暫停模式時,不執(zhí)行任何作業(yè),即使是作業(yè)到了它所期待的執(zhí)行時間。代碼 3.4 展示了怎么把調(diào)度器置為暫停模式然后又繼續(xù)運(yùn)行,這樣調(diào)度器會從中止處繼續(xù)執(zhí)行。

代碼 3.4 設(shè)置調(diào)度器為暫停模式
  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 中的片斷僅僅是一個最簡單的例子,說明了當(dāng)你執(zhí)有一個 Quartz 調(diào)度器的引用,你可以利用它做一些你有感興趣的事情。當(dāng)然了,并非說 Scheduler 只有處于暫停模式才能很好的利用它。例如,你能在調(diào)度器處于運(yùn)行狀態(tài)時,安排新的作業(yè)或者是卸下已存在的作業(yè)。我們將通過本書的一個調(diào)度器盡可能的去掌握關(guān)于 Quartz 更多的知識。

上面的例子看起來都很簡單,但千萬不要被誤導(dǎo)了。我們還沒有指定任何作業(yè)以及那些作業(yè)的執(zhí)行時間表。雖然代碼 3.3 中的代碼確實(shí)能啟動運(yùn)行,可是我們沒有指定任何作業(yè)來執(zhí)行。這就是我們下一節(jié)要討論的。

·編程式安排一個 Quartz Job

所有的要 Quartz 來執(zhí)行的作業(yè)必須通過調(diào)度器來注冊。大多情況下,這會在調(diào)度器啟動前做好。正如本章前面說過,這一操作也提供了聲明式與編程式兩種實(shí)現(xiàn)途徑的選擇。首先,我們講解如何用編程的方式;接下來在本章,我們會用聲明的方式重做這個練習(xí)。

因?yàn)槊恳粋€ Job 都必須用 Scheduler 來注冊,所以先定義一個 JobDetail,并關(guān)聯(lián)到這個 Scheduler 實(shí)例。見代碼 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 很好的例子。代碼首先調(diào)用 createScheduler() 方法從 Scheduler 工廠獲取一個 Scheduler 的實(shí)例。得到 Scheduler 實(shí)例之后,把它傳遞給 schedulerJob() 方法,由它把 job 同 Scheduler 進(jìn)行關(guān)聯(lián)。

首先,創(chuàng)建了我們想要運(yùn)行的 job 的 JobDetail 對象。JobDetail 構(gòu)造器的參數(shù)中包含指派給 job 的名稱,邏輯組名,和實(shí)現(xiàn) org.quartz.Job 接口的全限類名稱。我們可以使用 JobDetail 的別的構(gòu)造器。

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 實(shí)例中通過名稱和組名能唯一被標(biāo)識。假如你增加兩個具體相同名稱和組名的 job,程序會拋出 ObjectAlreadyExistsException 的異常。

在本章前面有說過,JobDetail 扮演著某一 job 定義的角色。它帶有 Job 實(shí)例的屬性,能在運(yùn)行時被所關(guān)聯(lián)的 Job 訪問到。其中在使用 JobDetail 時,的一個最重要的東西就是 JobDataMap,它被用來存放 Job 實(shí)例的狀態(tài)和參數(shù)。在代碼 3.5 中,待掃描的目錄名稱就是通過  scheduleJob() 方法存入到 JobDataMap 中的。

·理解和使用 Quartz Trigger

Job 只是一個部分而已。注意到代碼 3.5,我們沒有在 JobDetail 對象中為 job 設(shè)定執(zhí)行日期和次數(shù)。這是 Quartz trigger 該做的事。顧名思義,Trigger 的責(zé)任就是觸發(fā)一個 Job 去執(zhí)行。當(dāng)用 Scheduler 注冊一個 Job 的時候要創(chuàng)建一個 Trigger 與這個 Job 相關(guān)聯(lián)。Quartz 提供了四種類型的 Trigger,但其中兩種是最為常用的,它們就是在下面章節(jié)中要用到的 SimpleTrigger 和  CronTrigger.

SimpleTrigger 是兩個之中簡單的那個,它主要用來激發(fā)單事件的 Job,Trigger 在指定時間激發(fā),并重復(fù) n 次--兩次激發(fā)時間之間的延時為 m,然后結(jié)束作業(yè)。CronTrigger 非常復(fù)雜且強(qiáng)大。它是基于通用的公歷,當(dāng)需要用一種較復(fù)雜的時間表去執(zhí)行一個 Job 時用到。例如,四月至九月的每個星期一、星期三、或星期五的午夜。

為更簡單的使用 Trigger,Quartz 包含了一個工具類,叫做 org.quartz.TriggerUtils. TriggerUtils 提供了許多便捷的方法簡化了構(gòu)造和配置 trigger. 本章的例子中有用的就是 TriggerUtils 類;SimpleTriggerCronTrigger 會在后面章節(jié)中用到。

正如你從代碼3.5中看到的那樣,調(diào)用了 TriggerUtils 的方法 makeSecondlyTrigger() 來創(chuàng)建一個每10秒種激發(fā)一次的 trigger(實(shí)際是由 TriggerUtils 生成了一個 SimpleTrigger 實(shí)例,但是我們的代碼并不想知道這些)。我們同樣要給這個 trigger 實(shí)例一個名稱并告訴它何時激發(fā)相應(yīng)的 Job;在代碼3.5 中,與之關(guān)聯(lián)的 Job 會立即啟動,因?yàn)橛煞椒?setStartTime() 設(shè)定的是當(dāng)前時間。

代碼 3.5 演示的是如何向 Scheduler 注冊單一 job。假如你有不只一個個 job (你也許就是),你將需要為每一個 job 創(chuàng)建各自的 JobDetail。每一個 JobDetail 必須通過 scheduleJob() 方法一一注冊到 Scheduler 上。



回到代碼  3.1 中,我們從代碼中看到要掃描的目錄名屬性是從 JobDataMap 中獲取到的。再看代碼 3.5,你能發(fā)現(xiàn)這個屬性是怎么設(shè)置的。

如果你想重用了一個 job 類,讓它產(chǎn)生多個實(shí)例運(yùn)行,那么你需要為每個實(shí)例都創(chuàng)建一個 JobDetail。例如,假如你想重用 ScanDirectoryJob 讓它檢查兩個不同的目錄,你需要創(chuàng)建并注冊兩個 JobDetail 實(shí)例。代碼 3.6 顯示了是如何做的。

代碼 3.6. 運(yùn)行 ScanDirectoryJob 的多個實(shí)例

  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 非常的類似,只存在一點(diǎn)小小的區(qū)別。主要的區(qū)別是代碼 3.6 中重構(gòu)了允許多次調(diào)用 schedulerJob() 方法。在設(shè)置上比如 Job 名稱和掃描間隔名稱通過參數(shù)傳。因此從 createScheduler() 方法獲取到 Scheduler 實(shí)例后,兩個 job(同一個類) 用不同的參數(shù)就被安排到了 Scheduler 上了。(譯者注:當(dāng)用調(diào) createScheduler() 方法得到 Scheduler 實(shí)例后,都還沒有往上注冊 Job,何來兩個 job 呢)。

在 Scheduler 啟動之前還是之后安排 Job 代碼

3.5 中,我們在安排 job 之前就調(diào)用了 Scheduler 的 start() 方法。回到代碼 3.5 中,采用了另一種方式:我們是在 job 安排了之后調(diào)用了 start() 方法。Job 和 Trigger 可在任何時候在 Scheduler 添加或刪除 (除非是調(diào)用了它的 shutdown()方法)。


·運(yùn)行代碼 3.6 中的程序

如果我們執(zhí)行類 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