http://blog.csdn.net/zixiao217/article/details/53053598
首先給一個簡明扼要的理解: Scheduler 調度程序-任務執行計划表,只有安排進執行計划的任務Job(通過scheduler.scheduleJob方法安排進執行計划),當它預先定義的執行時間到了的時候(任務觸發trigger),該任務才會執行。
在上一節中我們的示例中,我們預先安排了一個定時任務:該任務只做一件事,就是打印任務執行時間以及匯報任務已經執行。我們的任務類實現了org.quartz.Job這個接口:public class HelloJob implements Job
,才會被安排成定是可執行任務。這一節,我們就詳細了解一下Quartz中編程的幾個重要接口。
(本文章分享在CSDN平台,更多精彩請閱讀 東陸之滇的csdn博客:http://blog.csdn.net/zixiao217)
Quartz編程API幾個重要接口
- Scheduler - 用於與調度程序交互的主程序接口。
- Job - 我們預先定義的希望在未來時間能被調度程序執行的任務類,如上一節的HelloJob類。
- JobDetail - 使用JobDetail來定義定時任務的實例。
- Trigger - 觸發器,表明任務在什么時候會執行。定義了一個已經被安排的任務將會在什么時候執行的時間條件,比如上一節的實例的每2秒就執行一次。
- JobBuilder -用於聲明一個任務實例,也可以定義關於該任務的詳情比如任務名、組名等,這個聲明的實例將會作為一個實際執行的任務。
- TriggerBuilder - 觸發器創建器,用於創建觸發器trigger實例。
Scheduler調度程序、SchedulerFactory調度程序工廠
Scheduler調度程序
org.quartz.Scheduler這是Quartz 調度程序的主要接口。
Scheduler維護了一個JobDetails 和Triggers的注冊表。一旦在Scheduler注冊過了,當定時任務觸發時間一到,調度程序就會負責執行預先定義的Job。
調度程序Scheduler實例是通過SchedulerFactory工廠來創建的。一個已經創建的scheduler ,可以通過同一個工廠實例來獲取它。 調度程序創建之后,它只是出於”待機”狀態,必須在任務執行前調用scheduler的start()方法啟用調度程序。你還可以使用shutdown()方法關閉調度程序,使用isShutdown()方法判斷該調度程序是否已經處於關閉狀態。通過Scheduler的scheduleJob(…)方法的幾個重載方法將任務納入調度程序中。在上一節中我們使用的是scheduleJob(JobDetail jobDetail, Trigger trigger)
方法將我們預先定義的定時任務安排進調度計划中。任務安排之后,你就可以調用start()方法啟動調度程序了,當任務觸發時間到了的時候,該任務將被執行。
SchedulerFactory調度程序工廠
SchedulerFactory有兩個默認的實現類:DirectSchedulerFactory和StdSchedulerFactory。
DirectSchedulerFactory
DirectSchedulerFactory是一個org.quartz.SchedulerFactory的單例實現。
這里有一些使用DirectSchedulerFactory的示例代碼段:
示例1:你可以使用createVolatileScheduler方法去創建一個不需要寫入數據庫的調度程序實例:
//創建一個擁有10個線程的調度程序 DirectSchedulerFactory.getInstance().createVolatileScheduler(10); //記得啟用該調度程序 DirectSchedulerFactory.getInstance().getScheduler().start();
為方便起見,提供了幾種創建方法。所有創建方法最終會最終會使用所有參數的來創建調度程序:
public void createScheduler(String schedulerName, String schedulerInstanceId, ThreadPool threadPool, JobStore jobStore, String rmiRegistryHost, int rmiRegistryPort)
示例2:
// 創建線程池
SimpleThreadPool threadPool = new SimpleThreadPool(maxThreads, Thread.NORM_PRIORITY); threadPool.initialize(); // 創建job存儲器 JobStore jobStore = new RAMJobStore(); //使用所有參數創建調度程序 DirectSchedulerFactory.getInstance().createScheduler("My Quartz Scheduler", "My Instance", threadPool, jobStore, "localhost", 1099); // 不要忘了調用start()方法來啟動調度程序 DirectSchedulerFactory.getInstance().getScheduler("My Quartz Scheduler", "My Instance").start();
你也可使用JDBCJobStore,形如:
DBConnectionManager.getInstance().addConnectionProvider("someDatasource", new JNDIConnectionProvider("someDatasourceJNDIName")); JobStoreTX jdbcJobStore = new JobStoreTX(); jdbcJobStore.setDataSource("someDatasource"); jdbcJobStore.setPostgresStyleBlobs(true); jdbcJobStore.setTablePrefix("QRTZ_"); jdbcJobStore.setInstanceId("My Instance");
StdSchedulerFactory
StdSchedulerFactory是org.quartz.SchedulerFactory的實現類,它是基於Quartz屬性文件創建Quartz Scheduler 調度程序的。我們在上一節實例中使用的就是StdSchedulerFactory,因為我們指定了屬性文件quartz.properties。
默認情況下是加載當前工作目錄下的”quartz.properties”屬性文件。如果加載失敗,會去加載org/quartz包下的”quartz.properties”屬性文件。我們使用JD-GUI反編譯工具打開quartz.jar,可以在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

如果你不想使用默認的文件名,你可以指定org.quartz.properties
屬性指向你的屬性配置文件。要不然,你可以在調用getScheduler()方法之前調用initialize(xx)方法初始化工廠配置。
屬性配置文件中,還可以引用其他配置文件的信息,你可以使用$@來引用:
quartz1.properties
org.quartz.scheduler.instanceName=HelloScheduler
quartz2.properties
org.quartz.scheduler.instanceName=$@org.quartz.scheduler.instanceName
參照以下StdSchedulerFactory的屬性配置,實際使用中你自己可以指定一些符合需求的參數,例如指定存儲器可以配置org.quartz.jobStore.class
的值。
PROPERTIES_FILE = "org.quartz.properties"; PROP_SCHED_INSTANCE_NAME = "org.quartz.scheduler.instanceName"; PROP_SCHED_INSTANCE_ID = "org.quartz.scheduler.instanceId"; PROP_SCHED_INSTANCE_ID_GENERATOR_CLASS = "org.quartz.scheduler.instanceIdGenerator.class"; PROP_SCHED_THREAD_NAME = "org.quartz.scheduler.threadName"; PROP_SCHED_SKIP_UPDATE_CHECK = "org.quartz.scheduler.skipUpdateCheck"; PROP_SCHED_BATCH_TIME_WINDOW = "org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow"; PROP_SCHED_MAX_BATCH_SIZE = "org.quartz.scheduler.batchTriggerAcquisitionMaxCount"; PROP_SCHED_JMX_EXPORT = "org.quartz.scheduler.jmx.export"; PROP_SCHED_JMX_OBJECT_NAME = "org.quartz.scheduler.jmx.objectName"; PROP_SCHED_JMX_PROXY = "org.quartz.scheduler.jmx.proxy"; PROP_SCHED_JMX_PROXY_CLASS = "org.quartz.scheduler.jmx.proxy.class"; PROP_SCHED_RMI_EXPORT = "org.quartz.scheduler.rmi.export"; PROP_SCHED_RMI_PROXY = "org.quartz.scheduler.rmi.proxy"; PROP_SCHED_RMI_HOST = "org.quartz.scheduler.rmi.registryHost"; PROP_SCHED_RMI_PORT = "org.quartz.scheduler.rmi.registryPort"; PROP_SCHED_RMI_SERVER_PORT = "org.quartz.scheduler.rmi.serverPort"; PROP_SCHED_RMI_CREATE_REGISTRY = "org.quartz.scheduler.rmi.createRegistry"; PROP_SCHED_RMI_BIND_NAME = "org.quartz.scheduler.rmi.bindName"; PROP_SCHED_WRAP_JOB_IN_USER_TX = "org.quartz.scheduler.wrapJobExecutionInUserTransaction"; PROP_SCHED_USER_TX_URL = "org.quartz.scheduler.userTransactionURL"; PROP_SCHED_IDLE_WAIT_TIME = "org.quartz.scheduler.idleWaitTime"; PROP_SCHED_DB_FAILURE_RETRY_INTERVAL = "org.quartz.scheduler.dbFailureRetryInterval"; PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON = "org.quartz.scheduler.makeSchedulerThreadDaemon"; PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD = "org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer"; PROP_SCHED_CLASS_LOAD_HELPER_CLASS = "org.quartz.scheduler.classLoadHelper.class"; PROP_SCHED_JOB_FACTORY_CLASS = "org.quartz.scheduler.jobFactory.class"; PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN = "org.quartz.scheduler.interruptJobsOnShutdown"; PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN_WITH_WAIT = "org.quartz.scheduler.interruptJobsOnShutdownWithWait"; PROP_THREAD_POOL_CLASS = "org.quartz.threadPool.class"; PROP_JOB_STORE_CLASS = "org.quartz.jobStore.class"; PROP_JOB_STORE_USE_PROP = "org.quartz.jobStore.useProperties"; PROP_CONNECTION_PROVIDER_CLASS = "connectionProvider.class";
Job定時任務實例類
一個任務是一個實現org.quartz.Job接口的類,任務類必須含有空構造器,它只有一個簡單的方法:
void execute(JobExecutionContext context) throws JobExecutionException;
當關聯這個任務實例的觸發器表明的執行時間到了的時候,調度程序Scheduler 會調用這個方法來執行任務,我們的任務內容就可以在這個方法中執行。
public class HelloJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("現在是北京時間:" + DateUtil.getCurrDateTime() + " - helloJob任務執行"); } }
在該方法退出之前,會設置一個結果對象到JobExecutionContext 中。盡管這個結果對Quartz來說沒什么意義,但是JobListeners或者TriggerListeners 來說,是可以監聽查看job的執行情況的。后面會詳細講解監聽器的內容。
JobDataMap提供了一種”初始化成員屬性數據的機制”,在實現該Job接口的時候可能會用到。
Job實例化的過程
可能很多人對於一個Job實例的組成以及創建過程感到迷惑,筆者曾經也是如此,所以現在請耐心理解。
你可以創建一個Job類,在調度程序(任務計划表)中創建很多JobDetai可以存儲很多初始化定義信息——每一個都可以設置自己的屬性和JobDataMap——將他們全部添加到調度程序中去。
這里舉個例子說明一下,你可以創建一個任務類實現Job接口,不妨稱之為”SalesReportJob”,我們用它做銷售報表使用。我們可以通過JobDataMap指定銷售員的名稱和銷售報表的依據等等。這就會創建多個JobDetails了,例如”SalesReportForJoe”,”SalesReportForMike”分別對應在JobDataMap中指定的名字”joe”和”mike”。
重要:當觸發器的執行時間到了的時候,會加載與之關聯的JobDetail,並在調度程序Scheduler中通過JobFactory的配置實例化它引用的Job。JobFactory 調用newInstance()創建一個任務實例,然后調用setter 方法設置在JobDataMap定義好的名字。你可以實現JobFactory,比如使用IOC或DI機制初始化的任務實例。
Job的聲明和並發
關於Job的聲明和並發需要說明一下,以下一對注解使用在你的Job類中,可以影響Quartz的行為:
@DisallowConcurrentExecution : 可以添加到你的任務類中,它會告訴Quartz不要執行多個任務實例。
注意措辭,在上面的”SalesReportJob”類添加該注解,將會只有一個”SalesReportForJoe”實例在給定的時間執行,但是”SalesReportForMike”是可以執行的。這個約束是基於JobDetail的,而不是基於任務類的。
@PersistJobDataAfterExecution : 告訴Quartz在任務執行成功完畢之后(沒有拋出異常),修改JobDetail的JobDataMap備份,以供下一個任務使用。
如果你使用了@PersistJobDataAfterExecution 注解的話,強烈建議同時使用@DisallowConcurrentExecution注解,以避免當兩個同樣的job並發執行的時候產生的存儲數據迷惑。
Job的其他一些屬性
- 持久化 - 如果一個任務不是持久化的,則當沒有觸發器關聯它的時候,Quartz會從scheduler中刪除它。
- 請求恢復 - 如果一個任務請求恢復,一般是該任務執行期間發生了系統崩潰或者其他關閉進程的操作,當服務再次啟動的時候,會再次執行該任務。這種情況下,JobExecutionContext.isRecovering()會返回true。
JobDetail定義任務實例的一些屬性特征
org.quartz.JobDetail
接口負責傳輸給定的任務實例的屬性到Scheduler。JobDetail是通過JobBuilder創建的。
Quartz不會存儲一個真實的Job類實例,但是允許你通過JobDetail定義一個任務實例——JobDetail是用來定義任務實例的。
任務Job有一個名稱name 和組group 來關聯。在一個Scheduler中這二者的組合必須是唯一的。
觸發器任務計划執行表的執行”機制”。多個觸發器可以指向同一個工作,但一個觸發器只能指向一個工作。
JobDataMap任務數據映射
JobDataMap用來保存任務實例的狀態信息。
當一個Job被添加到調度程序(任務執行計划表)scheduler的時候,JobDataMap實例就會存儲一次關於該任務的狀態信息數據。也可以使用@PersistJobDataAfterExecution注解標明在一個任務執行完畢之后就存儲一次。
JobDataMap實例也可以村粗一個觸發器trigger。這是非常有用的,特別是當你的任務被多個觸發器引用的時候,根據不同的觸發時機,你可以提供不同的輸入條件。
JobExecutionContext 也可以再執行時包含一個方便的JobDataMap ,它合並了觸發器的 JobDataMap (如果有的話)和Job的 JobDataMap (如果有的話)。
這里我們改一下上一節的程序作為示例,在定義JobDetail的時候,將一些數據放入JobDataMap 中:
// 定義一個job,並且綁定HelloJob類
JobDetail job = newJob(HelloJob.class) .withIdentity("job1", "group1") .usingJobData("jobSays", "Hello World!") .usingJobData("myFloatValue", 3.141f) .build();
然后在任務執行的時候,可以獲取JobDataMap 中的數據:
package org.byron4j.quartz; import org.byron4j.utils.DateUtil; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; /** * 實現org.quartz.Job接口,聲明該類是一個可執行任務類 * * @author Administrator * */ public class HelloJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("現在是北京時間:" + DateUtil.getCurrDateTime() + " - helloJob任務執行"); JobKey key = context.getJobDetail().getKey(); JobDataMap dataMap = context.getJobDetail().getJobDataMap(); String jobSays = dataMap.getString("jobSays"); float myFloatValue = dataMap.getFloat("myFloatValue"); System.err.println("Instance " + key + " of HelloJob says: " + jobSays + ", and val is: " + myFloatValue); } }
控制台輸出:
scheduleName = MyScheduler
現在是北京時間:2016-11-06 16:24:57 - helloJob任務執行 Instance group1.job1 of HelloJob says: Hello World!, and val is: 3.141 現在是北京時間:2016-11-06 16:24:59 - helloJob任務執行 Instance group1.job1 of HelloJob says: Hello World!, and val is: 3.141 現在是北京時間:2016-11-06 16:25:01 - helloJob任務執行 Instance group1.job1 of HelloJob says: Hello World!, and val is: 3.141
我們在觸發器也添加數據:
JobDetail job = newJob(HelloJob.class) .withIdentity("job1", "group1") .usingJobData("jobSays", "Hello World!") .usingJobData("myFloatValue", 3.141f) .build(); // 聲明一個觸發器,現在就執行(schedule.start()方法開始調用的時候執行);並且每間隔2秒就執行一次 Trigger trigger = newTrigger() .withIdentity("trigger1", "group1") .usingJobData("trigger_key", "每2秒執行一次") .startNow() .withSchedule(simpleSchedule() .withIntervalInSeconds(2) .repeatForever()) .build();
HelloJob的execute方法改成如下:
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey key = context.getJobDetail().getKey(); //使用歸並的JobDataMap JobDataMap dataMap = context.getMergedJobDataMap(); String jobSays = dataMap.getString("jobSays"); float myFloatValue = dataMap.getFloat("myFloatValue"); String triggerSays = dataMap.getString("trigger_key"); System.err.println("Instance " + key + " of HelloJob says: " + jobSays + ", and val is: " + myFloatValue + ";trigger says:" + triggerSays); }
控制台輸出如下,得到了job、trigger的JobDataMap 的數據:
scheduleName = MyScheduler
Instance group1.job1 of HelloJob says: Hello World!, and val is: 3.141;trigger says:每2秒執行一次 Instance group1.job1 of HelloJob says: Hello World!, and val is: 3.141;trigger says:每2秒執行一次
Trigger觸發器
Trigger觸發器,可以理解為安排了一個任務,這個任務是在每年9月10日早上9點向你敬愛的老師發送一天祝福短信,觸發器就是指每年9月10日早上9點觸發執行這個任務。
觸發器使用TriggerBuilder來實例化。
觸發器有一個TriggerKey
關聯,這在一個Scheduler中必須是唯一的。
觸發器任務計划執行表的執行”機制”。多個觸發器可以指向同一個工作,但一個觸發器只能指向一個工作。
觸發器可以傳送數據給job——通過將數據放進觸發器的JobDataMap。
觸發器常用屬性
觸發器也有很多屬性,這些屬性都是在使用TriggerBuilder 定義觸發器時設置的。
- TriggerKey - 唯一標識觸發器,這在一個Scheduler中必須是唯一的
- “startTime” - 開始時間,通常使用startAt(java.util.Date)
- “endTime” - 結束時間,設置了結束時間則在這之后,不再觸發
觸發器的優先級
有時候,你會安排很多任務,但是Quartz並沒有更多的資源去處理它。這種情況下,你必須需要很好地控制哪個任務先執行了。這時候你可以使用設置priority 屬性(使用方法withPriority(int))來控制觸發器的優先級。
注意:優先級只有觸發器出發時間一樣的時候才有意義。
注意:當一個任務請求恢復執行時,它的優先級和原始優先級是一樣的。
JobBuilder用於創建JobDetail;TriggerBuilder 用於創建觸發器Trigger
JobBuilder用於創建JobDetail。總是把保持在有效狀態,合理的使用默認設置在你調用build() 方法的時候。如果你沒有調用withIdentity(..)指定job的名字,它會自動給你生成一個。
TriggerBuilder 用於創建觸發器Trigger。如果你沒有調用withSchedule(..) 方法,會使用默認的schedule 。如果沒有使用withIdentity(..)會自動生成一個觸發器名稱給你。
Quartz通過一種領域特定語言(DSL)提供了一種自己的builder的風格API來創建任務調度相關的實體。DSL可以通過對類的靜態方法的使用來調用:TriggerBuilder, JobBuilder, DateBuilder, JobKey, TriggerKey 以及其它的關於Schedule創建的實現。
客戶端可以使用類似示例使用DSL:
/*靜態引入builder*/ import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; JobDetail job = newJob(MyJob.class) .withIdentity("myJob") .build(); Trigger trigger = newTrigger() .withIdentity(triggerKey("myTrigger", "myTriggerGroup")) .withSchedule(simpleSchedule() .withIntervalInHours(1) .repeatForever()) .startAt(futureDate(10, MINUTES)) .build(); scheduler.scheduleJob(job, trigger);
總結:Scheduler—job—trigger
我們以一個現實生活中的例子為例:
Scheduler就是定時任務執行計划表,目前共有兩個job安排進了執行計划:元旦放假不上班,春節放假團圓在家,這些都是ZF預先定義好的執行計划。”元旦放假不上班”、”春節放假團圓在家”是兩個job,第一個job的觸發時間是每年1月1日(觸發器1),第二個job是每年的農歷初一(觸發器2)。