Quartz2.2.x官方文檔2.2.X—第三章 3.關於Jobs和Job Details

Quartz 教程


Table of Contents | ‹ Lesson 2 | Lesson 4 ›

課程3: 關於 Jobs 和 Job Details

正如你課程2看到的,Jobs十分容易實現,只需要實現接口中的‘execute’ 方法。你還需要了解更多關於jobs的性質,Job接口的execute(..)方法,關於JobDetails。

當你實現job類通過代碼知道怎麼實現不同類型的Job,Quartz需要配置你希望job擁有的不同屬性。這是通過JobDetail類完成的,在上一節簡要提到過。

JobDetail實例的構建使用JobBuilder類。你通常可以使用靜態導入所有方法,這樣可以使你的代碼擁有DSL的感覺。


import static org.quartz.JobBuilder.*;

現在讓我們討論一下關於Jobs的'本質'和在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' Job類定義如下:


  public class HelloJob implements Job {

    public HelloJob() {
    }

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

我們給這個scheduler一個JobDetail實例,它知道這個被執行job的類型,你只需簡單提供job類就可以構造JobDetail。每次調度執行這個job, 都會在調用它的execute(..)方法前,創建一個這個類的新實例。當執行完成後,對job類實例的引用將刪除,實例會被垃圾收集器回收。這種行爲使得Jobs必須有一個無參的構造函數(當使用默認JobFactory實現時). 另一方面在job類中定義數據字段是沒有意義的 - 因爲在job執行期間這些值不會被保存.

你可能會問“那我怎樣能賦予Job實體屬性/配置呢? 和 “我怎樣能在job執行期間追蹤它的狀態?” 這些問題的答案是同一個:關鍵在於JobDataMap, 它是JobDetail對象的一部分.

JobDataMap

JobDataMap能夠用來保存你希望在Job執行時能夠傳遞給Job的任意數量的數據。JobDataMap是Java Map接口的一種是實現, 並且有一些遍歷的方法,來存儲和檢索原始類型的數據。

下面是當定義/構建JobDetail時一些往JobDataMap中放入數據的片段,job添加至調度程序之前:


  // 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 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(在本教程的JobStore部分討論) 你應該在決定使用JobDataMap的地方小心謹慎,因爲它會將對象序列化,而這容易導致類的版本問題。顯然標準Java類型是十分安全的,但是對已經序列化的實體修改它們的定義,特別要注意不要破壞它們的兼容性。當然, 你可以只將原始類型放入JDBC-JobStore和JobDataMap,只有字符串被允許存放到map中,這樣就排除了任何序列化問題的可能。

如果你給job類添加set方法,這個方法的名稱相當於在JobDataMap中的key。(例如上個例子中的數據,setJobSays(String val)方法), 接着當job實例化後,Quartz’s默認的JobFactory實現將會自動調用這些set方法,這樣就避免了在執行方法中顯示的獲取map中的值.

Triggers也可以有與之關聯的JobDataMaps。在你有一個Job在調度程序儲存中,並且有多個觸發器定期/重複使用的情況下是十分有用的,每一個觸發器觸發時,你都可以給Job提供不同的數據輸入。

在Job執行期間通過JobExecutionContext獲得JobDataMap是十分方便的。它的是JobDatail的JobDataMap和Trigger中JobDataMap值的合併,後者中任何與前者中相同的值,都會把前者覆蓋。

這裏有一個例子,關於在Job執行期間從JobExecutionContext中獲得合併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向你的類注入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()方法的代碼更加清晰。雖然這個代碼更長,它實際上編寫代碼更少,如果你的IDE開發工具使用了set方法自動生成,那麼就不需要單獨再從JobDataMap中獲取數據。至於如何選擇在於你了。

Job “Instances”

許多用戶對於花費時間教你如何構建"job 實例"感到困惑。我們將會在下部分講解關於job狀態和併發的事情。

你可以創建一個單獨的job類,然偶胡在調度程序中創建需要JobDetails的實例 - 每個都有自己的屬性和JobDataMap - 並將它們全部添加到調度程序中.

例如,你可以創建一個實現Job接口的類"SalesReportJob"。這個job被用來根據輸入給它的參數(通過JobDataMap),來指定銷售報告根據銷售員來生成。他們可以創建多個job的定義(JobDetails), 例如“SalesReportForJoe” 和“SalesReportForMike” ,在JobDataMaps中將"joe"和"mike"分別輸入給對應的jobs。

當一個觸發觸發時,JobDetail(實例定義) 與加載有關,它所引用的job類是通過在調度程序中的JobFactory配置初始化的。默認的JobFactory會簡單的調用job class的newInstance()方法,然後嘗試在類中調用JobDataMap中與key名稱相匹配的set方法。你可能希望創建你自己的JobFactory讓你應用程序的Ioc或則DI容器生產/初始化job實例。

在“Quartz speak”, 我們將每一個存儲的JobDetail作爲"job definition"或者“JobDetail instance”, 我們將每一個調用的job稱爲"job實例"或者"job定義的實例"。通常如果我們只是使用"job",我們使用的是"job"這個詞,一般我們指"job"這個詞的意思是名稱定義或者JobDetail。當我們提到job接口的實現,我們通常使用"job class"。

Job 狀態和併發

現在, 需要注意job的狀態數據(又稱作JobDataMap)和併發. 有一些註解被添加到你的Job類中,這些註解會影響在這些方面Quartz的行爲。

@DisallowConcurrentExecution 是可以添加到Job類的註釋,告訴Quartz不要同時執行給定job定義的多個實例(指的是給定的做作業類) .
注意這裏的描述,小心的選擇使用。在前一節例子中,如果“SalesReportJob”上有這個註解,在給定時間內只有一個"SalesReportForJoe"實例被執行, 但是能夠同時執行多個"SalesReportForMike"實例。約束基於實例定義(JobDetail),不是工作類的實例。不管怎樣, 它(在Quartz定義中)都明確的指出註釋應加載類本身,因爲它通常會對類的編碼產生影響。

@PersistJobDataAfterExecution 是一個能夠在execute()方法完全執行後,告訴Quartz更新JobDetail的JobDataMap的存儲副本的註解。這樣同一個Job(JobDetail)下一次執行將會收到更新後的值而不是原始存儲的值。就像@DisallowConcurrentExecution 註解, 這適用於作業定義實例,而不是一個作業類實例,儘管決定讓job類攜帶屬性,因爲會對類的編碼產生影響。(例如在執行方法中,"statefulness"需要顯示指定"understood"。

如果你適用@PersistJobDataAfterExecution註解, 你也應該好好思考需要使用@DisallowConcurrentExecution 註解, 因爲爲了避免同一個Job的兩個實例同時執行時,數據可能會混亂的問題。

其他Jobs屬性

下面是能夠通過JobDetail對象定義一個job實例的其他屬性快速總結:

  • Durability - 如果job非持久化,一旦job與任何活躍觸發器失去關聯則自動從調度程序中刪除。換句話說,非持久jobs有一個 生命週期,trigger存在是必須的。
  • RequestsRecovery - 如果一個任務在"請求中恢復",它在調度程序硬關閉期間執行(例如在運行中崩潰,或者機器被關閉) , 接着當調度程序開始運行它會重新被執行。此時,JobExecutionContext.isRecovering()方法會返回true。

JobExecutionException

最後,我們需要告訴你一些Job.execute(..)方法的一些詳細信息。你可以從execute方法拋出唯一異常(包含運行時異常)是JobExecutionException。因此,你應該在使用execute方法的地方用'try-catch'包裹起來。你可以用些時間來看下JobExecutionException的文檔,你可以通過它知道如何處理這個異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章