1.Quartz簡介
Quartz是一款Java編寫的開源任務調度框架,同時它也是Spring默認的任務調度框架。基於定時、定期的策略來執行任務是它的核心功能,比如x年x月的每個星期五上午8點到9點,每隔10分鍾執行1次。
它的作用其實類似於Java中的Timer
定時器以及JUC中的ScheduledExecutorService
調度線程池,當然Quartz作為一個獨立的任務調度框架無疑在這方面表現的更為出色,功能更強大,能夠定義更為復雜的執行規則。
Quartz中主要用到了:Builder建造者模式、Factory工廠模式以及組件模式。
Quartz有3個核心要素:任務(Job)、觸發器(Trigger)、調度器(Scheduler)。
任務(Job,你需要做什么事?我們需要將具體的業務邏輯寫到實現了Job
接口的實現類中)
觸發器(Trigger
,你什么時候去做?它定義了任務的執行規則,什么時候開始執行,什么時候結束執行)
調度器(Scheduler
,你什么時候需要去做什么事?通過傳入的任務Job
和觸發器Trigger
,以指定的規則執行任務)。
Quartz框架的核心是調度器。調度器負責管理Quartz應用運行時環境。調度器不是靠自己做所有的工作,而是依賴框架內一些非常重要的部件。Quartz不僅僅是線程和線程池管理。為確保可伸縮性,Quartz采用了基於多線程的架構。啟動時,框架初始化一套worker線程,這套線程被調度器用來執行預定的作業。這就是Quartz能並發運行多個作業的原理。Quartz依賴一套松耦合的線程池管理部件來管理線程環境。
2.Quartz的體系結構
JobDetail:quartz每次都會直接創建一個JobDetail,同時創建一個Job實例,它不直接接受一個Job的實例,但是它接受一個Job的實現類,通過new instance()的反射方式來實例一個Job,在這里Job是一個接口,我們需要自己編寫類去實現這個接口。
Trigger : 它由SimpleTrigger和CronTrigger組成,SimpleTrigger實現類似Timer的定時調度任務,CronTrigger可以通過cron表達式實現更復雜的調度邏輯·。
SimpleTrigger很方便,如果你需要一次性執行(只是在一個給定時刻執行job),或者如果你需要一個job在一個給定的時間,並讓它重復N次,並在執行之間延遲T。
CronTrigger是有用的,如果你想擁有引發基於當前日歷時間表,如每個星期五,中午或在每個月的第十天 10:15。
Scheduler:調度器,JobDetail和Trigger可以通過Scheduler綁定到一起。
3. Quartz重要組成部分
3.1 Job接口
Job
接口很簡單,只有一個execute
方法,這是我們自己的具體業務邏輯的入口。
要創建一個任務,我們需要編寫一個實現該接口的具體任務類:
public class HelloJob implements Job{ public void execute(JobExecutionContext context) throws JobExecutionException { //編寫我們自己的業務邏輯 } }
3.2 JobDetail
JobDetail
描述了Job
對象的基本信息,主要包含四個重要的屬性:name
(Job的名稱)、group
(Job的組名稱)、jobClass
(Job對應的類)以及jobDataMap
(存儲一些用戶自定義的信息或對象)。在Scheduler
中Job
的名稱name
和組group
組合必須是唯一的。
quartz每次都會直接創建一個JobDetail,同時創建一個Job實例,它不直接接受一個Job的實例,但是它接受一個Job的實現類,通過new instance()的反射方式來實例一個Job.可以通過下面的方式將一個Job實現類綁定到JobDetail中
// 指明job的名稱,所在組的名稱,以及綁定job類 JobDetail job = JobBuilder.newJob(HelloJob.class) //綁定job類 .withIdentity("JobName", "JobGroupName") //指明job的名稱為JobName,所在組的名稱為JobGroupName .usingJobData(jobDataMap) //傳遞job執行時需要的數據 .build();
3.3 JobBuiler
主要是用來創建jobDeatil實例
3.4 JobDataMap數據存儲類
通過查看JobDetail
、Trigger
及JobExecutionContext
的源碼可以發現,他們中都存在JobDataMap
這個類型,它是以Map
的形式存儲我們的一些自定義數據的。當Job
對象的execute
方法被調用時,JobDataMap
會通過JobExecutionContext
傳遞給execute
方法,它可以用來裝載任何可序列化的數據對象。JobDataMap
實現了Java中的Map
接口,提供了一些自己的方法來存儲數據。
這是JobDataMap
的繼承樹:
可以看到JobDataMap
是DirtyFlagMap
的子類,而DirtyFlagMap
實際實現了Java中的java.util.Map
類型:
// DirtyFlagMap是java.util.Map接口的子類 public class DirtyFlagMap<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { }
一句話:把它當Java中的map
來用就對了!
3.5 trigger
前文講到它主要用來執行Job實現類的業務邏輯的,我們可以通過下面的代碼來創建一個Trigger實例
CronTrigger trigger = (CronTrigger) TriggerBuilder .newTrigger() .withIdentity("myTrigger", "group1") //創建一個標識符 .startAt(date)//什么時候開始觸發 //每秒鍾觸發一次任務 .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *")) .build();
3.6 Scheduler
創建Scheduler有兩種方式
通過StdSchedulerFactory來創建
SchedulerFactory sfact=new StdSchedulerFactory(); Scheduler scheduler=sfact.getScheduler();
通過DirectSchedulerFactory來創建
DiredtSchedulerFactory factory=DirectSchedulerFactory.getInstance();
Scheduler scheduler=factory.getScheduler();
Scheduler 配置參數一般存儲在quartz.properties中,我們可以修改參數來配置相應的參數。通過調用getScheduler()方法就能創建和初始化調度對象。
Scheduler的主要函數介紹:
Date schedulerJob(JobDetail,Trigger trigger);返回最近觸發的一次時間 void standby()暫時掛起 void shutdown()完全關閉,不能重新啟動了 shutdown(true)表示等待所有正在執行的job執行完畢之后,再關閉scheduler shutdown(false)即直接關閉scheduler
在這里我們不得不提一下quartz.properties這個資源文件,在org.quartz這個包下,當我們程序啟動的時候,它首先會到我們的根目錄下查看是否配置了該資源文件,如果沒有就會到該包下讀取相應信息,當我們咋實現更復雜的邏輯時,需要自己指定參數的時候,可以自己配置參數來實現。下面我們簡單看一下這個資源文件:
org.quartz.scheduler.instanceName: DefaultQuartzScheduler org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
該資源文件主要組成部分:
①調度器屬性
②線程池屬性
③作業存儲設置
④插件設置
<1>調度器屬性:
org.quartz.scheduler.instanceName屬性用來區分特定的調度器實例,可以按照功能用途來給調度器起名。
org.quartz.scheduler.instanceId屬性和前者一樣,也允許任何字符串,但這個值必須是在所有調度器實例中是唯一的,尤其是在一個集群當中,作為集群的唯一key,假如你想quartz幫你生成這個值的話,可以設置我Auto
<2>線程池屬性:
threadCount設置線程的數量
threadPriority設置線程的優先級
org.quartz.threadPool.class 線程池的實現
<3>作業存儲設置:
描述了在調度器實例的聲明周期中,job和trigger信息是怎么樣存儲的
<4>插件配置:
滿足特定需求用到的quartz插件的配置
4. Cron表達式
在這里,我們着重講解一下cron表達式,quartz之所以能夠實現更加復雜的業務邏輯,主要在依賴於cron表達式。
cron表達式編寫的順序一次是”秒 分 時 日 月 周 年”。
在線Cron生成表達式:http://cron.qqe2.com/
cron 一定有七位數,最后一位是年,SpringBoot 定時方案只需要設置六位即可:
- 第一位, 表示秒, 取值是0 ~ 59,允許的字符為,- * /
- 第二位, 表示分. 取值是0 ~ 59,允許的字符為 ,- * /
- 第三位, 表示小時, 取值是0 ~ 23,允許的字符為,- * /
- 第四位, 表示天/日, 取值是0 ~ 31,允許的字符為,- * ? / L W C
- 第五位, 表示月份, 取值是1 ~ 12,允許的字符為 ,- * /
- 第六位, 表示星期, 取值是1 ~ 7, 星期一,星期二…, 還有 1 表示星期日,允許的字符為,- * ? / L W C
- 第七位, 年份, 可以留空, 取值是1970 ~ 2099
cron中,還有一些特殊的符號,含義如下:
- (*) 星號,可以理解為每的意思,每秒、每分、每天、每月、每年…。
- (?) 問號,問號只能出現在日期和星期這兩個位置,表示這個位置的值不確定,每天 3 點執行,因此第六位星期的位置,是不需要關注的,就是不確定的值;同時,日期和星期是兩個相互排斥的元素,通過問號來表明不指定值,比如 1 月 10 日是星期一,如果在星期的位置另指定星期二,就前后沖突矛盾了。
- (-) 減號,表達一個范圍,如在小時字段中使用“10 - 12”,則表示從 10 到 12 點,即 10、11、12。
- (,) 逗號,表達一個列表值,如在星期字段中使用“1,2,4”,則表示星期一、星期二、星期四。
- (/) 斜杠,如 x/y,x 是開始值,y 是步長,比如在第一位(秒),0/15 就是從 0 秒開始,每隔 15 秒執行一次,最后就是 0、15、30、45、60,另 */y,等同於 0/y。
舉幾個例子熟悉一下:
表達式 意義 "0 0 12 * * ?" 每天中午12點觸發 "0 15 10 ? * *" 每天上午10:15觸發 "0 15 10 * * ?" 每天上午10:15觸發 "0 15 10 * * ? *" 每天上午10:15觸發 "0 15 10 * * ? 2005" 2005年的每天上午10:15觸發 "0 * 14 * * ?" 在每天下午2點到下午2:59期間的每1分鍾觸發 "0 0/5 14 * * ?" 在每天下午2點到下午2:55期間的每5分鍾觸發 "0 0/5 14,18 * * ?" 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鍾觸發 "0 0-5 14 * * ?" 在每天下午2點到下午2:05期間的每1分鍾觸發 "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44觸發 "0 15 10 ? * MON-FRI" 周一至周五的上午10:15觸發 "0 15 10 15 * ?" 每月15日上午10:15觸發 "0 15 10 L * ?" 每月最后一日的上午10:15觸發 "0 15 10 ? * 6L" 每月的最后一個星期五上午10:15觸發 "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一個星期五上午10:15觸發 "0 15 10 ? * 6#3" 每月的第三個星期五上午10:15觸發 特殊字符 意義 * 表示所有值; ? 表示未說明的值,即不關心它為何值; - 表示一個指定的范圍; , 表示附加一個可能值; / 符號前表示開始時間,符號后表示每次遞增的值; L("last") ("last") "L" 用在day-of-month字段意思是 "這個月最后一天";用在 day-of-week字段, 它簡單意思是 "7" or "SAT"。 如果在day-of-week字段里和數字聯合使用,它的意思就是 "這個月的最后一個星期幾" – 例如: "6L" means "這個月的最后一個星期五". 當我們用“L”時,不指明一個列表值或者范圍是很重要的,不然的話,我們會得到一些意想不到的結果。 W("weekday") 只能用在day-of-month字段。用來描敘最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近這個月第15天的工作日”,即如果這個月第15天是周六,那么觸發器將會在這個月第14天即周五觸發;如果這個月第15天是周日,那么觸發器將會在這個月第16天即周一觸發;如果這個月第15天是周二,那么就在觸發器這天觸發。注意一點:這個用法只會在當前月計算值,不會越過當前月。“W”字符僅能在day-of-month指明一天,不能是一個范圍或列表。也可以用“LW”來指定這個月的最后一個工作日。 # 只能用在day-of-week字段。用來指定這個月的第幾個周幾。例:在day-of-week字段用"6#3"指這個月第3個周五(6指周五,3指第3個)。如果指定的日期不存在,觸發器就不會觸發。 C 指和calendar聯系后計算過的值。例:在day-of-month 字段用“5C”指在這個月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在這周日或之后包括calendar的第一天
5.Quartz框架實戰
5.1 maven依賴
添加quartz的依賴:
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
核心類:
Scheduler :調度器,所有Job的調度都是由它控制;
JobDetail :生成Job對象的實例,存儲Job對象需要的參數;
Job :執行業務邏輯;
Trigger :定義觸發的條件;
幫助類
SimpleScheduleBuilder:用於構建Scheduler:
JobBuilder :用於構建JobDetail:
TriggerBuilder :用於構建Trigger;
5.2 創建job
首先創建一個Quartz任務,任務中從JobExecutionContext
中獲取到了JobDetail
和Trigger
中的JobDataMap
,並從中取到了客戶端QuartzScheduler
中傳入的數據:
/** * @Author lucky * @Date 2021/12/27 9:18 */ public class HelloJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { JobDetail detail = jobExecutionContext.getJobDetail(); String name = detail.getJobDataMap().getString("name"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //顯示的格式 String date = sdf.format(new Date()); System.out.println(date+": say hello " + name ); } }
5.3 任務調度測試案例
創建Quartz客戶端,構建JobDetail
和Trigger
並使用Scheduler
開始任務調度(這里要注意的是Scheduler
實例創建后處於“待機”狀態,所以別忘了調用start
方法啟動調度器,否則任務是不會執行的!):
/** * @Author lucky * @Date 2021/12/27 9:23 */ public class JobTest { public static void main(String[] args) throws InterruptedException { // 創建工廠 SchedulerFactory schedulerfactory = new StdSchedulerFactory(); Scheduler scheduler = null; try { // 通過schedulerFactory獲取一個調度器 scheduler = schedulerfactory.getScheduler(); JobDataMap jobDataMap=new JobDataMap(); jobDataMap.put("name","quartz" ); // 創建一個JobDetail實例,指明job的名稱,所在組的名稱,以及綁定job類 JobDetail job = JobBuilder.newJob(HelloJob.class) //綁定job類 .withIdentity("JobName", "JobGroupName") //指定JobDetail的名稱和組名稱 .usingJobData(jobDataMap) //使用jobDataMap存儲用戶數據, jobDataMap為JobDetail傳遞的文本數據 .build(); // 構建一個Trigger(定義觸發的條件),指定Trigger名稱和組,規定該Job立即執行,且3秒鍾重復執行一次 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("CronTrigger1", "CronTriggerGroup") //指定Trigger名稱和組 .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever()) // 設置運行規則,每隔3秒執行一次,一直重復下去 .startNow() // 執行的時機,立即執行 .build(); //綁定JobDetail和Trigger scheduler.scheduleJob(job, trigger); //開始任務調度 scheduler.start(); Thread.sleep(30000); // 停止任務調度 scheduler.shutdown(); } catch (SchedulerException e) { e.printStackTrace(); } } }
控制台輸出:
6. @scheduled注解定時任務
在 Spring Boot 中使用 @Scheduled 注解創建定時任務非常簡單,只需要兩步操作就可以創建一個定時任務:
(1)在定時任務類上增加 @EnableScheduling 注解
(2)在要執行任務的方法上增加 @Scheduled 注解
(3)ShedLock的作用,確保任務在同一時刻最多執行一次。如果一個任務正在一個節點上執行,則它將獲得一個鎖,該鎖將阻止從另一個節點(或線程)執行同一任務。如果一個任務已經在一個節點上執行,則在其他節點上的執行不會等待,只需跳過它即可
package com.ttbank.flep.core.job; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; /** * @Author lucky * @Date 2022/1/27 10:37 */ @Component @Configurable @EnableScheduling public class ScheduledTasks { /** * 每6秒執行一次 **/ @Scheduled(cron = "*/6 * * * * * ") public void reportCurrentByCron(){ SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println ("Scheduling Tasks Examples By Cron: The time is now " + sdf.format (new Date())); } }
控制台輸出:
參考文獻:
https://zhuanlan.zhihu.com/p/133208221---非常好
https://zhuanlan.zhihu.com/p/133211946
https://blog.csdn.net/chengqiuming/article/details/84187419---非常好
https://blog.csdn.net/cyan20115/article/details/106550915
https://blog.csdn.net/java_hanyu_tel/article/details/79697161----非常好
https://www.cnblogs.com/haw2106/p/9950826.html-----非常好
https://www.cnblogs.com/loong-hon/p/10912741.html-----------非常好。
https://www.cnblogs.com/niceyoo/p/10917461.html