3. 聲明式部署一個 Job

前面我們討論過,盡可能的用聲明式處理軟件配置,其次才才慮編程式。再來看代碼  3.6,如果我們要在 job 啟動之后改變它的執行時間和頻度,必須去修改源代碼重新編譯。這種方式只適用于小的例子程序,但是對于一個大且復雜的系統,這就成了一個問題了。因此,假如能以聲明式部署 Quart Job 時,并且也是需求允許的情況下,你應該每次都選擇這種方式。

·配置 quartz.properties 文件

文件 quartz.properties 定義了 Quartz 應用運行時行為,還包含了許多能控制 Quartz 運轉的屬性。本章只會講到它的基本配置;更多的高級設置將在以后討論。在現階段也不用太深入到每一項配置有效值的細節。

現在我們來看看最基礎的 quartz.properties 文件,并討論其中一些設置。代碼 3.7 是一個修剪版的 quartz.propertis 文件。


 Quartz 框架會為幾乎所有的這些屬性設定默認值。

代碼 3.7. 基本的 Quartz Properties 文件

  1. #===============================================================  
  2. #Configure Main Scheduler Properties  
  3. #===============================================================   
  4. org.quartz.scheduler.instanceName = QuartzScheduler   
  5. org.quartz.scheduler.instanceId = AUTO  
  6. #===============================================================  
  7. #Configure ThreadPool  
  8. #===============================================================   
  9. org.quartz.threadPool.threadCount =  5   
  10. org.quartz.threadPool.threadPriority = 5   
  11. org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool  
  12. #===============================================================  
  13. #Configure JobStore  
  14. #===============================================================   
  15. org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore  
  16. #===============================================================  
  17. #Configure Plugins  
  18. #===============================================================   
  19. org.quartz.plugin.jobInitializer.class =   
  20. org.quartz.plugins.xml.JobInitializationPlugin   
  21.   
  22. org.quartz.plugin.jobInitializer.overWriteExistingJobs = true  
  23. org.quartz.plugin.jobInitializer.failOnFileNotFound = true  
  24. org.quartz.plugin.jobInitializer.validating=false  

在代碼 3.7 所示的 quartz.properties 文件中,屬性被邏輯上分為了四部分。屬性在寫法上無須要求分組或按特定的順序。有 # 的行是注釋行。


這里討論的并沒有涉及到所有可能的設置,僅僅是一些基本的設置。也是你需要去熟悉的,能使聲明式例子運轉起來的必須的設置項。quartz.properties 中的所有屬性配置將會分散在本書中的各章節中依據所在章節涉及內容詳細討論。

·調度器屬性

第一部分有兩行,分別設置調度器的實例名(instanceName) 和實例 ID (instanceId)。屬性 org.quartz.scheduler.instanceName 可以是你喜歡的任何字符串。它用來在用到多個調度器區分特定的調度器實例。多個調度器通常用在集群環境中。(Quartz 集群將會在第十一章,“Quartz 集群”中討論)。現在的話,設置如下的一個字符串就行:

org.quartz.scheduler.instanceName = QuartzScheduler

實際上,這也是當你沒有該屬性配置時的默認值。

代碼 3.7 中顯示的調度器的第二個屬性是 org.quartz.scheduler.instanceId。和 instaneName 屬性一樣,instanceId 屬性也允許任何字符串。這個值必須是在所有調度器實例中是唯一的,尤其是在一個集群當中。假如你想 Quartz 幫你生成這個值的話,可以設置為 AUTO。如果 Quartz 框架是運行在非集群環境中,那么自動產生的值將會是 NON_CLUSTERED。假如是在集群環境下使用 Quartz,這個值將會是主機名加上當前的日期和時間。大多情況下,設置為 AUTO 即可。

·線程池屬性

接下來的部分是設置有關線程必要的屬性值,這些線程在 Quartz 中是運行在后臺擔當重任的。threadCount 屬性控制了多少個工作者線程被創建用來處理 Job。原則上是,要處理的 Job 越多,那么需要的工作者線程也就越多。threadCount 的數值至少為 1。Quartz 沒有限定你設置工作者線程的最大值,但是在多數機器上設置該值超過100的話就會顯得相當不實用了,特別是在你的 Job 執行時間較長的情況下。這項沒有默認值,所以你必須為這個屬性設定一個值。

threadPriority 屬性設置工作者線程的優先級。優先級別高的線程比級別低的線程更優先得到執行。threadPriority 屬性的最大值是常量 java.lang.Thread.MAX_PRIORITY,等于10。最小值為常量 java.lang.Thread.MIN_PRIORITY,為1。這個屬性的正常值是 Thread.NORM_PRIORITY,為5。大多情況下,把它設置為5,這也是沒指定該屬性的默認值。

最后一個要設置的線程池屬性是 org.quartz.threadPool.class。這個值是一個實現了 org.quartz.spi.ThreadPool 接口的類的全限名稱。Quartz 自帶的線程池實現類是 org.quartz.smpl.SimpleThreadPool,它能夠滿足大多數用戶的需求。這個線程池實現具備簡單的行為,并經很好的測試過。它在調度器的生命周期中提供固定大小的線程池。你能根據需求創建自己的線程池實現,如果你想要一個隨需可伸縮的線程池時也許需要這么做。這個屬性沒有默認值,你必須為其指定值。

·作業存儲設置

作業存儲部分的設置描述了在調度器實例的生命周期中,Job 和 Trigger 信息是如何被存儲的。我們還沒有談論到作業存儲和它的目的;因為對當前例子是非必的,所以我們留待以后說明。現在的話,你所要了解的就是我們存儲調度器信息在內存中而不是在關系型數據庫中就行了。

把調度器信息存儲在內存中非常的快也易于配置。當調度器進程一旦被終止,所有的 Job 和 Trigger 的狀態就丟失了。要使 Job 存儲在內存中需通過設置  org.quartz.jobStrore.class 屬性為 org.quartz.simpl.RAMJobStore,就像在代碼 3.7 所做的那樣。假如我們不希望在 JVM 退出之后丟失調度器的狀態信息的話,我們可以使用關系型數據庫來存儲這些信息。這需要另一個作業存儲(JobStore) 實現,我們在后面將會討論到。第五章“Cron Trigger 和其他”和第六章“作業存儲和持久化”會提到你需要用到的不同類型的作業存儲實現。

·插件配置

