Job與JobDetail介紹

2022-09-16 09:42 更新

正如你在第2課中看到的,Jobs很容易實(shí)現(xiàn),在接口中只有一個(gè)“execute”方法。本節(jié)主要關(guān)注:Job的特點(diǎn)、Job接口的execute方法以及JobDetail。

你定義了一個(gè)實(shí)現(xiàn)Job接口的類,這個(gè)類僅僅表明該job需要完成什么類型的任務(wù),除此之外,Quartz還需要知道該Job實(shí)例所包含的屬性;這將由JobDetail類來完成。

JobDetail實(shí)例是通過JobBuilder類創(chuàng)建的,導(dǎo)入該類下的所有靜態(tài)方法,會讓你編碼時(shí)有DSL的感覺:

    import static org.quartz.JobBuilder.*;

讓我們先看看Job的特征(nature)以及Job實(shí)例的生命期。不妨先回頭看看第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);

現(xiàn)在考慮這樣定義的作業(yè)類“HelloJob”:

  public class HelloJob implements Job {

    public HelloJob() {
    }

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

可以看到,我們傳給scheduler一個(gè)JobDetail實(shí)例,因?yàn)槲覀冊趧?chuàng)建JobDetail時(shí),將要執(zhí)行的job的類名傳給了JobDetail,所以scheduler就知道了要執(zhí)行何種類型的job;每次當(dāng)scheduler執(zhí)行job時(shí),在調(diào)用其execute(…)方法之前會創(chuàng)建該類的一個(gè)新的實(shí)例;執(zhí)行完畢,對該實(shí)例的引用就被丟棄了,實(shí)例會被垃圾回收;這種執(zhí)行策略帶來的一個(gè)后果是,job必須有一個(gè)無參的構(gòu)造函數(shù)(當(dāng)使用默認(rèn)的JobFactory時(shí));另一個(gè)后果是,在job類中,不應(yīng)該定義有狀態(tài)的數(shù)據(jù)屬性,因?yàn)樵趈ob的多次執(zhí)行中,這些屬性的值不會保留。

那么如何給job實(shí)例增加屬性或配置呢?如何在job的多次執(zhí)行中,跟蹤job的狀態(tài)呢?答案就是:JobDataMap,JobDetail對象的一部分。

JobDataMap

JobDataMap中可以包含不限量的(序列化的)數(shù)據(jù)對象,在job實(shí)例執(zhí)行的時(shí)候,可以使用其中的數(shù)據(jù);JobDataMap是Java Map接口的一個(gè)實(shí)現(xiàn),額外增加了一些便于存取基本類型的數(shù)據(jù)的方法。

將job加入到scheduler之前,在構(gòu)建JobDetail時(shí),可以將數(shù)據(jù)放入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的執(zhí)行過程中,可以從JobDataMap中取出數(shù)據(jù),如下示例:

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);
    }
  }

如果你使用的是持久化的存儲機(jī)制(本教程的JobStore部分會講到),在決定JobDataMap中存放什么數(shù)據(jù)的時(shí)候需要小心,因?yàn)镴obDataMap中存儲的對象都會被序列化,因此很可能會導(dǎo)致類的版本不一致的問題;Java的標(biāo)準(zhǔn)類型都很安全,如果你已經(jīng)有了一個(gè)類的序列化后的實(shí)例,某個(gè)時(shí)候,別人修改了該類的定義,此時(shí)你需要確保對類的修改沒有破壞兼容性;更多細(xì)節(jié),參考現(xiàn)實(shí)中的序列化問題。另外,你也可以配置JDBC-JobStore和JobDataMap,使得map中僅允許存儲基本類型和String類型的數(shù)據(jù),這樣可以避免后續(xù)的序列化問題。

如果你在job類中,為JobDataMap中存儲的數(shù)據(jù)的key增加set方法(如在上面示例中,增加setJobSays(String val)方法),那么Quartz的默認(rèn)JobFactory實(shí)現(xiàn)在job被實(shí)例化的時(shí)候會自動調(diào)用這些set方法,這樣你就不需要在execute()方法中顯式地從map中取數(shù)據(jù)了。

在Job執(zhí)行時(shí),JobExecutionContext中的JobDataMap為我們提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的數(shù)據(jù),則后者會覆蓋前者的值。

下面的示例,在job執(zhí)行時(shí),從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實(shí)現(xiàn)數(shù)據(jù)的自動“注入”,則示例代碼為:

  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) {
      this.myFloatValue = myFloatValue;
    }

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

  }

你也許發(fā)現(xiàn),整體上看代碼更多了,但是execute()方法中的代碼更簡潔了。而且,雖然代碼更多了,但如果你的IDE可以自動生成setter方法,你就不需要寫代碼調(diào)用相應(yīng)的方法從JobDataMap中獲取數(shù)據(jù)了,所以你實(shí)際需要編寫的代碼更少了。當(dāng)前,如何選擇,由你決定。

Job實(shí)例

很多用戶對于Job實(shí)例到底由什么構(gòu)成感到很迷惑。我們在這里解釋一下,并在接下來的小節(jié)介紹job狀態(tài)和并發(fā)。

你可以只創(chuàng)建一個(gè)job類,然后創(chuàng)建多個(gè)與該job關(guān)聯(lián)的JobDetail實(shí)例,每一個(gè)實(shí)例都有自己的屬性集和JobDataMap,最后,將所有的實(shí)例都加到scheduler中。

