定時任務的需求在衆多應用系統中廣泛存在,在Spring中,我們可以使用三種不同的定時機制,下面一一描述並加以比較
1. 基於Quartz的定時機制
下面詳細解釋這個類圖中涉及的關鍵類及其使用場景
1.1. SchedulerFactoryBean
這是Spring中基於Quartz的定時機制入口,只要Spring容器裝載了這個類,Quartz定時機制就會啓動,並加載定義在這個類中的所有trigger
Spring配置範例:
1.2. CronTriggerBean
實現了Trigger接口,基於Cron表達式的觸發器
這種觸發器的好處是表達式與linux下的crontab一致,能夠滿足非常複雜的定時需求,也容易配置
Spring配置範例:
1.3. SimpleTriggerBean
該類也實現了Trigger接口,基於配置的定時調度
這個觸發器的優點在於很容易配置一個簡單的定時調度策略
Spring配置範例:
1.4. JobDetailBean
JobDetail類的簡單擴展,能夠包裝一個繼承自QuartzJobBean的普通Bean,使之成爲定時運行的Job
缺點是包裝的Bean必須繼承自一個指定的類,通用性不強,對普通Job的侵入性過強,不推薦使用
1.5. MethodInvokingJobDetailFactoryBean
Spring提供的一個不錯的JobDetail包裝工具,能夠包裝任何bean,並執行類中指定的任何stati或非static的方法,避免強制要求bean去實現某接口或繼承某基礎類
Spring配置範例:
1.6. 關於TriggerListener和JobListener
Quartz中提供了類似WebWork的攔截器的功能,系統執行任務前或任務執行完畢後,都會檢查是否有對應的Listener需要被執行,這種AOP的思想爲我們帶來了靈活的業務需求實現方式。
例如現在有一個簡單的業務要求:任務執行前先判斷當前服務器是否爲task服務器,不是則不執行任務。對於這種業務需求,我們可以簡單的實現一個TriggerListener,並將其插入SchedulerFactoryBean的globalTriggerListeners中,這樣所有的job在執行前後都會調用TriggerListener中對應的方法。
代碼範例:
- public class MyTaskTriggerListener implements TriggerListener {
- protected static final Log logger = LogFactory.getLog(MyTaskTriggerListener.class);
- /**
- * 需要運行task任務的機器列表
- */
- private String taskServers;
- public String getName() {
- return "MyTaskTriggerListener";
- }
- public void triggerComplete(Trigger arg0, JobExecutionContext arg1, int arg2) {
- }
- public void triggerFired(Trigger arg0, JobExecutionContext arg1) {
- }
- public void triggerMisfired(Trigger arg0) {
- }
- /**
- * 判斷當前服務器是否爲task服務器,來決定是否執行task
- * @return
- */
- public boolean vetoJobExecution(Trigger arg0, JobExecutionContext arg1) {
- String serverName;
- try {
- serverName = InetAddress.getLocalHost().getHostName();//獲取主機名
- } catch (UnknownHostException e) {
- e.printStackTrace();
- return true;
- }
- if (taskServers.indexOf(serverName) > -1) {
- if (logger.isInfoEnabled()) {
- logger.info("this is a task server, job will be executed");
- }
- return false;
- } else {
- if (logger.isInfoEnabled()) {
- logger.info("this is not a task server, job will be vetoed");
- }
- return true;
- }
- }
- public String getTaskServers() {
- return taskServers;
- }
- public void setTaskServers(String taskServers) {
- this.taskServers = taskServers;
- }
- }
2. 基於Timer的定時機制
JDK提供了基礎的定時類:Timer,在這個類的基礎上,Spring提供了一套簡單的定時機制
下面詳細解釋這個類圖中涉及的關鍵類及其使用場景
2.1. TimerFactoryBean
這個類非常類似Quartz中的SchedulerFactoryBean,是基於Timer的定時機制的入口,Spring容器裝載此類後會自動開始定時器
Spring配置範例:
2.2. ScheduledTimerTask
類似於Quartz中的Trigger的SimpleTriggerBean實現,任務是在設定的時間觸發並執行配置的任務,特點是配置簡單、明瞭,使用於簡單的任務觸發邏輯
Spring配置範例:
2.3. TimerTask抽象類
普通task實現必須要繼承的父類,主要包含一個run()的方法,類似Quartz中的QuartzJobBean,對應用侵入性較強,也不推薦使用
2.4. MethodInvokingTimerTaskFactoryBean
類似Quartz中的MethodInvokingJobDetailFactoryBean,用於封裝任何bean,並可以執行bean中的任意方法,不再複述
3. 基於Executor的定時機制
這種定時機制與上面兩種定時機制沒有太大區別,特別是在配置和實現功能上,不同的是它的核心是基於ScheduledExecutorService(ScheduledThreadPoolExecutor是默認實現),一種JDK5.0中提供的基於線程的併發機制,
關於JDK5中的線程池的概念及其一些深入分析,請參考老唐的博客:http://blog.csdn.net/sfdev/archive/2008/12/30/3648457.aspx 這裏不再複述
4. 三種定時機制的比較和案例分析
看完了這三種定時機制,各有各的優劣,不同場景下我們應該靈活選擇不同的定時機制。總的來說,如果我們需要簡單的定時器,我們可以選用基於timer的定時器,如果定時規則較爲複雜,我們可以選用基於Quartz的定時器,如果我們要用到線程池來處理異步任務,我們可以選用基於Executor的定時機制,雖然只是任務實現中用到線程池,畢竟也是一脈相承的,當然也可以用Quartz的定時器+基於Executor的任務線程池,完全沒有任何衝突的。
說這麼多,還是比較抽象,不如我們來分析一下老唐的Notify系統來加深對Spring定時機制的瞭解(詳細設計參考最近一期的程序員雜誌)。
在老唐的Notify系統中,完全使用了基於JDK5.0中的Executor的定時機制,即由一個ScheduledExecutorFactoryBean觸發系統的每隔2分鐘運行一個單線程的任務,在這個任務中,執行完各種機制檢查和配置策略後,將要執行的Notify任務放入一個已配置好的線程池,並由線程池指定線程來完成Notify的任務。
在最近一期的項目中,我們將task移植到了apps,Notify系統也同時被移植過來了,爲了統一所有的task,我們將以前task中基於timer、Quartz和Executor的各種任務統一改爲基於Quartz的調度。在這個過程中,Notify系統的基於Executor的定時機制也被改爲基於Quartz的定時機制,過程非常順利。基於這次移植項目,可以說這三種定時機制是非常容易互換的,並且通用性比較強,只需要簡單的配置即可。