1. 程式人生 > >Quartz教程 第3課 Job和JobDetail詳解

Quartz教程 第3課 Job和JobDetail詳解

3課 Job和JobDetail詳解

正如你二課中看到的,Job介面非常容易實現,只有一個execute方法。我們需要再學習一些知識去理解job的本質,Job介面的execute方法以及JobDetail介面。

當你實現Job介面類,Quartz需要你提供job例項的各種引數,Job介面實現類中的程式碼才知道如何去完成指定型別Job的實際工作。這個過程是通過JobDetail類來完成的,該類會在一個章節簡單的提到過。

JobDetail的例項是呼叫JobBuilder類建立的。通常你可以使用靜態匯入方式獲得該類所有方法的呼叫,這樣可以在你的程式碼體驗DSL的感覺。

importstatic org.quartz.JobBuilder.*;

讓我們花一些時間來討論一下QuartzJob以及Job例項的生命週期。首先,我們來回顧一下第1課中看到的程式碼片段:

// define the job and tie it to our HelloJob class

  JobDetail job = newJob(HelloJob.class)

      .withIdentity("myJob","group1")// name "myJob", group "group1"

      .build();

// Trigger the job to run now, and then every 40 seconds

  Trigger trigger = newTrigger()

      .withIdentity("myTrigger","group1")

      .startNow()

      .withSchedule(simpleSchedule()

          .withIntervalInSeconds(40)

          .repeatForever())            

      .build();

// Tell quartz to schedule the job using our trigger

  sched.scheduleJob(job, trigger);

現在考慮下面定義的HelloJob”類:

publicclassHelloJobimplementsJob {

publicHelloJob() {

    }

public voidexecute(JobExecutionContext context)

throwsJobExecutionException

    {

      System.err.println("Hello!  HelloJob is executing.");

    }

  }

請注意我們給scheduler提供了一個JobDetail例項,我們構建JobDetail物件時僅提供了job的class物件,排程器就知道它要執行的job型別。每次排程器執行job時,它在呼叫excecute方法前會建立一個新的job例項。當執行完成job例項的引用就會被丟棄,這個例項就會垃圾回收機制回收。這種呼叫過程導致的其中一個結果是job物件必須要有一個無引數構造器(使用預設的JobFacotry實現時),另外一個結果job實現類不能定義狀態資料欄位,因為這些狀態資料欄位的值在呼叫job任務時不會被保留。

你現在可能想問“我要怎樣才能為Job例項提供配置引數?在執行任務時我要如何跟蹤job物件的狀態?”這兩個問題的答案都一樣:使用JobDataMap,它是JobDetail物件的一部分。

3.1JobDataMap

JobDataMap可以用來持有任何可序列化的資料物件,當job例項物件被執行時這些引數物件會傳遞給它。JobDataMap實現Map介面,並且添加了一些非常方便的方法用來存取基本資料型別。

下面的程式碼片斷演示了在定義/構建JobDetail物件時,job物件新增到排程器之前,如何將資料存放至JobDataMap中:

// define the job and tie it to our DumbJob class

JobDetail job = newJob(DumbJob.class)

      .withIdentity("myJob","group1")// name "myJob", group "group1"

      .usingJobData("jobSays","Hello World!")

      .usingJobData("myFloatValue", 3.141f)

      .build();

下面的例子顯示了在job執行過程中如何從JobDataMap取資料:

public classDumbJobimplementsJob {

publicDumbJob() { }

public voidexecute(JobExecutionContext context)throwsJobExecutionException{

      JobKey key = context.getJobDetail().getKey();

      JobDataMap dataMap = context.getJobDetail().getJobDataMap();

      String jobSays = dataMap.getString("jobSays");

floatmyFloatValue = dataMap.getFloat("myFloatValue");

      System.err.println("Instance " + key +" of DumbJob says: " + jobSays +", and val is: " + myFloatValue);

    }

}

如果你使用持久化的JobStore(將會在教程JobStore部分討論),你應該要多考慮放在JobDataMap中的資料物件,因為此時的物件會被序列化,因此這更容易出現版本問題。顯然標準Java型別是很安全,但是非標準Java型別,任何時候有人變更你序例化例項的類的定義,都要注意不要破壞相容性。你可以選擇將JDBC-JobStore和JobDataMap設計成只有基本資料型別和String型別才允許儲存的map物件,從而從根本上消除序列化問題

如果你在job類中新增setter方法對應JobDataMap的鍵值(例如setJobSays(String val)方法對應上面例子裡的jobSays資料),Quartz框架預設的JobFactory實現類在初始化job例項物件時會自動地呼叫這些setter方法,從而防止在呼叫執行方法時需要從map物件取指定的屬性值。

觸發器也可以關聯JobDataMap物件,當儲存在排程器中的job物件需要定期/重複執行,被多個觸發器共用時,這種場景下使用JobDataMap將非常方便,然而每個獨立的觸發器,你都可以為job物件提供不同的輸入引數。

在進行任務排程時JobDataMap儲存在JobExecutionContext中非常方便獲取。它合併JobDetail和Trigger裡的JobDataMap資料物件,後面的會把前面相同鍵值的值覆蓋。

接下來的例子演示了任務執行過程中從JobExecutionContext取合併的JobDataMap資料:

public classDumbJobimplementsJob {

publicDumbJob() {}

public voidexecute(JobExecutionContext context)throwsJobExecutionException{

      JobKey key = context.getJobDetail().getKey();

      JobDataMap dataMap = context.getMergedJobDataMap();  // Note the difference from the previous example

      String jobSays = dataMap.getString("jobSays");

floatmyFloatValue = dataMap.getFloat("myFloatValue");

      ArrayList state = (ArrayList)dataMap.get("myStateData");

      state.add(newDate());

      System.err.println("Instance " + key +" of DumbJob says: " + jobSays +", and val is: " + myFloatValue);

    }

  }

