(一)、Job
??Job可以理解為就是一個工作任務,代碼中就是一個實現了org.quartz.Job或org.quartz.StatefulJob接口的java類。當Scheduler決定運行Job時,execute()方法就會被執行。
??具體可以干啥:
???1、每天定時發送系統郵件
???2、在指定的時刻發送一條短信給用戶
???3、執行完A任務后希望B任務在10秒后執行
???4、。。。
??總結就是任何java能做的任務都可以成為一個job。
??注意:在 @Quartz集成-項目使用中已經講到,org.quartz.Job接口是一個無狀態的任務接口,實現此接口的任務每當觸發器觸發時都會準時執行,調度器并不關心此任務是否有其他實例正在運行,如果任務存在阻塞,可能會導致大量的任務實例并行執行。如果業務中存在不允許并行執行的任務,此時就用到有狀態的org.quartz.StatefulJob接口,此接口的任務實現類不能并行執行,必須等上一次觸發器觸發執行完成后 才可以進行下一次觸發執行。
??那么Job和StatefulJob在代碼上具體有什么區別呢?
??它們沒有區別,只是名字不同而已,StatefulJob接口繼承了Job接口,框架只是通過接口的名字來標識是無狀態任務還是有狀態任務。
(二)、JobDetail
??JobDetail(org.quartz.JobDetail)從字面的意思理解就是任務詳情,它包含了任務本身和任務的其他信息。從 @Quartz集成-項目使用中的代碼可以看出,是將一個JobDetail實例通過scheduleJob()方法注冊給了調度器,而不是一個Job實現類的實例。注冊在調度器上的每一個Job都只會創建一個JobDetail實例,而當真正執行JobDetail中的Job時,調度器才會對此Job實現類進行實例化,并運行它的execute()方法。這樣的好處就是我們無需擔心線程安全性問題,因為相當于同一時刻僅有一個線程去執行Job實例。
JobDetail的屬性介紹:
屬性名 | 詳細介紹 |
---|---|
name | 任務的名稱,框架需要通過此名稱進行關聯,所以需要保證唯一性 |
group | 任務組,默認為“DEFAULT”,取值Scheduler.DEFAULT_GROUP |
jobClass | Job的實現類類對象,注意不能使用代理類,原因下面介紹 |
description | 任務描述信息,可以用來傳遞字符串參數,但是盡量不要這么干 |
jobDataMap | 類似Map類,可以通過此類的對象向Job的execute方法中傳遞業務參數 |
volatility | 重啟應用之后是否刪除任務的相關信息,默認為false |
durability | Job執行完后是否繼續持久化到數據庫,默認為false |
shouldRecover | 服務器重啟后是否忽略過期的任務,默認為false |
JobDetail類中主要的幾個方法:
構造方法:
public JobDetail(){}
public JobDetail(String name, Class jobClass){} // 常用
public JobDetail(String name, String group, Class jobClass){} // 常用
public JobDetail(String name, String group, Class jobClass, boolean volatility, boolean durability, boolean recover){}
常用方法:
public String getName(){} // 獲取Job的名稱
public JobDataMap getJobDataMap(){} //獲取Job中需要使用到的數據
(三)、JobDataMap
??實際開發中的任務業務代碼不是簡單的打印幾句話而已,經常會需要傳入各種參數才能完成。我們可以通過哪些辦法傳遞參數到Job的execute()方法中呢?
(1)、通過JobDetail中的jobDataMap屬性
??JobDataMap最頂層就是實現了java.util.Map接口,所以將它當做Map來使用就可以。但是需要注意的是,org.quartz.jobStore.userProperties默認配置為false,設置為true時表示JobDataMap中的value存放的類型必須是String類型,此配置有利于持久化時不會出現多個類版本。
??存放非字符串時會拋出一個異常信息:org.quartz.JobPersistenceException: Couldn't store job: JobDataMap values must be Strings when the 'useProperties' property is set.
??解決辦法:
???1、在quartz.properties配置文件中加入org.quartz.jobStore.userProperties=false或直接刪除此條配置取默認值
???2、將對象轉JSON字符串后存入JobDataMap。
(2)、在execute方法中根據Job的name屬性(名稱是唯一的)在數據庫中查詢
??數據庫表記錄如果生成的是UUID,那么可以將Job的類名+UUID作為Job的名稱,比如:
TimingSendMessage_4a6c346ea44d4515946b87099275446a,TimingSendMessage是Job實現類的名稱,4a6c346ea44d4515946b87099275446a是業務記錄的唯一標識,然后在execute()方法中通過JobDetail實例的getName()方法獲取到名稱,再通過“_”下劃線切割,這樣就能拿到UUID了,最后通過此UUID在數據庫中查詢需要的數據。此方法的缺點就是代碼看上去很不優雅而且代碼理解稍加復雜。
(3)、使用JobDetail的description屬性傳遞簡單的String類型參數
??直接調用setDescription(String description)方法,然后在execute()方法中通過get方法獲取。雖然這樣做可以達到目的,但是最好不要這樣,因為你寫的代碼可能最后只有你自己懂或自己都忘了干了什么。
(四)、開發中的一些小技巧和注意事項
(1)、Job的execute()方法中需要使用到Spring管理的依賴bean
??Spring幫忙管理bean的好處就是自己不用new對象啦,但是前面的文章提到過,Job的實例是在真正調度JobDetail中的Job實現類時進行實例化的,此對象由框架本身進行管理,所以實例化的對象并沒有交給Spring來管理,所以通過@Autowired注入時,會有點麻煩。
- 方法1、通過實現了org.springframework.context.ApplicationContextAware接口的java類來獲取,具體實現代碼這里不講述了。獲取示例:
ITaskStoreService taskStoreService = SpringContextHolder.getBean("taskStoreService");
- 通過@Autowired注解注入
package com.quartz.task;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.xk.quartz.entity.TaskStore;
import com.xk.quartz.service.impl.ITaskStoreService;
// 注意加上注解讓Spring掃描到此Job類
@Component
public class DefaultJob implements Job{
// 需要注入的依賴bean
private static ITaskStoreService taskStoreService;
// 定義一個set方法
@Autowired
public void setTaskStoreService(ITaskStoreService taskStoreService) {
DefaultJob.taskStoreService = taskStoreService;
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail jobDetail = context.getJobDetail();
String name = jobDetail.getName();
// 使用依賴的bean
TaskStore store = taskStoreService.selectById(name);
System.out.println(store);
}
}
(2)、Job的execute()方法不會被Spring進行事務管理,如何保證一致性?
用上面一段代碼稍作修改做以下示例:
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail jobDetail = context.getJobDetail();
String name = jobDetail.getName();
// 查詢記錄
TaskStore store = taskStoreService.selectById(name);
// 更新記錄
store.setUpdateDate(new Date());
taskStoreService.updateById(store);
// 刪除記錄
taskStoreService.deleteById("表記錄的id");
}
??從代碼可以看出,execute()方法既進行了更新和刪除,如果刪除方法因為網絡等原因刪除失敗,那么更新是不會回滾的。在execute()方法加上@Transactional也沒有用,還是上面說的原因,Job每次在需要執行的時候才進行實例化,不被Spring管理。此時我們可以這樣,將更新和刪除兩段代碼封裝到TaskStoreService服務的同一個方法中,然后在方法上加上@Transactional交給Spring來管理事務,最后在execute()方法中調用封裝后的方法。
@Transactional
public void updateAndDelete(TaskStore store, String delId) {
// 更新
store.setUpdateDate(new Date());
taskStoreService.updateById(store);
// 刪除
taskStoreService.deleteById(store);
}
(3)、如何監控所有的Job運行和日志的打印?
??可以通過Quartz提供的監聽器來實現,本章節提供另外一種自定義方式。Quartz提供的監聽器后面的章節中會講到。
??首先實現org.quartz.Job接口的一個抽象基類,然后重寫execute(JobExecutionContext context)方法,再在基類中定義一個抽象方法businessWorking(JobExecutionContext context) ,名字可以自己根據情況取,最后在execute方法中調用businessWorking方法。下面貼上代碼:
package com.quartz.task;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* 基礎Job抽象類
*/
public abstract class AbsQuartzJob implements Job{
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// Job運行前監控
System.out.println("execute before");
// 調用業務代碼
this.businessWorking(context);
// Job運行后監控
System.out.println("execute after");
}
/** 業務代碼抽象方法 */
public abstract void businessWorking(JobExecutionContext context);
}
??開發中的業務Job類直接繼承AbsQuartzJob抽象類,業務代碼寫在繼承類的businessWorking()方法中。代碼示例:
package com.quartz.task;
import org.quartz.JobExecutionContext;
/**
* 業務job
*/
public class BusinessJob extends AbsQuartzJob{
@Override
public void businessWorking(JobExecutionContext context) {
// TODO 業務代碼
}
}
??這里有人可能會問,為什么不使用JDK或CGLIB動態代理呢?我本人之前也這樣實現過,但是有個致命的問題,那就是JobDetail被持久化之后會保存到數據庫中,所以Job實現類的包路徑+類名也會存在數據庫中,當項目重啟后,所有動態代理的類都自動被虛擬機清除了,此時Quartz框架啟動后會加載被持久化的Job,當去訪問Job的實現類時,發現已經找不到此實現類了。啟動直接報錯!所以上面的實現方式可能是相對方便好用一點的方法了。