在這個簡單的 quartz.properties 文件中最后一部分是你要用到的 Quart 插件的配置。插件常常在別的開源框架上使用到,比如 Apache 的 Struts 框架(見 http://struts.apache.org)。

一個聲明式擴框架的方法就是通過新加實現了 org.quartz.spi.SchedulerPlugin 接口的類。SchedulerPlugin  接口中有給調度器調用的三個方法。


Quartz 插件會在第八章“使用 Quartz 插件”中詳細討論

要在我們的例子中聲明式配置調度器信息,我們會用到一個 Quartz 自帶的叫做 org.quartz.plugins.xml.JobInitializationPlugin 的插件。

默認時,這個插件會在 classpath 中搜索名為 quartz_jobs.xml 的文件并從中加載 Job 和 Trigger 信息。

在下一節中討論 quartz_jobs.xml 文件,這是我們所參考的非正式的 Job 定義文件。


默認時,插件 JobInitializationPlugin 在 classpath 中尋找 quartz_jobs.xml 文件。你可以覆蓋相應設置強制這個插件使用不同的文件名查找。要做到這個,你必須設置上一節討論的 quartz.properties 中的文件名。目前,我們就使用默認的文件名 quartz_jobs.xml,至于如何修改 quartz.properties 中相應設置會在本章中后面講到。

·使用 quartz_jobx.xml 文件

代碼 3.8 就是目錄掃描例子的 Job 定義的 XML 文件。正如代碼 3.5 所示例子那樣,這里我們用的是聲明式途徑來配置 Job 和 Trigger 信息的。

代碼 3.8. ScanDirectory Job 的 quartz_jobs.xml
  1. <?xml version='1.0' encoding='utf-8'?>  
  2.   
  3. <quartz>  
  4.   
  5.   <job>  
  6.     <job-detail>  
  7.      <name>ScanDirectory</name>  
  8.      <group>DEFAULT</group>  
  9.      <description>  
  10.           A job that scans a directory for files   
  11.      </description>  
  12.      <job-class>  
  13.             org.cavaness.quartzbook.chapter3.ScanDirectoryJob   
  14.      </job-class>  
  15.      <volatility>false</volatility>  
  16.      <durability>false</durability>  
  17.      <recover>false</recover>  
  18.      <job-data-map allows-transient-data="true">  
  19.          <entry>  
  20.          <key>SCAN_DIR</key>  
  21.          <value>c:\quartz-book\input</value>  
  22.        </entry>  
  23.      </job-data-map>  
  24.     </job-detail>  
  25.   
  26.     <trigger>  
  27.      <simple>  
  28.        <name>scanTrigger</name>  
  29.        <group>DEFAULT</group>  
  30.        <job-name>ScanDirectory</job-name>  
  31.        <job-group>DEFAULT</job-group>  
  32.        <start-time>2005-06-10 6:10:00 PM</start-time>  
  33.        <!-- repeat indefinitely every 10 seconds -->  
  34.        <repeat-count>-1</repeat-count>  
  35.        <repeat-interval>10000</repeat-interval>  
  36.      </simple>  
  37.     </trigger>  
  38.   
  39.   </job>  
  40. </quartz>  

<job> 元素描述了一個要注冊到調度器上的 Job,相當于我們在前面章節中使用 scheduleJob() 方法那樣。你所看到的<job-detail> 和  <trigger> 這兩個元素就是我們在代碼 3.5 中以編程式傳遞給方法 schedulerJob() 的參數。前面本質上是與這里一樣的,只是現在用的是一種較流行聲明的方式。你還可以對照著代碼 3.5 中的例子來看在代碼3.8 中我們是如何設置 SCAN_DIR 屬性到 JobDataMap 中的。

<trigger>元素也是非常直觀的:它使用前面同樣的屬性,但更簡單的建立一個 SimpleTrigger。因此代碼 3.8 僅僅是一種不同的(可論證的且更好的)方式做了代碼 3.5 中同樣的事情。顯然,你也可以支持多個 Job。在代碼3.6 中我們編程的方式那么做的,也能用聲明的方式來支持。代碼 3.9 顯示了與代碼 3.6 可比較的版本

代碼 3.9. 你能在一個 quartz_jobs.xml 文件中指定多個 Job

  1. <?xml version='1.0' encoding='utf-8'?>  
  2.   
  3. <quartz>  
  4.   <job>  
  5.     <job-detail>  
  6.      <name>ScanDirectory1</name>  
  7.      <group>DEFAULT</group>  
  8.      <description>  
  9.            A job that scans a directory for files   
  10.      </description>  
  11.      <job-class>  
  12.            org.cavaness.quartzbook.chapter3.ScanDirectoryJob   
  13.      </job-class>  
  14.      <volatility>false</volatility>  
  15.      <durability>false</durability>  
  16.      <recover>false</recover>  
  17.   
  18.      <job-data-map allows-transient-data="true">  
  19.      <entry>  
  20.        <key>SCAN_DIR</key>  
  21.          <value>c:\quartz-book\input1</value>  
  22.      </entry>  
  23.     </job-data-map>  
  24.   </job-detail>  
  25.   
  26.   <trigger>  
  27.     <simple>  
  28.      <name>scanTrigger1</name>  
  29.      <group>DEFAULT</group>  
  30.      <job-name>ScanDirectory1</job-name>  
  31.      <job-group>DEFAULT</job-group>  
  32.      <start-time>2005-07-19 8:31:00 PM</start-time>  
  33.      <!-- repeat indefinitely every 10 seconds -->  
  34.      <repeat-count>-1</repeat-count>  
  35.      <repeat-interval>10000</repeat-interval>  
  36.     </simple>  
  37.   </trigger>  
  38. </job>  
  39.   
  40. <job>  
  41.   <job-detail>  
  42.     <name>ScanDirectory2</name>  
  43.     <group>DEFAULT</group>  
  44.     <description>  
  45.           A job that scans a directory for files   
  46.     </description>  
  47.     <job-class>  
  48.           org.cavaness.quartzbook.chapter3.ScanDirectoryJob   
  49.     </job-class>  
  50.     <volatility>false</volatility>  
  51.     <durability>false</durability>  
  52.     <recover>false</recover>  
  53.   
  54.     <job-data-map allows-transient-data="true">  
  55.       <entry>  
  56.        <key>SCAN_DIR</key>  
  57.        <value>c:\quartz-book\input2</value>  
  58.      </entry>  
  59.     </job-data-map>  
  60.   </job-detail>  
  61.   
  62.   <trigger>  
  63.     <simple>  
  64.      <name>scanTrigger2</name>  
  65.      <group>DEFAULT</group>  
  66.      <job-name>ScanDirectory2</job-name>  
  67.      <job-group>DEFAULT</job-group>  
  68.      <start-time>2005-06-10 6:10:00 PM</start-time>  
  69.      <!-- repeat indefinitely every 15 seconds -->  
  70.      <repeat-count>-1</repeat-count>  
  71.      <repeat-interval>15000</repeat-interval>  
  72.     </simple>  
  73.   </trigger>  
  74.  </job>  
  75. </quartz>  

·為插件修改 quartz.properties 配置

在本章前面,告訴過你的是,JobInitializationPlugin 找尋 quartz_jobs.xml 來獲得聲明的 Job 信息。假如你想改變這個文件名,你需要修改 quartz.properties 來告訴插件去加載那個文件。例如,假如你想要 Quartz 從名為 my_quartz_jobs.xml 的 XML 文件中加載 Job 信息,你不得不為插件指定這一文件名。代碼 3.10 顯示了怎么完成這個配置;我們現在是最后一次在這里重復說明這一插件部分。

代碼 3.10. 為 JobInitializationPlugin 修改 quartz.properties

  1. org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin   
  2. org.quartz.plugin.jobInitializer.fileName = my_quartz_jobs.xml   
  3.   
  4. org.quartz.plugin.jobInitializer.overWriteExistingJobs = true  
  5. org.quartz.plugin.jobInitializer.validating = false  
  6. org.quartz.plugin.jobInitializer.overWriteExistingJobs = false  
  7. org.quartz.plugin.jobInitializer.failOnFileNotFound = true  

在代碼 3.10中,我們添加了屬性 org.quartz.plugin.jobInitializer.fileName 并設置該屬性值為我們想要的文件名。這個文件名要對 classloader 可見,也就是說要在 classpath 下。

當 Quartz 啟動后讀取 quartz.properties 文件,然后初始化插件。它會傳遞上面配置的所有屬性給插件,這時候插件也就得到通知去搜尋不同的文件。

譯者后記:
想了又想,關于動詞的 “Schedule” 還是選擇“部署”,此前用的是“安排”,感覺不那么正式。當然英語中“部署”基本都用“Deploy”對應,平時與同事交流 Quartz 方面的技術都是說“往調度器上部署一個 Job”的,只要詞能達意就行。

對于 “register with the Scheduler”,有時候是用的“通過調度器來注冊”,有時候是“注冊到調度器上”,意思基本一致的。



4. 打包 Quartz 應用程序

讓我們最后簡單討論打包一個用到了 Quarts 框架的應用程序的流程,也以此來結束本章的內容。

·Quartz 第三方依賴包

從 1.5 版的發行包開始,你會看到一個 <QUARTZ_HOME>\lib 目錄,在這個目錄,你會發現幾個子目錄:

·<QUARTZ_HOME>\lib\core
·<QUARTZ_HOME>\lib\optional
·<QUARTZ_HOME>\lib\build


作為開發呢,你絕對是需求 Quartz JAR 包,也需要其他一些依賴包。需要哪些第三方包還依賴于你是運行在獨立環境中還是作為一個 J2EE 發行包的一部份。典型的,jakarta Commons 庫 (commons-loggin, commons-beanutils,還有其他的) 總是要用到。然而,當你是部署到一個應用服務器環境中,你需要確保不能把那些在應用服務器上已存在的包拷過去;如果你這樣做的,你可能回得到非常奇怪的結果。

表 3.1 列出了第三方包的信息,幫助你確定是否需要在應用中包含它們

表 3.1. Quartz  第三方包,必須/可選

名稱 必須/備注 網址
activation.jar 主要是 JavaMail 要用到 http://java.sun.com/products/javabeans/glasgow/jaf.html
commons-beanutils.jar http://jakarta.apache.org/commons/beanutils
commons-collections.jar http://jakarta.apache.org/commons/collections
commons-dbcp-1.1.jar 是,假如用到數據庫作為作業存儲 http://jakarta.apache.org/commons/dbcp
commons-digester.jar 假如你使用了某些插件,就需要它
commons-logging.jar http://jakarta.apache.org/commons/logging/
commons-pool-1.1.jar http://jakarta.apache.org/commons/pool/
javamail.jar 發送 e-mail 用 http://java.sun.com/products/javamail/
jdbc2_0-stdext.jar 是,假如用到數據庫作為作業存儲 http://java.sun.com/products/jdbc/
jta.jar 是,假如用到數據庫作為作業存儲 http://java.sun.com/products/jta/database
quartz.jar Quart 框架核心包
servlet.jar 假如使用了Servlet 容器,但容器中應該存在 http://java.sun.com/products/servlet/
log4j.jar 來吧,誰沒用過它呢? http://logging.apache.org/


·配置和屬性文件

你還必須在你的應用中包含 quartz.properties。假如你是應散裝(exploded format) 形式部署應用,你應該把 quartz.properties 文件放置在類加載器能夠加載的目錄中。(所謂的 “exploed” 形式指不打成一個 JAR、WAR、EAR 或者其他 Java 包,以獨立文件存在于文件系統中) 例如,如果你有一個 classes 目錄(比如一個 Web 應用的 WEB-INF/classes 目錄),就可以把 quartz.properties 文件放在那兒。假如你以 Java 打包形式部署,應該放屬性文件放在包的根下。在對待 quartz_job.xml 文件時也使用同樣的規則。