Hello Quartz (第三部分)
宣告式部署一個 Job
前面我們討論過,儘可能的用宣告式處理軟體配置,其次才才慮程式設計式。再來看程式碼 3.6,如果我們要在 Job 啟動之後改變它的執行時間和頻度,必須去修改原始碼重新編譯。這種方式只適用於小的例子程式,但是對於一個大且複雜的系統,這就成了一個問題了。因此,假如能以宣告式部署 Quart Job 時,並且也是需求允許的情況下,你應該每次都選擇這種方式。
·配置 quartz.properties 檔案
檔案 quartz.properties 定義了 Quartz 應用執行時行為,還包含了許多能控制 Quartz 運轉的屬性。本章只會講到它的基本配置;更多的高階設定將在以後討論。在現階段也不用太深入到每一項配置有效值的細節。
現在我們來看看最基礎的 quartz.properties 檔案,並討論其中一些設定。程式碼 3.7 是一個修剪版的 quartz.propertis 檔案。
注
Quartz 框架會為幾乎所有的這些屬性設定預設值。
程式碼 3.7. 基本的 Quartz Properties 檔案
- #===============================================================
- #Configure Main Scheduler Properties
- #===============================================================
- org.quartz.scheduler.instanceName = QuartzScheduler
- org.quartz.scheduler.instanceId = AUTO
- #===============================================================
- #Configure ThreadPool
- #===============================================================
- org.quartz.threadPool.threadCount = 5
- org.quartz.threadPool.threadPriority = 5
- org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
- #===============================================================
- #Configure JobStore
- #===============================================================
- org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
- #===============================================================
- #Configure Plugins
- #===============================================================
- org.quartz.plugin.jobInitializer.class =
- org.quartz.plugins.xml.JobInitializationPlugin
- org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
- org.quartz.plugin.jobInitializer.failOnFileNotFound = true
- org.quartz.plugin.jobInitializer.validating=false
#=============================================================== #Configure Main Scheduler Properties #=============================================================== org.quartz.scheduler.instanceName = QuartzScheduler org.quartz.scheduler.instanceId = AUTO #=============================================================== #Configure ThreadPool #=============================================================== org.quartz.threadPool.threadCount = 5 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool #=============================================================== #Configure JobStore #=============================================================== org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore #=============================================================== #Configure Plugins #=============================================================== org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin org.quartz.plugin.jobInitializer.overWriteExistingJobs = true org.quartz.plugin.jobInitializer.failOnFileNotFound = true 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
- <?xml version='1.0' encoding='utf-8'?>
- <quartz>
- <job>
- <job-detail>
- <name>ScanDirectory</name>
- <group>DEFAULT</group>
- <description>
- A job that scans a directory for files
- </description>
- <job-class>
- org.cavaness.quartzbook.chapter3.ScanDirectoryJob
- </job-class>
- <volatility>false</volatility>
- <durability>false</durability>
- <recover>false</recover>
- <job-data-map allows-transient-data="true">
- <entry>
- <key>SCAN_DIR</key>
- <value>c:\quartz-book\input</value>
- </entry>
- </job-data-map>
- </job-detail>
- <trigger>
- <simple>
- <name>scanTrigger</name>
- <group>DEFAULT</group>
- <job-name>ScanDirectory</job-name>
- <job-group>DEFAULT</job-group>
- <start-time>2005-06-10 6:10:00 PM</start-time>
- <!-- repeat indefinitely every 10 seconds -->
- <repeat-count>-1</repeat-count>
- <repeat-interval>10000</repeat-interval>
- </simple>
- </trigger>
- </job>
- </quartz>
<?xml version='1.0' encoding='utf-8'?> <quartz> <job> <job-detail> <name>ScanDirectory</name> <group>DEFAULT</group> <description> A job that scans a directory for files </description> <job-class> org.cavaness.quartzbook.chapter3.ScanDirectoryJob </job-class> <volatility>false</volatility> <durability>false</durability> <recover>false</recover> <job-data-map allows-transient-data="true"> <entry> <key>SCAN_DIR</key> <value>c:\quartz-book\input</value> </entry> </job-data-map> </job-detail> <trigger> <simple> <name>scanTrigger</name> <group>DEFAULT</group> <job-name>ScanDirectory</job-name> <job-group>DEFAULT</job-group> <start-time>2005-06-10 6:10:00 PM</start-time> <!-- repeat indefinitely every 10 seconds --> <repeat-count>-1</repeat-count> <repeat-interval>10000</repeat-interval> </simple> </trigger> </job> </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
- <?xml version='1.0' encoding='utf-8'?>
- <quartz>
- <job>
- <job-detail>
- <name>ScanDirectory1</name>
- <group>DEFAULT</group>
- <description>
- A job that scans a directory for files
- </description>
- <job-class>
- org.cavaness.quartzbook.chapter3.ScanDirectoryJob
- </job-class>
- <volatility>false</volatility>
- <durability>false</durability>
- <recover>false</recover>
- <job-data-map allows-transient-data="true">
- <entry>
- <key>SCAN_DIR</key>
- <value>c:\quartz-book\input1</value>
- </entry>
- </job-data-map>
- </job-detail>
- <trigger>
- <simple>
- <name>scanTrigger1</name>
- <group>DEFAULT</group>
- <job-name>ScanDirectory1</job-name>
- <job-group>DEFAULT</job-group>
- <start-time>2005-07-19 8:31:00 PM</start-time>
- <!-- repeat indefinitely every 10 seconds -->
- <repeat-count>-1</repeat-count>
- <repeat-interval>10000</repeat-interval>
- </simple>
- </trigger>
- </job>
- <job>
- <job-detail>
- <name>ScanDirectory2</name>
- <group>DEFAULT</group>
- <description>
- A job that scans a directory for files
- </description>
- <job-class>
- org.cavaness.quartzbook.chapter3.ScanDirectoryJob
- </job-class>
- <volatility>false</volatility>
- <durability>false</durability>
- <recover>false</recover>
- <job-data-map allows-transient-data="true">
- <entry>
- <key>SCAN_DIR</key>
- <value>c:\quartz-book\input2</value>
- </entry>
- </job-data-map>
- </job-detail>
- <trigger>
- <simple>
- <name>scanTrigger2</name>
- <group>DEFAULT</group>
- <job-name>ScanDirectory2</job-name>
- <job-group>DEFAULT</job-group>
- <start-time>2005-06-10 6:10:00 PM</start-time>
- <!-- repeat indefinitely every 15 seconds -->
- <repeat-count>-1</repeat-count>
- <repeat-interval>15000</repeat-interval>
- </simple>
- </trigger>
- </job>
- </quartz>
<?xml version='1.0' encoding='utf-8'?> <quartz> <job> <job-detail> <name>ScanDirectory1</name> <group>DEFAULT</group> <description> A job that scans a directory for files </description> <job-class> org.cavaness.quartzbook.chapter3.ScanDirectoryJob </job-class> <volatility>false</volatility> <durability>false</durability> <recover>false</recover> <job-data-map allows-transient-data="true"> <entry> <key>SCAN_DIR</key> <value>c:\quartz-book\input1</value> </entry> </job-data-map> </job-detail> <trigger> <simple> <name>scanTrigger1</name> <group>DEFAULT</group> <job-name>ScanDirectory1</job-name> <job-group>DEFAULT</job-group> <start-time>2005-07-19 8:31:00 PM</start-time> <!-- repeat indefinitely every 10 seconds --> <repeat-count>-1</repeat-count> <repeat-interval>10000</repeat-interval> </simple> </trigger> </job> <job> <job-detail> <name>ScanDirectory2</name> <group>DEFAULT</group> <description> A job that scans a directory for files </description> <job-class> org.cavaness.quartzbook.chapter3.ScanDirectoryJob </job-class> <volatility>false</volatility> <durability>false</durability> <recover>false</recover> <job-data-map allows-transient-data="true"> <entry> <key>SCAN_DIR</key> <value>c:\quartz-book\input2</value> </entry> </job-data-map> </job-detail> <trigger> <simple> <name>scanTrigger2</name> <group>DEFAULT</group> <job-name>ScanDirectory2</job-name> <job-group>DEFAULT</job-group> <start-time>2005-06-10 6:10:00 PM</start-time> <!-- repeat indefinitely every 15 seconds --> <repeat-count>-1</repeat-count> <repeat-interval>15000</repeat-interval> </simple> </trigger> </job> </quartz>
·為外掛修改 quartz.properties 配置
在本章前面,告訴過你的是,JobInitializationPlugin 找尋 quartz_jobs.xml 來獲得宣告的 Job 資訊。假如你想改變這個檔名,你需要修改 quartz.properties 來告訴外掛去載入那個檔案。例如,假如你想要 Quartz 從名為 my_quartz_jobs.xml 的 XML 檔案中載入 Job 資訊,你不得不為外掛指定這一檔名。程式碼 3.10 顯示了怎麼完成這個配置;我們現在是最後一次在這裡重複說明這一外掛部分。
程式碼 3.10. 為 JobInitializationPlugin 修改 quartz.properties
- org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin
- org.quartz.plugin.jobInitializer.fileName = my_quartz_jobs.xml
- org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
- org.quartz.plugin.jobInitializer.validating = false
- org.quartz.plugin.jobInitializer.overWriteExistingJobs = false
- org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin org.quartz.plugin.jobInitializer.fileName = my_quartz_jobs.xml org.quartz.plugin.jobInitializer.overWriteExistingJobs = true org.quartz.plugin.jobInitializer.validating = false org.quartz.plugin.jobInitializer.overWriteExistingJobs = false org.quartz.plugin.jobInitializer.failOnFileNotFound = true
在程式碼 3.10中,我們添加了屬性 org.quartz.plugin.jobInitializer.fileName 並設定該屬性值為我們想要的檔名。這個檔名要對 classloader 可見,也就是說要在 classpath 下。
當 Quartz 啟動後讀取 quartz.properties 檔案,然後初始化外掛。它會傳遞上面配置的所有屬性給外掛,這時候外掛也就得到通知去搜尋不同的檔案。