版權聲明:本文由吳仙傑創做整理,轉載請註明出處:http://www.javashuo.com/article/p-xsencvmg-y.htmlhtml
Quartz 設計有三個核心類,分別是 Scheduler(調度器)Job(任務)和 Trigger (觸發器),它們是咱們使用 Quartz 的關鍵。java
1)Job:定義須要執行的任務。該類是一個接口,只定義一個方法 execute(JobExecutionContext context)
,在實現類的 execute
方法中編寫所須要定時執行的 Job(任務), JobExecutionContext
類提供了調度應用的一些信息。Job 運行時的信息保存在 JobDataMap 實例中。shell
2)Trigger:負責設置調度策略。該類是一個接口,描述觸發 job 執行的時間觸發規則。主要有 SimpleTrigger 和 CronTrigger 這兩個子類。當且僅當需調度一次或者以固定時間間隔週期執行調度,SimpleTrigger 是最適合的選擇;而 CronTrigger 則能夠經過 Cron 表達式定義出各類複雜時間規則的調度方案:如工做日週一到週五的 15:00~16:00 執行調度等。segmentfault
3)Scheduler:調度器就至關於一個容器,裝載着任務和觸發器。該類是一個接口,表明一個 Quartz 的獨立運行容器, Trigger 和 JobDetail 能夠註冊到 Scheduler 中, 二者在 Scheduler 中擁有各自的組及名稱, 組及名稱是 Scheduler 查找定位容器中某一對象的依據, Trigger 的組及名稱必須惟一, JobDetail 的組和名稱也必須惟一(但能夠和 Trigger 的組和名稱相同,由於它們是不一樣類型的)。Scheduler 定義了多個接口方法, 容許外部經過組及名稱訪問和控制容器中 Trigger 和 JobDetail。併發
Scheduler 能夠將 Trigger 綁定到某一 JobDetail 中, 這樣當 Trigger 觸發時, 對應的 Job 就被執行。一個 Job 能夠對應多個 Trigger, 但一個 Trigger 只能對應一個 Job。能夠經過 SchedulerFactory 建立一個 SchedulerFactory 實例。Scheduler 擁有一個 SchedulerContext,它相似於 SchedulerContext,保存着 Scheduler 上下文信息,Job 和 Trigger 均可以訪問 SchedulerContext 內的信息。SchedulerContext 內部經過一個 Map,以鍵值對的方式維護這些上下文數據,SchedulerContext 爲保存和獲取數據提供了多個 put()
和 getXxx()
的方法。能夠經過 Scheduler#getContext()
獲取對應的 SchedulerContext 實例。ide
4)JobDetail:描述 Job 的實現類及其它相關的靜態信息,如:Job 名字、描述、關聯監聽器等信息。Quartz 每次調度 Job 時, 都從新建立一個 Job 實例, 因此它不直接接受一個 Job 的實例,相反它接收一個 Job 實現類,以便運行時經過 newInstance()
的反射機制實例化 Job。ui
5)ThreadPool:Scheduler 使用一個線程池做爲任務運行的基礎設施,任務經過共享線程池中的線程提升運行效率。.net
Job 有一個 StatefulJob 子接口(Quartz 2 後用 @PersistJobDataAfterExecution
註解代替),表明有狀態的任務,該接口是一個沒有方法的標籤接口,其目的是讓 Quartz 知道任務的類型,以便採用不一樣的執行方案。線程
無狀態任務在執行時擁有本身的 JobDataMap 拷貝,對 JobDataMap 的更改不會影響下次的執行。設計
有狀態任務共享同一個 JobDataMap 實例,每次任務執行對 JobDataMap 所作的更改會保存下來,後面的執行能夠看到這個更改,也即每次執行任務後都會對後面的執行發生影響。
正由於這個緣由,無狀態的 Job 能併發執行,而有狀態的 StatefulJob 不能併發執行。這意味着若是前次的 StatefulJob 尚未執行完畢,下一次的任務將阻塞等待,直到前次任務執行完畢。有狀態任務比無狀態任務須要考慮更多的因素,程序每每擁有更高的複雜度,所以除非必要,應該儘可能使用無狀態的 Job。
6)Listener:Quartz 擁有完善的事件和監聽體系,大部分組件都擁有事件,如:JobListener 監放任務執行前事件、任務執行後事件;TriggerListener 監聽觸發器觸發前事件、觸發後事件;TriggerListener 監聽調度器開始事件、關閉事件等等,能夠註冊相應的監聽器處理感興趣的事件。
使用 Quartz 進行任務調度:
package org.quartz.examples; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import java.util.ArrayList; import java.util.Date; import java.util.List; public class QuartzTest implements Job { /** * Quartz requires a public empty constructor so that the * scheduler can instantiate the class whenever it needs. */ public QuartzTest() { } /** * 該方法實現須要執行的任務 */ @SuppressWarnings("unchecked") @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 從 context 中獲取 instName, groupName 以及 dataMap String instName = context.getJobDetail().getKey().getName(); String groupName = context.getJobDetail().getKey().getGroup(); JobDataMap dataMap = context.getJobDetail().getJobDataMap(); // 從 dataMap 中獲取 myDescription, myValue 以及 myArray String myDescription = dataMap.getString("myDescription"); int myValue = dataMap.getInt("myValue"); List<String> myArray = (List<String>) dataMap.get("myArray"); System.out.println("---> Instance = " + instName + ", group = " + groupName + ", description = " + myDescription + ", value =" + myValue + ", array item[0] = " + myArray.get(0)); System.out.println("Runtime: " + new Date().toString() + " <---"); } public static void main(String[] args) throws SchedulerException, InterruptedException { // 經過 schedulerFactory 獲取一個調度器 SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); // 建立 jobDetail 實例,綁定 Job 實現類 // 指明 job 的名稱,所在組的名稱,以及綁定 job 類 JobDetail job = JobBuilder.newJob(QuartzTest.class).withIdentity("job1", "group1").build(); // 定義調度觸發規則 // SimpleTrigger,從當前時間的下 1 秒開始,每隔 1 秒執行 1 次,重複執行 2 次 /*Trigger trigger = TriggerBuilder.newTrigger() // 指明 trigger 的 name 和 group .withIdentity("trigger1", "group1") // 從當前時間的下 1 秒開始執行,默認爲當即開始執行(.startNow()) .startAt(DateBuilder.evenSecondDate(new Date())) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1) // 每隔 1 秒執行 1 次 .withRepeatCount(2)) // 重複執行 2 次,一共執行 3 次 .build();*/ // corn 表達式,先當即執行一次,而後每隔 5 秒執行 1 次 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?")) .build(); // 初始化參數傳遞到 job job.getJobDataMap().put("myDescription", "Hello Quartz"); job.getJobDataMap().put("myValue", 1990); List<String> list = new ArrayList<>(); list.add("firstItem"); job.getJobDataMap().put("myArray", list); // 把做業和觸發器註冊到任務調度中 sched.scheduleJob(job, trigger); // 啓動計劃程序(實際上直到調度器已經啓動纔會開始運行) sched.start(); // 等待 10 秒,使咱們的 job 有機會執行 Thread.sleep(10000); // 等待做業執行完成時才關閉調度器 sched.shutdown(true); } }
運行結果(設置了 sleep 10 秒,故在 0 秒調度一次,5 秒調度一次, 10 秒調度最後一次):
---> Instance = job1, group = group1, description = Hello Quartz, value =1990, array item[0] = firstItem Runtime: Wed Apr 19 11:24:15 CST 2017 <--- ---> Instance = job1, group = group1, description = Hello Quartz, value =1990, array item[0] = firstItem Runtime: Wed Apr 19 11:24:20 CST 2017 <--- ---> Instance = job1, group = group1, description = Hello Quartz, value =1990, array item[0] = firstItem Runtime: Wed Apr 19 11:24:25 CST 2017 <---
格式:[秒] [分] [時] [每個月的第幾日] [月] [每週的第幾日] [年]
字段名 | 必須的 | 容許值 | 容許的特殊字符 |
---|---|---|---|
Seconds | YES | 0-59 | , - * / |
Minutes | YES | 0-59 | , - * / |
Hours | YES | 0-23 | , - * / |
Day of month | YES | 1-31 | , - * ? / L W |
Month | YES | 1-12 or JAN-DEC | , - * / |
Day of week | YES | 1-7 or SUN-SAT | , - * ? / L # |
Year | NO | empty, 1970-2099 | , - * / |
特殊字符說明:
字符 | 含義 |
---|---|
* |
用於 指定字段中的全部值 。好比:* 在分鐘中表示 每一分鐘 。 |
? |
用於 指定日期中的某一天 ,或是 星期中的某一個星期 。 |
- |
用於 指定範圍 。好比:10-12 在小時中表示 10 點,11 點,12 點 。 |
, |
用於 指定額外的值 。好比:MON,WED,FRI 在日期中表示 星期一, 星期三, 星期五 。 |
/ |
用於 指定增量 。好比:0/15 在秒中表示 0 秒, 15 秒, 30 秒, 45 秒 。5/15 在秒中表示 5 秒,20 秒,35 秒,50 秒 。 |
L |
在兩個字段中擁有不一樣的含義。好比:L 在日期(Day of month)表示 某月的最後一天 。在星期(Day of week)只表示 7 或 SAT 。可是,值L 在星期(Day of week)中表示 某月的最後一個星期幾 。 好比:6L 表示 某月的最後一個星期五 。也能夠在日期(Day of month)中指定一個偏移量(從該月的最後一天開始).好比:L-3 表示 某月的倒數第三天 。 |
W |
用於指定工做日(星期一到星期五)好比:15W 在日期中表示 到 15 號的最近一個工做日 。若是第十五號是週六, 那麼觸發器的觸發在 第十四號星期五 。若是第十五號是星期日,觸發器的觸發在 第十六號週一 。若是第十五是星期二,那麼它就會工做開始在 第十五號週二 。然而,若是指定 1W 而且第一號是星期六,那麼觸發器的觸發在第三號週一,由於它不會 "jump" 過一個月的日子的邊界。 |
L 和 W |
能夠在日期(day-of-month)合使用,表示 月份的最後一個工做日 。 |
# |
用於 指定月份中的第幾天 。好比:6#3 表示 月份的第三個星期五 (day 6 = Friday and "#3" = the 3rd one in the month)。其它的有,2#1 表示 月份第一個星期一 。4#5 表示 月份第五個星期三 。注意: 若是隻是指定 #5 ,則觸發器在月份中不會觸發。 |
注意:字符不區分大小寫,MON
和 mon
相同。
表達式 | 含義 |
---|---|
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 分的每一分鐘 |
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 點 5 分的每一分鐘 |
0 10,44 14 ? 3 WED |
3 月每週三的下午 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 L-2 * ? |
每個月最後兩天的上午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 分 |
0 0 12 1/5 * ? |
每個月的 1 號開始每隔 5 天的中午 12 點 |
0 11 11 11 11 ? |
每一年 11 月 11 號上午 11 點 11 分 |
監聽器在運行時將其註冊到調度程序中,而且必須給出一個名稱(或者,他們必須經過他們的 getName()
來宣傳本身的名稱)。
偵聽器與調度程序的 ListenerManager 一塊兒註冊,而且描述了監聽器想要接收事件的做業/觸發器的 Matcher。
1)註冊對特定做業的 JobListener:
sched.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("job1", "group1")));
2)註冊對特定組的全部做業的 JobListener:
sched.getListenerManager().addJobListener(new MyJobListener(), GroupMatcher.jobGroupEquals("group1"));
3)註冊對兩個特定組的全部做業的 JobListener:
sched.getListenerManager().addJobListener(new MyJobListener(), OrMatcher.or(GroupMatcher.jobGroupEquals("group1"), GroupMatcher.jobGroupEquals("group2")));
4)註冊一個對全部做業的 JobListener:
sched.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
JobListener 實現類:
package org.quartz.examples; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener; import org.quartz.SchedulerException; public class MyJobListener implements JobListener { @Override public String getName() { return "MyJobListener"; // 必定要設置名稱 } @Override public void jobToBeExecuted(JobExecutionContext jobExecutionContext) { } @Override public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) { } @Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { if (jobException != null) { try { // 當即關閉調度器 context.getScheduler().shutdown(); System.out.println("Error occurs when executing jobs, shut down the scheduler."); // 給管理員發送郵件... } catch (SchedulerException e) { e.printStackTrace(); } } } }
Job 實現類:
package org.quartz.examples; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.KeyMatcher; import java.util.ArrayList; import java.util.Date; import java.util.List; public class QuartzTest implements Job { /** * Quartz requires a public empty constructor so that the * scheduler can instantiate the class whenever it needs. */ public QuartzTest() { } /** * 該方法實現須要執行的任務 */ @SuppressWarnings("unchecked") @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 故意運行異常,觀察監聽器是否正常工做 int i = 1/0; // 從 context 中獲取 instName, groupName 以及 dataMap String instName = context.getJobDetail().getKey().getName(); String groupName = context.getJobDetail().getKey().getGroup(); JobDataMap dataMap = context.getJobDetail().getJobDataMap(); // 從 dataMap 中獲取 myDescription, myValue 以及 myArray String myDescription = dataMap.getString("myDescription"); int myValue = dataMap.getInt("myValue"); List<String> myArray = (List<String>) dataMap.get("myArray"); System.out.println("---> Instance = " + instName + ", group = " + groupName + ", description = " + myDescription + ", value =" + myValue + ", array item[0] = " + myArray.get(0)); System.out.println("Runtime: " + new Date().toString() + " <---"); } public static void main(String[] args) throws SchedulerException, InterruptedException { // 經過 schedulerFactory 獲取一個調度器 SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); // 建立 jobDetail 實例,綁定 Job 實現類 // 指明 job 的名稱,所在組的名稱,以及綁定 job 類 JobDetail job = JobBuilder.newJob(QuartzTest.class).withIdentity("job1", "group1").build(); // 定義調度觸發規則 // SimpleTrigger,從當前時間的下 1 秒開始,每隔 1 秒執行 1 次,重複執行 2 次 /*Trigger trigger = TriggerBuilder.newTrigger() // 指明 trigger 的 name 和 group .withIdentity("trigger1", "group1") // 從當前時間的下 1 秒開始執行,默認爲當即開始執行(.startNow()) .startAt(DateBuilder.evenSecondDate(new Date())) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1) // 每隔 1 秒執行 1 次 .withRepeatCount(2)) // 重複執行 2 次,一共執行 3 次 .build();*/ // corn 表達式,先當即執行 1 次,而後每隔 5 秒執行 1 次 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?")) .build(); // 初始化參數傳遞到 job job.getJobDataMap().put("myDescription", "Hello Quartz"); job.getJobDataMap().put("myValue", 1990); List<String> list = new ArrayList<>(); list.add("firstItem"); job.getJobDataMap().put("myArray", list); // 註冊對特定做業的監聽器 sched.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("job1", "group1"))); // 把做業和觸發器註冊到任務調度中 sched.scheduleJob(job, trigger); // 啓動計劃程序(實際上直到調度器已經啓動纔會開始運行) sched.start(); // 等待 10 秒,使咱們的 job 有機會執行 Thread.sleep(10000); // 等待做業執行完成時才關閉調度器 sched.shutdown(true); } }
運行結果:
[ERROR] 19 四月 11:54:35.361 上午 DefaultQuartzScheduler_Worker-1 [org.quartz.core.JobRunShell] Job group1.job1 threw an unhandled Exception: java.lang.ArithmeticException: / by zero at org.quartz.examples.QuartzTest.execute(QuartzTest.java:27) at org.quartz.core.JobRunShell.run(JobRunShell.java:202) at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [ERROR] 19 四月 11:54:35.361 上午 DefaultQuartzScheduler_Worker-1 [org.quartz.core.ErrorLogger] Job (group1.job1 threw an exception. org.quartz.SchedulerException: Job threw an unhandled exception. [See nested exception: java.lang.ArithmeticException: / by zero] at org.quartz.core.JobRunShell.run(JobRunShell.java:213) at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) Caused by: java.lang.ArithmeticException: / by zero at org.quartz.examples.QuartzTest.execute(QuartzTest.java:27) at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ... 1 more Error occurs when executing jobs, shut down the scheduler.
...註冊 TriggerListener 的工做原理相同。
SchedulerListener 在調度程序的 SchedulerListener 中註冊。SchedulerListener 幾乎能夠實現任何實現 org.quartz.SchedulerListener
接口的對象。
註冊對添加調度器時的 SchedulerListener:
scheduler.getListenerManager().addSchedulerListener(mySchedListener);
註冊對刪除調度器時的 SchedulerListener
:
scheduler.getListenerManager().removeSchedulerListener(mySchedListener);
PS:本文針對的 Quartz 版本爲 Quartz 2.2.3。官方下載地址:Quartz 2.2.3 .tar.gz