點贊多大膽,就有多大產!路漫漫其修遠兮,吾將上下而求索,開源促使進步,獻給每一位技術使用者和愛好者!
乾貨滿滿,擺好姿勢,點贊發車,學習知識
定時任務介紹
什麼是定時任務
大部分項目都會使用到定時任務這個功能,拿商城訂單來說,當你下單之後如果沒有付款,後臺就會插入一條待支付的task(job),一般是30分鐘,超過30min後就會執行這個job,去判斷你是否支付,如果30分鐘後沒有支付則取消這個訂單,定時任務在項目中使用場景非常多,比如:郵件定時發送,優惠券到期提醒等等場景。
常用定時工具
Java 的Timer
這是java自帶的java.util.Timer類,這個類允許你調度一個java.util.TimerTask任務。使用這種方式可以讓你的程序按照某一個頻度執行,但不能在指定時間運行。一般用的較少
Spring Task
Spring3.0以後自帶的task,可以將它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多
Quartz
這是一個功能比較強大的的調度器,可以讓你的程序在指定時間執行,也可以按照某一個頻度執行,用的比較多的定時任務工具
Quartz
什麼是Quartz
Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目。
Quartz 是一個完全由 Java 編寫的開源作業調度框架,爲在 Java 應用程序中進行作業調度提供了簡單卻強大的機制
Quartz 可以與 J2EE 與 J2SE 應用程序相結合也可以單獨使用。
Quartz 允許程序開發人員根據時間的間隔來調度作業。
Quartz 實現了作業和觸發器的多對多的關係,還能把多個作業與不同的觸發器關聯。
Quartz核心概念
我們需要明白 Quartz 的幾個核心概念,這樣理解起 Quartz 的原理就會變得簡單了。
- Job 表示一個工作,要執行的具體內容。此接口中只有一個方法,如下:
void execute(JobExecutionContext context)
- JobDetail 表示一個具體的可執行的調度程序,Job 是這個可執行程調度程序所要執行的內容,另外 JobDetail 還包含了這個任務調度的方案和策略。
- Trigger 代表一個調度參數的配置,什麼時候去調。包括SimpleTrigger和CronTrigger
- Scheduler 代表一個調度容器,一個調度容器中可以註冊多個 JobDetail 和 Trigger。當 Trigger 與 JobDetail 組合,就可以被 Scheduler 容器調度了
- 所以我們使用Quartz時需要創建一個Job、使用Trigger來定義調用時間和策略、通過Scheduler來調度任務。
Quartz運行環境
- Quartz 可以運行嵌入在另一個獨立式應用程序。
- Quartz 可以在應用程序服務器(或 servlet 容器)內被實例化,並且參與 XA 事務。
- Quartz 可以作爲一個獨立的程序運行(其自己的 Java 虛擬機內),可以通過 RMI 使用。
- Quartz 可以被實例化,作爲獨立的項目集羣(負載平衡和故障轉移功能),用於作業的執行
Quartz入門案例
我們這裏的案例每1秒調用一次任務,執行1分鐘後結束
導入依賴
<!--quzrtz依賴-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
創建Job
package com.stt.springbootquartz.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* ClassName: PrintWordJob
* Description: 打印數據
* date: 2019/11/14 0014 下午 22:40
*
* @author stt
* @since JDK 1.8
*/
public class PrintWordJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
//打印時間
System.out.println(printTime);
}
}
創建Schedule
package com.stt.springbootquartz.schedule;
import com.stt.springbootquartz.job.PrintWordJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
/**
* ClassName: MyScheduler
* Description:
* date: 2019/11/14 0014 下午 22:43
*
* @author stt
* @since JDK 1.8
*/
public class MyScheduler {
public static void main(String[] args) throws SchedulerException, InterruptedException {
//1、創建調度器Scheduler
StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//2、創建JobDetail實例、b並與PrintWordJob綁定
JobDetail jobDetail = JobBuilder.newJob(PrintWordJob.class).withIdentity("job1", "group1").build();
//3、創建Trigger實例,每隔1秒執行一次
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.startNow()//立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)//每秒執行一次
.repeatForever())//一直執行
.build();
//4、執行
scheduler.scheduleJob(jobDetail,trigger);
System.out.println("調度器開始");
scheduler.start();
//睡眠
TimeUnit.MINUTES.sleep(1);
scheduler.shutdown();
System.out.println("調度器關閉");
}
}
Quartz API
描述
Quartz API的關鍵接口是:
- Scheduler - 與調度程序交互的主要API。
- Job - 由希望由調度程序執行的組件實現的接口。
- JobDetail - 用於定義作業的實例。
- Trigger(即觸發器) - 定義執行給定作業的計劃的組件。
- JobBuilder - 用於定義/構建JobDetail實例,用於定義作業的實例。
- TriggerBuilder - 用於定義/構建觸發器實例。
Scheduler的生命期,從SchedulerFactory創建它時開始,到Scheduler調用shutdown()方法時結束;Scheduler被創建後,可以增加、刪除和列舉Job和Trigger,以及執行其它與調度相關的操作(如暫停Trigger)。但是,Scheduler只有在調用start()方法後,纔會真正地觸發trigger(即執行job)。
Job和JobDetail
可以看到,我們傳給scheduler一個JobDetail實例,因爲我們在創建JobDetail時,將要執行的job的類名傳給了JobDetail,所以scheduler就知道了要執行何種類型的job;每次當scheduler執行job時,在調用其execute(…)方法之前會創建該類的一個新的實例;執行完畢,對該實例的引用就被丟棄了,實例會被垃圾回收;這種執行策略帶來的一個後果是,job必須有一個無參的構造函數(當使用默認的JobFactory時);另一個後果是,在job類中,不應該定義有狀態的數據屬性,因爲在job的多次執行中,這些屬性的值不會保留。那麼如何給job實例增加屬性或配置呢?如何在job的多次執行中,跟蹤job的狀態呢?答案就是:JobDataMap,JobDetail對象的一部分
爲什麼設計成JobDetail + Job,不直接使用Job,因爲JobDetail定義的是任務數據,而真正的執行邏輯是在Job中。這是因爲任務是有可能併發執行,如果Scheduler直接使用Job,就會存在對同一個Job實例併發訪問的問題。而JobDetail & Job 方式,Sheduler每次執行,都會根據JobDetail創建一個新的Job實例,這樣就可以規避併發訪問的問題。
JobDataMap
JobDataMap中可以包含不限量的(序列化的)數據對象,在job實例執行的時候,可以使用其中的數據;JobDataMap是Java Map接口的一個實現,額外增加了一些便於存取基本類型的數據的方法。將job加入到scheduler之前,在構建JobDetail時,可以將數據放入JobDataMap,如下示例:
JobExecutionContext
JobExecutionContext中包含了Quartz運行時的環境以及Job本身的詳細數據信息。當Schedule調度執行一個Job的時候,就會將JobExecutionContext傳遞給該Job的execute()中,Job就可以通過JobExecutionContext對象獲取信息
Trigger
Trigger是Quartz的觸發器,會去通知Scheduler何時去執行對應Job。
new Trigger().startAt():表示觸發器首次被觸發的時間;
new Trigger().endAt():表示觸發器結束觸發的時間;
SimpleTrigger
SimpleTrigger可以實現在一個指定時間段內執行一次作業任務或一個時間段內多次執行作業任務。下面提供幾個案例
指定時間開始觸發,不重複
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime) // 定義時間
.forJob("job1", "group1")
.build();
指定時間觸發,每隔10秒執行一次,重複10次
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(myTimeToStartFiring) // i如果沒有給出開始時間(如果忽略了這一行),則暗示“now”
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10)) // 10次重複將產生11次發射
.forJob(myJob) //標記作業
.build();
5分鐘以後開始觸發,僅執行一次:
trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger5", "group1")
.startAt(futureDate(5, IntervalUnit.MINUTE)) // 使用DateBuilder在將來創建一個日期
.forJob(myJobKey)
.build();
立即觸發,每個5分鐘執行一次,直到22:00:
trigger = newTrigger()
.withIdentity("trigger7", "group1")
.withSchedule(simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever())
.endAt(dateOf(22, 0, 0))
.build();
建立一個觸發器,將在下一個小時的整點觸發,然後每2小時重複一次:
trigger = newTrigger()
.withIdentity("trigger8")
.startAt(evenHourDate(null))
.withSchedule(simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
.build();
scheduler.scheduleJob(trigger, job);
SpringBoot整合Quartz
pom依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
任務類
public class TestJob extends QuartzJobBean {
private static final Logger log = LoggerFactory.getLogger(TestJob.class);
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("調用。。。。。。。。。");
}
}
定時任務配置類
@Configuration
public class QuartzConfiguration {
@Bean
public JobDetail testJobDetail(){
return JobBuilder.newJob(TestJob.class).withIdentity("testJob")
.storeDurably().build();
}
@Bean
public Trigger testTrigger(){
//設置定時策略
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2).repeatForever();
//創建Trigger對象
return TriggerBuilder.newTrigger().
forJob(testJobDetail()).withIdentity("testTrigger").withSchedule(scheduleBuilder).build();
}
}
到這裏大家啓動SpringBoot項目,控制檯每2秒就會輸出一個 調用。。。