1. 程式人生 > >[譯]Java定時任務排程-Quartz文件(三)進一步講講Job和Job Detail

[譯]Java定時任務排程-Quartz文件(三)進一步講講Job和Job Detail

正如上篇文章所說的,Job很容易實現,只需要介面中唯一的execute方法。除此之外,你還需要稍微瞭解下Job、execute、Job interface和JobDetail的一些東西。

當你寫的Job類執行特定任務時,Quartz需要知道這個類應該具有的各種屬性。這就是前面所提到的JobDetail類完成的。

JobDetail例項是由JobBuilder建立的,可以這樣引入:

import static org.quartz.JobBuilder.*;

讓我們花一點時間來看看Job的原理和Quartz中Job的生命週期。首先看一下在第一篇文章中提及的一塊程式碼片段:

// 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的定義如下:

public class HelloJob implements Job {

    public HelloJob() {
    }

    public void execute(JobExecutionContext context)
      throws
JobExecutionException { System.err.println("Hello! HelloJob is executing."); } }

注意傳給scheduler的是一個JobDetail的例項,僅僅通過在建立JobDetail是指定了Job class,它就知道知道所執行的任務型別。每當scheduler執行任務的時候,總是建立一個新的類例項,在執行其execute方法。執行完畢後,會釋放對該例項的引用,稍後將會被垃圾回收。這種做法要求Job類必須有一個無參的構造方法,還有就是靜態域是無效的,因為無法在多個執行例項中共享。

你可能想問,如何對Job例項配置屬性呢?或者換句話說,如何跟蹤多個例項的執行呢?答案是使用JobDataMap,它屬於JobDetail物件的一部分。

JobDataMap

JobDataMap是用來在執行過程中儲存必要的資料物件。JobDataMap實現了Java Map介面,
還定義了一些存取基本資料型別的方法。
下面的例子介紹了在建立JobDetail時向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();

下面的例子介紹在執行過程中如何從JobDataMap中取資料:

public class DumbJob implements Job {

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();

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

      String jobSays = dataMap.getString("jobSays");
      float myFloatValue = dataMap.getFloat("myFloatValue");

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

如果你使用持久化JobStore,必須注意放入JobDataMap中的資料型別,因為這個物件會被序列化,可能會有版本問題。顯而易見,java的基本型別是沒問題的,但是如果是使用者定義的型別發生了變化,而存在之前序列化的物件,就必須要考慮相容性問題了。當然,也可以限定JDBC-JobStore和JobDataMap只能儲存基本型別和String,這樣就排除了序列化帶來的潛在問題。

如果你在Job類中定義了與JobDataMap中key對應的setter方法,Quartz的就會在job例項化是呼叫這些setter方法,所以應避免定義類似的方法,以保證map中資料的準確性。

Triigers也有對應的JobDataMap。當定義一個任務,關聯到多分資料、多個觸發器,定期、重複執行時,JobDataMap會很有用。
JobDataMap定義在Job執行過程中的JobExecutionContext中,由JobDetail中的JobDataMap和Trigger中的JobDataMap合併而成,如果有重名的變數,前面的將會被後面的所覆蓋。
下面是從合併後的JobDataMap中取值的一個例子:


public class DumbJob implements Job {

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();

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

      String jobSays = dataMap.getString("jobSays");
      float myFloatValue = dataMap.getFloat("myFloatValue");
      ArrayList state = (ArrayList)dataMap.get("myStateData");
      state.add(new Date());

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

如果你想使用JobFactory將data map中的變數“注入”到你的類中,可以這樣做:


  public class DumbJob implements Job {


    String jobSays;
    float myFloatValue;
    ArrayList state;

    public DumbJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      JobKey key = context.getJobDetail().getKey();

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

      state.add(new Date());

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

    public void setJobSays(String jobSays) {
      this.jobSays = jobSays;
    }

    public void setMyFloatValue(float myFloatValue) {
      myFloatValue = myFloatValue;
    }

    public void setState(ArrayList state) {
      state = state;
    }

  }

你應該能注意到,下面的程式碼總的來說更長,但是execute中的程式碼卻變短了。有人會說雖然總程式碼量長,但是setter方法都可以用IDE自動生成,所以“手工”的程式碼實際是更短的,而且不必手動的從JobDataMap中取資料。怎麼做,你決定!

Job “Instances”

很多使用者花很長時間糾結於是誰建立了job例項。這裡我們將就任務的狀態和併發等問題,弄個明白。
你可以定義一個job類,然後建立多個JobDetail例項,每個例項都有自己的屬性和JobDataMap,然後把它們關聯到一個scheduler。
例如,你可以建立一個實現了Job interface的類“SalesReportJob”,這個job需要根據傳進來的銷售人員的姓名來確定銷售報告。接著需要定義多個JobDetail,例如“SalesReportForJoe” and “SalesReportForMike”,並將“joe” and “mike”存入個子的JobDataMap中。

當觸發器啟動是,與之關聯的JobDetail將會被載入,對應的job class會通過scheduler中配置的JobFactory例項化。預設的JobFactory會呼叫job class的newInstance方法,然後根據JobDataMap中的key值呼叫對應的setter方法進行變數賦值。你可能想建立自己的JobFactory實現來完成注入自己的IoC(控制反轉)或者DI(依賴注入)。
在“Quartz speak”中,我們將儲存的JobDetail看做“job definition”或者“JobDetail 例項”。通常來說,提到“job”,我們指的是一個具體的實現,即JobDetail;提到實現job interface的類,通常指的是“job class”。

Job State and Concurrency

接下來再看看任務的狀態資料和併發問題。以下是Job類中可以使用的註解,這些註解會影響Quartz的行為。

@DisallowConcurrentExecution 新增到Job class,Quartz就不會併發的執行多個任務例項。
注意這裡的用詞。在前面的例子中,如果“SalesReportJob”添加了這個註解,那麼同時只會有一個“SalesReportForJoe”例項執行,但這並不影響併發的執行“SalesReportForJoe”任務例項。這個約束是在JobDetail維度的,不是在Job class維度。

@PersistJobDataAfterExecution 添加了這個註解的job class,在執行完JobDetail後,會將其JobDataMap備份,以便於下次執行是使用,而不是使用預設的初始化資料。和第一個一樣,這個約束也是在JobDetail維度,而不是Job class維度。

如果你使用@PersistJobDataAfterExecution註解,你需要慎重考慮是否要使用@DisallowConcurrentExecution註解,以避免當一個JobDetail併發執行時資料是否會相互影響。

Other Attributes Of Jobs

下面是其他可以通過JobDetail物件定義給job例項的屬性歸類:

  • Durability - 如果job是非持久的,與其相關的所有trigger觸發執行完成之後將會自動刪除,換計劃說,所有job都是以與其相關的trigger結束而結束的。
  • RequestsRecovery - 如果job是“請求恢復”的,如果任務執行過程中schduler強制關閉了,那麼當scheduler重新啟動時,它將再次執行。在這種情況下JobExecutionContext.isRecovering()將返回TRUE;

JobExecutionException

最後,我們需要講下Job.execute(..)的細節。在這個方法內部唯一可以排除的異常是JobExecutionException。鑑於此,可能會丟擲的異常都應該使用tru/catch捕獲處理。你也因該進一步看下JobExecutionException相關文件,以便更好的處理。