或者如果你想在類中依賴JobFactory注入map資料,它看起來可能是這樣的:

public classDumbJobimplementsJob {

    String jobSays;

floatmyFloatValue;

    ArrayList state;

publicDumbJob() {}

public voidexecute(JobExecutionContext context)throwsJobExecutionException {

      JobKey key = context.getJobDetail().getKey();

      JobDataMap dataMap = context.getMergedJobDataMap();  // Note the difference from the previous example

      state.add(newDate());

      System.err.println("Instance " + key +" of DumbJob says: " + jobSays +", and val is: " + myFloatValue);

    }

public voidsetJobSays(String jobSays) {

this.jobSays = jobSays;

    }

public voidsetMyFloatValue(floatmyFloatValue) {

      myFloatValue = myFloatValue;

    }

public voidsetState(ArrayList state) {

      state = state;

    }

}

你可能會注意到類的整體程式碼比較長,但execute方法很簡潔。有人會認為雖然程式碼比較長,如果程式設計師的IDE自動生成setter方法的話,那麼實際只需要編寫更少的程式碼,而不必手工編寫那些單獨的呼叫方法從JobDataMap中取值。你可以自主選擇編寫程式碼的方式。

3.2Job例項

許多使用者對Job例項物件確切的結構是什麼疑惑了很長時間,我們將嘗試在這為大家解答,並且在下一個板塊講述Job狀態和併發機制。

你可以建立一個單獨的Job實現類,建立多個不同的JobDetail例項,將不同Job例項定義儲存在排程器中,每個JobDetail例項都有各自的引數和JobDataMap,並且把這些JobDetail新增到排程器中。

例如:你建立一個Job介面的實現類,類名為“SalesReportJob”,Job類可以預先傳入一些假想的引數(通過JobDataMap)來指定銷售報表中業務員的名字。接下來建立多個Job的定義(即JobDetail),如“SalesReportForJoe”和 “SalesReportForMike”通過“Joe”和“Mike”指定到相應的JobDataMap中作為引數輸入到各自的Job物件中。

trigger被觸發時,相關的JobDetail例項會被載入,通過在排程器中配置的JobFactory會將關聯的Job類例項化預設的JobFactory只是簡單的呼叫Job類newInstance方法,然後嘗試呼叫匹配JobDataMap鍵值的setter方法。你可以開建立自己的JobFactory實現類,以完成一些諸如通過應用IOC或DI機制完成Job例項的建立和初始化的事情

Quartz框架的話來說,我們將每個儲存的JobDetail稱為Job定義或JobDetail例項,將每個執行的作業任務(Job)稱為Job例項或Job定義例項。通常我們只用“job”單詞來對應命名的Job定義或是JobDetail。當我們指Job介面的實現類時,一般使用“job class”術語。

3.3Job狀態和併發機制

現在,需要關注一下Job狀態資料(也叫JobDataMap和併發機制。有一對加在Job類上面的註解,可以影響Quartz框架的這些方面的行為。

@DisallowConcurrentExecution註解新增到Job類中,會告知Quartz不要併發執行相同Job定義建立的多個例項物件。注意這裡的措辭,要慎重地選擇。引用上一章節的例子,如果SalesReportJob新增這個註解,在給定的時間段內只能執行一個 SalesReportJobForJoe例項物件,但是可以併發執行一個SalesReportJobForMike例項。這個限制是基於例項定義(JobDetail),而不是基於Job類例項然而,在Quartz設計階段決定在該類中攜帶註解,因為該註解會影響JobDetail類的編碼。

@PersistJobDataAfterExecution註解新增到Job類中,會告知Quartz成功執行完execute方法後(沒有丟擲異常)更新JobDetail的JobDataMap中儲存的資料。例如同一個JobDetail下一次執行時將接收更新的值而不是初始值。跟@DisallowConcurrentExecution註解類似,@PersistJobDataAfterExecution註解適用於Job定義例項,而不是Job類例項。只是該註解是附著在Job類的成員變數中,因為它不會影響整個類的編碼(例如statefulness只需要在execute方法程式碼內正確使用即可)。

如果你使用@PersistJobDataAfterExecution註解,強烈建議你也應該考慮使用 @DisallowConcurrentExecution註解,避免當兩個相同JobDetail例項併發執行時可能由於最後儲存狀態資料不一致導致執行混亂。

3.4 其它的Job屬性

這裡總結了可以通過JobDetail物件Job例項定義的其它屬性

Durability如果一個Job是非持續的,一旦沒有任何活躍的觸發器關聯這個Job例項時,這個例項會自動地從排程器中移除。換句話說,非持續job的生命週期受限於觸發器的存在性。

RequestsRecovery如果一個Job設定了請求恢復引數,並且在排程器強制關閉過程中恰好在執行(強制關閉的情況例如:執行的執行緒崩潰,或者伺服器宕機),當排程器重啟時,它會重新被執行。這種情況下,JobExecutionContextisRecovering方法會返回true

3.5JobExecutionException

最後,我們需要告知你Job.execute方法的一些細節。允許你從execute方法丟擲的唯一一種異常型別是JobExecutionException(執行時異常除外,可以正常丟擲),由於這個限制,你應該在execute方法內的try-catch程式碼塊中包裝好要處理的異常。你也可以花些時間查閱一下JobExecutionException的文件,便於你在開發Job類中需要捕獲處理異常時,為排程器提供各種資訊。