一、什么是Quartz
Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,完全由Java開發,可以用來執行定時任務,類似於java.util.Timer。但是相較於Timer, Quartz增加了很多功能:
- 持久性作業 - 就是保持調度定時的狀態;
- 作業管理 - 對調度作業進行有效的管理;
拿火車票購票來說,當你下單后,后台就會插入一條待支付的task(job),一般是30分鍾,超過30min后就會執行這個job,去判斷你是否支付,未支付就會取消此次訂單;當你支付完成之后,后台拿到支付回調后就會再插入一條待消費的task(job),Job觸發日期為火車票上的出發日期,超過這個時間就會執行這個job,判斷是否使用等。
二、Quartz核心概念
1.任務/作業(Task/Job)
位於org.quartz.Job的接口,是quartz中的核心任務接口,要實現自定義的定時任務(就是我們要執行的業務動作/作業)只需要實現此接口。
2.觸發器(Trigger)
位於org.quartz.Trigger,觸發器用來告訴調度程序作業什么時候觸發,也就是我們要設置任務觸發的條件。quartz提供了5種觸發器類型,但兩個最常用的SimpleTrigger和CronTrigger。
五種類型的Trigger(觸發器):
SimpleTrigger,CronTirgger,DateIntervalTrigger,NthIncludedDayTrigger和Calendar。
3.調度器(Schedule)
來自org.quartz.Schedule是Quartz Scheduler的主要接口,代表一個獨立運行容器。調度程序維護JobDetails和觸發器的注冊表。 一旦注冊,調度器負責執行作業,當他們的相關聯的觸發器觸發時(當他們的預定時間到達時)。不難看出功能就是將任務job與觸發器Trigger作業結合起來。
4.任務執行時上下文(JobExecutionContext)
位於org.quartz.JobExecutionContext的類,是任務在執行時quartz注入的對象,用於保存任務的詳細信息(比如觸發器,調度器,工作詳情,任務入參等),我們可以通過此對象得到運行時的任務數據,也可以接受動態參數。
主要有以下方法:
5.任務詳情(JobDetail)
位於org.quartz.JobDetail,用來綁定job,為job提供一些屬性:
- name:唯一標識
- group:分組標識
- jobClass:具體任務
- jobDataMap:任務入參
為什么設計成JobDetail + Job,不直接使用Job?
JobDetail定義的是任務數據,而真正的執行邏輯是在Job中。
這是因為任務是有可能並發執行,如果Scheduler直接使用Job,就會存在對同一個Job實例並發訪問的問題。而JobDetail & Job 方式,Sheduler每次執行,都會根據JobDetail創建一個新的Job實例,這樣就可以規避並發訪問的問題。
三、案例demo實現
1.定義自定義任務,繼承job接口
public class MyJob implements Job {
private final Logger log = LoggerFactory.getLogger(MyJob.class);
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.error("定時任務:發送一次消息,時間={"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))+
"}");
log.error("請求參數:{name="+context..getJobDetail().getJobDataMap().get("name").toString()+",sign="+context.getJobDetail().getJobDataMap().get("sign").toString()+"}");
}
}
2.創建demo
public class Demo {
public static void main(String[] args) throws SchedulerException {
//1.初始化調度器工廠
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
//2.獲取調度器
Scheduler scheduler = schedulerFactory.getScheduler();
//3.創建任務實例
JobDetail jobDetail = newJob(MyJob.class)//一個靜態builder方法
.withDescription("這是我的入門實例Job")//任務備注
.withIdentity("demoJob","demoGroup")//標識與分組
.usingJobData("name","張三")//傳入參數
.usingJobData("sign","abcdefg")
.build();
//4.創建任務觸發器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("demoJob","demoGroup")
/*.withSchedule(SimpleScheduleBuilder.simpleSchedule()//簡單任務觸發器
.withIntervalInSeconds(10)//每10秒執行一次
.withRepeatCount(5)//執行5次
)*/
.withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))//定時觸發調度器
.startAt(new Date(System.currentTimeMillis()+10))//十秒后開始執行任務
.build();
//5.將任務和觸發器注入到quartz中
scheduler.scheduleJob(jobDetail,trigger);
//6.啟動任務
scheduler.start();
}
}
3.運行測試,注意運行就會觸發一次任務,然后每5秒觸發一次
四、CronExpression表達式詳解
通過使用不難發現任務執行的條件才是我們最關心的,quartz支持Cron表達式來定義觸發器規則。
- “*”字符被用來指定所有的值。如:”“在分鍾的字段域里表示“每分鍾”。
- “-”字符被用來指定一個范圍。如:“10-12”在小時域意味着“10點、11點、12點”。
- “,”字符被用來指定另外的值。如:“MON,WED,FRI”在星期域里表示”星期一、星期三、星期五”.
- “?”字符只在日期域和星期域中使用。它被用來指定“非明確的值”。當你需要通過在這兩個域中的一個來指定一些東西的時候,它是有用的。看下面的例子你就會明白。
- “L”字符指定在月或者星期中的某天(最后一天)。即“Last ”的縮寫。但是在星期和月中“L”表示不同的意思,如:在月子段中“L”指月份的最后一天-1月31日,2月28日,如果在星期字段中則簡單的表示為“7”或者“SAT”。如果在星期字段中在某個value值得后面,則表示“某月的最后一個星期value”,如“6L”表示某月的最后一個星期五。
- “W”字符只能用在月份字段中,該字段指定了離指定日期最近的那個星期日。
- “#”字符只能用在星期字段,該字段指定了第幾個星期value在某月中
結合示例更加清晰: