一.Quartz概念
Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,它可以與J2EE與J2SE應用程序相結合也可以單獨使用。
quartz是開源且具有豐富特性的"任務調度庫",能夠集成於任何的java應用,小到獨立的應用,大至電子商業系統。quartz能夠創建亦簡單亦複雜的調度,以執行上十、上百,甚至上萬的任務。任務job被定義爲標準的java組件,能夠執行任何你想要實現的功能。quartz調度框架包含許多企業級的特性,如JTA事務、集羣的支持。
簡而言之,quartz就是基於java實現的任務調度框架,用於執行你想要執行的任何任務。
官網: http://www.quartz-scheduler.org/
二.Quartz運行環境
- Quartz 可以運行嵌入在另一個獨立式應用程序
- Quartz 可以在應用程序服務器(或servlet容器)內被實例化,並且參與事務
- Quartz 可以作爲一個獨立的程序運行(其自己的Java虛擬機內),可以通過RMI使用
- Quartz 可以被實例化,作爲獨立的項目集羣(負載平衡和故障轉移功能),用於作業的執行
三.Quartz設計模式
- Builder模式
- Factory模式
- 組件模式
- 鏈式編程
四.Quartz學習的核心概念
- 任務Job
Job就是你想要實現的任務類,每一個Job必須實現org.quartz.job接口,且只需實現接口定義的execute()方法。 - 觸發器Trigger
Trigger爲你執行任務的觸發器,比如你想每天定時3點發送一份統計郵件,Trigger將會設置3點進行執行該任務。Trigger主要包含兩種SimpleTrigger和CronTrigger兩種。關於二者的區別的使用場景,後續會進行討論。 - 調度器Scheduler
Scheduler爲任務的調度器,它會將任務job及觸發器Trigger整合起來,負責基於Trigger設定的時間來執行Job。
五.Quartz的體系結構
六.Quartz的幾個常用API
以下是Quartz編程API幾個重要接口,也是Quartz的重要組件
- Scheduler 用於與調度程序交互的主程序接口。 Scheduler 調度程序-任務執行計劃表,只有安排進執行計劃的任務Job(通過scheduler.scheduleJob方法安排進執行計劃),當它預先定義的執行時間到了的時候(任務觸發trigger),該任務纔會執行。
- Job 我們預先定義的希望在未來時間能被調度程序執行的任務類,我們可以自定義。
- JobDetail 使用JobDetail來定義定時任務的實例,JobDetail實例是通過JobBuilder類創建的。
- JobDataMap 可以包含不限量的(序列化的)數據對象,在job實例執行的時候,可以使用 其中的數據;JobDataMap是Java Map接口的一個實現,額外增加了一些便於存取基本類型的數 據的方法。
- Trigger 觸發器,Trigger對象是用來觸發執行Job的。當調度一個job時,我們實例一個觸發器然後調整它的屬性來滿足job執行的條件。表明任務在什麼時候會執行。定義了一個已經被安排的任務將會在什麼時候執行的時間條件,比如每2秒就執行一次。
- JobBuilder -用於聲明一個任務實例,也可以定義關於該任務的詳情比如任務名、組名等,這個聲明的實例將會作爲一個實際執行的任務。
- TriggerBuilder 觸發器創建器,用於創建觸發器trigger實例。
- JobListener、TriggerListener、SchedulerListener監聽器,用於對組件的監聽。
七.Quartz的使用
1.引入Quartz的jar包**
<dependencies>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
</dependencies>
<!-- 編譯插件 -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<!-- 插件的版本 -->
<version>3.5.1</version>
<!-- 編譯級別 -->
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- 編碼格式 -->
<encoding>UTF-8</encoding>
</configuration>
</plugin>
2.導入log4j.properties日誌文件
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=info, stdout
3.入門案例
(1)創建HelloJob任務類
HelloJob.java
// 定義任務類
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
// 定義時間
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = dateFormat.format(date);
// 定義工作任務內容
System.out.println("進行數據庫備份操作。當前任務執行的時間:"+dateString);
}
}
(2)創建任務調度類HelloSchedulerDemo
HelloSchedulerDemo.java
public class HelloSchedulerDemo {
public static void main(String[] args) throws Exception {
// 1:從工廠中獲取任務調度的實例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2:定義一個任務調度實例,將該實例與HelloJob綁定,任務類需要實現Job接口
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1") // 定義該實例唯一標識
.build();
// 3:定義觸發器 ,馬上執行, 然後每5秒重複執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定義該實例唯一標識
.startNow() // 馬上執行
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.repeatSecondlyForever(5)) // 每5秒執行一次
.build();
// 4:使用觸發器調度任務的執行
scheduler.scheduleJob(job, trigger);
// 5:開啓
scheduler.start();
// 關閉
// scheduler.shutdown();
}
}
(3)實現效果
4.Job和JobDetail介紹
- Job:工作任務調度的接口,任務類需要實現該接口。該接口中定義execute方法,類似JDK提供的TimeTask類的run方法。在裏面編寫任務執行的業務邏輯。
- Job實例在Quartz中的生命週期:每次調度器執行Job時,它在調用execute方法前會創建一個新的Job實例,當調用完成後,關聯的Job對象實例會被釋放,釋放的實例會被垃圾回收機制回收。
- JobDetail:JobDetail爲Job實例提供了許多設置屬性,以及JobDetaMap成員變量屬性,它用來存儲特定Job實例的狀態信息,調度器需要藉助JobDetail對象來添加Job實例。
- JobDetail重要屬性:name、group、jobClass、jobDataMap
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1") // 定義該實例唯一標識,並指定一個組。
.build();
System.out.println("name:"+job.getKey().getName());
System.out.println("group:"+job.getKey().getGroup());
System.out.println("jobClass:"+job.getJobClass().getName());
5.JobExecutionContext介紹
- 當Scheduler調用一個Job,就會將JobExecutionContext傳遞給Job的execute()方法;
- Job能通過JobExecutionContext對象訪問到Quartz運行時候的環境以及Job本身的明細數據。
- 可以看到每個實例都不一樣
6.JobDataMap介紹
(1)使用Map獲取。 - 在進行任務調度時,JobDataMap存儲在JobExecutionContext中 ,非常方便獲取。 - JobDataMap可以用來裝載任何可序列化的數據對象,當job實例對象被執行時這些參數對象會傳遞給它。 - JobDataMap實現了JDK的Map接口,並且添加了非常方便的方法用來存取基本數據類型。
HelloSchedulerDemo.java
// 2:定義一個任務調度實例,將該實例與HelloJob綁定,任務類需要實現Job接口
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1") // 定義該實例唯一標識
.usingJobData("message", "打印日誌")
.build();
// 3:定義觸發器 ,馬上執行, 然後每5秒重複執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定義該實例唯一標識
.startNow() // 馬上執行
//.startAt(triggerStartTime) // 針對某個時刻執行
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.repeatSecondlyForever(5)) // 每5秒執行一次
.usingJobData("message", "simple觸發器")
.build();
HelloJob.java
JobKey jobKey = context.getJobDetail().getKey();
System.out.println("工作任務名稱:"+jobKey.getName()+";工作任務組:"+jobKey.getGroup());
System.out.println("任務類名稱(帶包名):"+context.getJobDetail().getJobClass().getName());
System.out.println("任務類名稱:"+context.getJobDetail().getJobClass().getSimpleName());
System.out.println("當前任務執行時間:"+context.getFireTime());
System.out.println("下一任務執行時間:"+context.getNextFireTime());
TriggerKey triggerKey = context.getTrigger().getKey();
System.out.println("觸發器名稱:"+triggerKey.getName()+";觸發器組:"+triggerKey.getGroup());
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String jobMessage = jobDataMap.getString("message");
System.out.println("任務參數消息值:"+jobMessage);
JobDataMap triggerDataMap = context.getTrigger().getJobDataMap();
String triggerMessage = triggerDataMap.getString("message");
System.out.println("觸發器參數消息值:"+triggerMessage);
(2)Job實現類中添加setter方法對應JobDataMap的鍵值,Quartz框架默認的JobFactory實現類在初始化job實例對象時會自動地調用這些setter方法。
HelloJob.java
private String message;
public void setMessage(String message) {
this.message = message;
}
注意:如果遇到同名的key,Trigger中的.usingJobData(“message”, “simple觸發器”)會覆蓋JobDetail中的.usingJobData(“message”, “打印日誌”)。
7.有狀態的Job和無狀態的Job
@PersistJobDataAfterExecution註解的使用
有狀態的Job可以理解爲多次Job調用期間可以持有一些狀態信息,這些狀態信息存儲在JobDataMap中,而默認的無狀態job每次調用時都會創建一個新的JobDataMap。
(1)修改HelloSchedulerDemo.java。添加.usingJobData(“count”, 0),表示計數器。
JobDetail job = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1") // 定義該實例唯一標識
.usingJobData("message", "打印日誌")
.usingJobData("count", 0) .build();
(2)修改HelloJob.java
添加count的setting和getting方法。
private Integer count;
public void setCount(Integer count) {
this.count = count;
}
在public void execute(JobExecutionContext context) throws JobExecutionException的方法中添加。
++count;
System.out.println("count數量:"+count);
context.getJobDetail().getJobDataMap().put("count", count);
HelloJob類沒有添加@PersistJobDataAfterExecution註解,每次調用時都會創建一個新的JobDataMap。不會累加;
HelloJob類添加@PersistJobDataAfterExecution註解,多次Job調用期間可以持有一些狀態信息,即可以實現count的累加。
可以看到:job對象不是同一個對象,但是數量一直在遞增
8.Trigger介紹
Quartz有一些不同的觸發器類型,不過,用得最多的是SimpleTrigger和CronTrigger。
(1)jobKey
表示job實例的標識,觸發器被觸發時,該指定的job實例會被執行。
(2)startTime
表示觸發器的時間表,第一次開始被觸發的時間,它的數據類型是java.util.Date。
(3)endTime
指定觸發器終止被觸發的時間,它的數據類型是java.util.Date。
案例:
HelloJobTrigger.java
// 定義任務類
public class HelloJobTrigger implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 定義時間
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = dateFormat.format(date);
// 定義工作任務內容
System.out.println("進行數據庫備份操作。當前任務執行的時間:"+dateString);
// 獲取jobKey、startTime、endTime
Trigger trigger = context.getTrigger();
System.out.println("jobKey的標識:"+trigger.getJobKey().getName()+";jobKey的組名稱:"+trigger.getJobKey().getGroup());
System.out.println("任務開始時間:"+dateFormat.format(trigger.getStartTime())+";任務結束時間:"+dateFormat.format(trigger.getEndTime()));
}
}
HelloSchedulerDemoTrigger.java
public class HelloSchedulerDemoTrigger {
public static void main(String[] args) throws Exception {
// 1:從工廠中獲取任務調度的實例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 定義日期
Date startDate = new Date();
// 啓動任務,任務在當前時間3秒後執行
startDate.setTime(startDate.getTime()+3000);
// 定義日期
Date endDate = new Date();
// 結束任務,任務在當前時間10秒後停止
endDate.setTime(endDate.getTime()+10000);
// 2:定義一個任務調度實例,將該實例與HelloJob綁定,任務類需要實現Job接口
JobDetail job = JobBuilder.newJob(HelloJobTrigger.class)
.withIdentity("job1", "group1") // 定義該實例唯一標識
.usingJobData("message", "打印日誌")
.build();
// 3:定義觸發器 ,馬上執行, 然後每5秒重複執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定義該實例唯一標識
.startAt(startDate)
.endAt(endDate)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.repeatSecondlyForever(5)) // 每5秒執行一次
.usingJobData("message", "simple觸發器")
.build();
// 4:使用觸發器調度任務的執行
scheduler.scheduleJob(job, trigger);
// 5:開啓
scheduler.start();
// 關閉
// scheduler.shutdown();
}
}
開始3秒後執行,執行完之後,在次執行就是8秒,不夠十秒了,下次就不在執行了。
9.SimpleTrigger觸發器
SimpleTrigger對於設置和使用是最爲簡單的一種 QuartzTrigger。
它是爲那種需要在特定的日期/時間啓動,且以一個可能的間隔時間重複執行 n 次的 Job 所設計的。
案例一:表示在一個指定的時間段內,執行一次作業任務;
HelloJobSimpleTrigger.java
// 定義任務類
public class HelloJobSimpleTrigger implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 定義時間
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = dateFormat.format(date);
// 定義工作任務內容
System.out.println("進行數據庫備份操作。當前任務執行的時間:"+dateString);
}
}
HelloSchedulerDemoSimpleTrigger.java
public class HelloSchedulerDemoSimpleTrigger {
public static void main(String[] args) throws Exception {
// 1:從工廠中獲取任務調度的實例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 定義日期
Date startDate = new Date();
// 啓動任務,任務在當前時間3秒後執行
startDate.setTime(startDate.getTime()+3000);
// 2:定義一個任務調度實例,將該實例與HelloJob綁定,任務類需要實現Job接口
JobDetail job = JobBuilder.newJob(HelloJobSimpleTrigger.class)
.withIdentity("job1", "group1") // 定義該實例唯一標識
.build();
// 3:定義觸發器 ,馬上執行, 然後每5秒重複執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定義該實例唯一標識
.startAt(startDate)
.build();
// 4:使用觸發器調度任務的執行
scheduler.scheduleJob(job, trigger);
// 5:開啓
scheduler.start();
// 關閉
// scheduler.shutdown();
}
}
案例二:或在指定的時間間隔內多次執行作業任務。
修改HelloSchedulerDemoSimpleTrigger.java
// 3:定義觸發器 ,馬上執行, 然後每5秒重複執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定義該實例唯一標識
.startAt(startDate)
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5)
.withRepeatCount(2)) // 每5秒執行一次,連續執行3次後停止,默認值是0
.build();
案例三:指定任務的結束時間。
修改HelloSchedulerDemoSimpleTrigger.java
// 定義日期
Date endDate = new Date();
// 啓動結束,任務在當前時間10秒後停止
endDate.setTime(endDate.getTime()+10000);
// 2:定義一個任務調度實例,將該實例與HelloJob綁定,任務類需要實現Job接口
JobDetail job = JobBuilder.newJob(HelloJobSimpleTrigger.class)
.withIdentity("job1", "group1") // 定義該實例唯一標識
.build();
// 3:定義觸發器 ,馬上執行, 然後每5秒重複執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定義該實例唯一標識
.startAt(startDate)
.endAt(endDate)
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5)
.withRepeatCount(2)) // 每5秒執行一次,連續執行3次後停止
.build();
理論上該執行三次的,但是我們設置了結束時間,可以看到時間到了,就停止執行了
需要注意的點
- SimpleTrigger的屬性有:開始時間、結束時間、重複次數和重複的時間間隔。
- 重複次數屬性的值可以爲0、正整數、或常量 SimpleTrigger.REPEAT_INDEFINITELY。
- 重複的時間間隔屬性值必須爲大於0或長整型的正整數,以毫秒作爲時間單位,當重複的時間間隔爲0時,意味着與Trigger同時觸發執行。
- 如果有指定結束時間屬性值,則結束時間屬性優先於重複次數屬性,這樣的好處在於:當我們需要創建一個每間隔10秒鐘觸發一次直到指定的結束時間的
Trigger,而無需去計算從開始到結束的所重複的次數,我們只需簡單的指定結束時間和使用REPEAT_INDEFINITELY作爲重複次數的屬性
值即可。
10.CronTrigger觸發器
如果你需要像日曆那樣按日程來觸發任務,而不是像SimpleTrigger 那樣每隔特定的間隔時間觸發,CronTriggers通常比SimpleTrigger更有用,因爲它是基於日曆的作業調度器。
使用CronTrigger,你可以指定諸如“每個週五中午”,或者“每個工作日的9:30”或者“從每個週一、週三、週五的上午9:00到上午10:00之間每隔五分鐘”這樣日程安排來觸發。甚至,象SimpleTrigger一樣,CronTrigger也有一個startTime以指定日程從什麼時候開始,也有一個(可選的)endTime以指定何時日程不再繼續。
(1)Cron Expressions——Cron 表達式
Cron表達式被用來配置CronTrigger實例。Cron表達式是一個由7個子表達式組成的字符串。每個子表達式都描述了一個單獨的日程細節。這些子表達式用空格分隔,分別表示: 1. Seconds 秒 2. Minutes 分鐘 3. Hours 小時 4. Day-of-Month 月中的天 5. Month 月 6. Day-of-Week 週中的天 7. Year (optional field) 年(可選的域)
取值:
單個子表達式可以包含範圍或者列表。例如:前面例子中的週中的天這個域(這裏是"WED")可以被替換爲"MON-FRI", “MON, WED, FRI"或者甚至"MON-WED,SAT”。
所有的域中的值都有特定的合法範圍,這些值的合法範圍相當明顯,例如:秒和分域的合法值爲0到59,小時的合法範圍是0到23,Day-of-Month中值得合法凡範圍是1到31,但是需要注意不同的月份中的天數不同。月份的合法值是1到12。或者用字符串JAN,FEB MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 及DEC來表示。Days-of-Week可以用1到7來表示(1=星期日)或者用字符串SUN, MON, TUE, WED, THU, FRI 和SAT來表示.
練習一下:
"0 0 10,14,16 * * ?" 每天上午10點,下午2點,4點
"0 0/30 9-17 * * ?" 朝九晚五工作時間內每半小時,從0分開始每隔30分鐘發送一次
"0 0 12 ? * WED" 表示每個星期三中午12點
"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/55 14 * * ?" 在每天下午2點到下午2:55期間,從0開始到55分鐘觸發
"0 0/55 14,18 * * ?" 在每天下午2點到2:55期間和下午6點到6:55期間的,從0開始到55分鐘觸發
"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觸發
案例:
HelloJobCronTrigger.java
// 定義任務類
public class HelloJobCronTrigger implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 定義時間
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = dateFormat.format(date);
// 定義工作任務內容
System.out.println("進行數據庫備份操作。當前任務執行的時間:"+dateString);
}
}
HelloSchedulerDemoCronTrigger.java
public class HelloSchedulerDemoCronTrigger {
public static void main(String[] args) throws Exception {
// 1:從工廠中獲取任務調度的實例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2:定義一個任務調度實例,將該實例與HelloJob綁定,任務類需要實現Job接口
JobDetail job = JobBuilder.newJob(HelloJobCronTrigger.class)
.withIdentity("job1", "group1") // 定義該實例唯一標識
.build();
// 3:定義觸發器 ,馬上執行, 然後每5秒重複執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 定義該實例唯一標識
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * 6 4 ?"))// 定義表達式
.build();
// 4:使用觸發器調度任務的執行
scheduler.scheduleJob(job, trigger);
// 5:開啓
scheduler.start();
// 關閉
// scheduler.shutdown();
}
}
小提示:
‘L’和‘W’可以一起使用。(企業可用在工資計算)
‘#’可表示月中第幾個周幾。(企業可用在計算母親節和父親節)
周字段英文字母不區分大小寫,例如MON==mon。
利用工具,在線生成。
推薦:Quartz快速入門指南