入門簡介:
基本上任何公司都會用到調度這個功能, 比如我們公司需要定期執行調度生成報表, 或者比如博客什么的定時更新之類的,都可以靠Quartz來完成。正如官網所說,小到獨立應用大到大型電子商務網站, Quartz都能勝任。
Quartz體系結構:
明白Quartz怎么用,首先要了解Scheduler(調度器)、Job(任務)和Trigger(觸發器)這3個核心的概念。
Job: 是一個接口,只定義一個方法execute(JobExecutionContext context),在實現接口的execute方法中編寫所需要定時執行的Job(任務), JobExecutionContext類提供了調度應用的一些信息。Job運行時的信息保存在JobDataMap實例中;
JobDetail: **Quartz每次調度Job時, 都重新創建一個Job實例, 所以它不直接接受一個Job的實例,相反它接收一個Job實現類(JobDetail:描述Job的實現類及其它相關的靜態信息,如Job名字、描述、關聯監聽器等信息),以便運行時通過newInstance()的反射機制實例化Job。
Trigger: **是一個類,描述觸發Job執行的時間觸發規則。主要有SimpleTrigger和CronTrigger這兩個子類。當且僅當需調度一次或者以固定時間間隔周期執行調度,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表達式定義出各種復雜時間規則的調度方案:如工作日周一到周五的15:00~16:00執行調度等;
具體cron語法參考這篇文章http://www.lxweimin.com/p/9027d067ac5b
Calendar:**org.quartz.Calendar和java.util.Calendar不同, 它是一些日歷特定時間點的集合(可以簡單地將org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一個日歷時間點,無特殊說明后面的Calendar即指org.quartz.Calendar)。 一個Trigger可以和多個Calendar關聯, 以便排除或包含某些時間點。假設,我們安排每周星期一早上10:00執行任務,但是如果碰到法定的節日,任務則不執行,這時就需要在Trigger觸發機制的基礎上使用Calendar進行定點排除。針對不同時間段類型,Quartz在org.quartz.impl.calendar包下提供了若干個Calendar的實現類,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分別針對每年、每月和每周進行定義;
Scheduler: **代表一個Quartz的獨立運行容器, Trigger和JobDetail可以注冊到Scheduler中, 兩者在Scheduler中擁有各自的組及名稱, 組及名稱是Scheduler查找定位容器中某一對象的依據, Trigger的組及名稱必須唯一, JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同,因為它們是不同類型的)。Scheduler定義了多個接口方法, 允許外部通過組及名稱訪問和控制容器中Trigger和JobDetail。Scheduler可以將Trigger綁定到某一JobDetail中, 這樣當Trigger觸發時, 對應的Job就被執行。一個Job可以對應多個Trigger, 但一個Trigger只能對應一個Job??梢酝ㄟ^SchedulerFactory創建一個Scheduler實例。Scheduler擁有一個SchedulerContext,它類似于ServletContext,保存著Scheduler上下文信息,Job和Trigger都可以訪問SchedulerContext內的信息。SchedulerContext內部通過一個Map,以鍵值對的方式維護這些上下文數據,SchedulerContext為保存和獲取數據提供了多個put()和getXxx()的方法。可以通過Scheduler# getContext()獲取對應的SchedulerContext實例;
ThreadPool: **Scheduler使用一個線程池作為任務運行的基礎設施,任務通過共享線程池中的線程提高運行效率。Job有一個StatefulJob子接口,代表有狀態的任務,該接口是一個沒有方法的標簽接口,其目的是讓Quartz知道任務的類型,以便采用不同的執行方案。無狀態任務在執行時擁有自己的JobDataMap拷貝,對JobDataMap的更改不會影響下次的執行。而有狀態任務共享共享同一個JobDataMap實例,每次任務執行對JobDataMap所做的更改會保存下來,后面的執行可以看到這個更改,也即每次執行任務后都會對后面的執行發生影響。正因為這個原因,無狀態的Job可以并發執行,而有狀態的StatefulJob不能并發執行,這意味著如果前次的StatefulJob還沒有執行完畢,下一次的任務將阻塞等待,直到前次任務執行完畢。有狀態任務比無狀態任務需要考慮更多的因素,程序往往擁有更高的復雜度,因此除非必要,應該盡量使用無狀態的Job。如果Quartz使用了數據庫持久化任務調度信息,無狀態的JobDataMap僅會在Scheduler注冊任務時保持一次,而有狀態任務對應的JobDataMap在每次執行任務后都會進行保存。Trigger自身也可以擁有一個JobDataMap,其關聯的Job可以通過JobExecutionContext#getTrigger().getJobDataMap()獲取Trigger中的JobDataMap。不管是有狀態還是無狀態的任務,在任務執行期間對Trigger的JobDataMap所做的更改都不會進行持久,也即不會對下次的執行產生影響。Quartz擁有完善的事件和監聽體系,大部分組件都擁有事件,如任務執行前事件、任務執行后事件、觸發器觸發前事件、觸發后事件、調度器開始事件、關閉事件等等,可以注冊相應的監聽器處理感興趣的事件。
下圖描述了Scheduler的內部組件結構,SchedulerContext提供Scheduler全局可見的上下文信息,每一個任務都對應一個JobDataMap,虛線表達的JobDataMap表示對應有狀態的任務:
廢話不多說, 上代碼:
- 最簡單的Job代碼(就打印Hello Quartz !):
package com.ruixunyun.www.quartz;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class HelloQuartz implements Job {
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println("Hello Quartz !");
}
}
- 設置觸發器
package com.ruixunyun.www.quartz;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzStartListener {
public static void main(String[] args) throws InterruptedException {
//通過schedulerFactory獲取一個調度器
SchedulerFactory schedulerfactory = new StdSchedulerFactory();
Scheduler scheduler=null;
try{
// 通過schedulerFactory獲取一個調度器
scheduler = schedulerfactory.getScheduler();
// 創建jobDetail實例,綁定Job實現類
// 指明job的名稱,所在組的名稱,以及綁定job類
JobDetail job = JobBuilder.newJob(HelloQuartz.class).withIdentity("JobName", "JobGroupName").build();
// 定義調度觸發規則
// SimpleTrigger
// Trigger trigger=TriggerBuilder.newTrigger().withIdentity("SimpleTrigger", "SimpleTriggerGroup")
// .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(3).withRepeatCount(6))
// .startNow().build();
// corn表達式 每五秒執行一次
Trigger trigger=TriggerBuilder.newTrigger().withIdentity("CronTrigger1", "CronTriggerGroup")
.withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))
.startNow().build();
// 把作業和觸發器注冊到任務調度中
scheduler.scheduleJob(job, trigger);
// 啟動調度
scheduler.start();
Thread.sleep(10000);
// 停止調度
scheduler.shutdown();
}catch(SchedulerException e){
e.printStackTrace();
}
}
}
輸出(設置了sleep10秒, 故在0秒調度一次, 5秒一次, 10秒最后一次):Spring中使用Quartz
maven中加入quartz的jar包
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
- web.xml中添加監聽器,監聽QuartzStartListener.class
備注:放到spring中, SchedulerTest.class去掉了main方法,并且繼承了ServletContextListener類,實現了他的兩個方法:contextInitialized , contextDestroyed
package com.ruixunyun.www.quartz;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class QuartzStartListener implements ServletContextListener{
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//通過schedulerFactory獲取一個調度器
SchedulerFactory schedulerfactory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
// 通過schedulerFactory獲取一個調度器
scheduler = schedulerfactory.getScheduler();
// 創建jobDetail實例,綁定Job實現類
// 指明job的名稱,所在組的名稱,以及綁定job類
JobDetail job = JobBuilder.newJob(HelloQuartz.class).withIdentity("JobName", "JobGroupName").build();
// 定義調度觸發規則 corn表達式 一分鐘執行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("CronTrigger1", "CronTriggerGroup")
.withSchedule(CronScheduleBuilder.cronSchedule("0 */1 * * * ?"))
.startNow().build();
// 把作業和觸發器注冊到任務調度中
scheduler.scheduleJob(job, trigger);
// 啟動調度
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
web.xml的監聽配置
我的
<listener>
<listener-class>com.ruixunyun.www.quartz.QuartzStartListener</listener-class>
</listener>