比如,你創(chuàng)建了一個(gè)實(shí)現(xiàn)Job接口的類“SalesReportJob”。該job需要一個(gè)參數(shù)(通過JobdataMap傳入),表示負(fù)責(zé)該銷售報(bào)告的銷售員的名字。因此,你可以創(chuàng)建該job的多個(gè)實(shí)例(JobDetail),比如“SalesReportForJoe”、“SalesReportForMike”,將“joe”和“mike”作為JobDataMap的數(shù)據(jù)傳給對應(yīng)的job實(shí)例。

當(dāng)一個(gè)trigger被觸發(fā)時(shí),與之關(guān)聯(lián)的JobDetail實(shí)例會被加載,JobDetail引用的job類通過配置在Scheduler上的JobFactory進(jìn)行初始化。默認(rèn)的JobFactory實(shí)現(xiàn),僅僅是調(diào)用job類的newInstance()方法,然后嘗試調(diào)用JobDataMap中的key的setter方法。你也可以創(chuàng)建自己的JobFactory實(shí)現(xiàn),比如讓你的IOC或DI容器可以創(chuàng)建/初始化job實(shí)例。

在Quartz的描述語言中,我們將保存后的JobDetail稱為“job定義”或者“JobDetail實(shí)例”,將一個(gè)正在執(zhí)行的job稱為“job實(shí)例”或者“job定義的實(shí)例”。當(dāng)我們使用“job”時(shí),一般指代的是job定義,或者JobDetail;當(dāng)我們提到實(shí)現(xiàn)Job接口的類時(shí),通常使用“job類”。

Job狀態(tài)與并發(fā)

關(guān)于job的狀態(tài)數(shù)據(jù)(即JobDataMap)和并發(fā)性,還有一些地方需要注意。在job類上可以加入一些注解,這些注解會影響job的狀態(tài)和并發(fā)性。

@DisallowConcurrentExecution:將該注解加到j(luò)ob類上,告訴Quartz不要并發(fā)地執(zhí)行同一個(gè)job定義(這里指特定的job類)的多個(gè)實(shí)例。請注意這里的用詞。拿前一小節(jié)的例子來說,如果“SalesReportJob”類上有該注解,則同一時(shí)刻僅允許執(zhí)行一個(gè)“SalesReportForJoe”實(shí)例,但可以并發(fā)地執(zhí)行“SalesReportForMike”類的一個(gè)實(shí)例。所以該限制是針對JobDetail的,而不是job類的。但是我們認(rèn)為(在設(shè)計(jì)Quartz的時(shí)候)應(yīng)該將該注解放在job類上,因?yàn)閖ob類的改變經(jīng)常會導(dǎo)致其行為發(fā)生變化。

@PersistJobDataAfterExecution:將該注解加在job類上,告訴Quartz在成功執(zhí)行了job類的execute方法后(沒有發(fā)生任何異常),更新JobDetail中JobDataMap的數(shù)據(jù),使得該job(即JobDetail)在下一次執(zhí)行的時(shí)候,JobDataMap中是更新后的數(shù)據(jù),而不是更新前的舊數(shù)據(jù)。和 @DisallowConcurrentExecution注解一樣,盡管注解是加在job類上的,但其限制作用是針對job實(shí)例的,而不是job類的。由job類來承載注解,是因?yàn)閖ob類的內(nèi)容經(jīng)常會影響其行為狀態(tài)(比如,job類的execute方法需要顯式地“理解”其”狀態(tài)“)。

如果你使用了@PersistJobDataAfterExecution注解,我們強(qiáng)烈建議你同時(shí)使用@DisallowConcurrentExecution注解,因?yàn)楫?dāng)同一個(gè)job(JobDetail)的兩個(gè)實(shí)例被并發(fā)執(zhí)行時(shí),由于競爭,JobDataMap中存儲的數(shù)據(jù)很可能是不確定的。

Job的其它特性

通過JobDetail對象,可以給job實(shí)例配置的其它屬性有:

  • Durability:如果一個(gè)job是非持久的,當(dāng)沒有活躍的trigger與之關(guān)聯(lián)的時(shí)候,會被自動地從scheduler中刪除。也就是說,非持久的job的生命期是由trigger的存在與否決定的;
  • RequestsRecovery:如果一個(gè)job是可恢復(fù)的,并且在其執(zhí)行的時(shí)候,scheduler發(fā)生硬關(guān)閉(hard shutdown)(比如運(yùn)行的進(jìn)程崩潰了,或者關(guān)機(jī)了),則當(dāng)scheduler重新啟動的時(shí)候,該job會被重新執(zhí)行。此時(shí),該job的JobExecutionContext.isRecovering() 返回true。

JobExecutionException

最后,是關(guān)于Job.execute(..)方法的一些額外細(xì)節(jié)。execute方法中僅允許拋出一種類型的異常(包括RuntimeExceptions),即JobExecutionException。因此,你應(yīng)該將execute方法中的所有內(nèi)容都放到一個(gè)”try-catch”塊中。你也應(yīng)該花點(diǎn)時(shí)間看看JobExecutionException的文檔,因?yàn)槟愕膉ob可以使用該異常告訴scheduler,你希望如何來處理發(fā)生的異常。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號