quartz的核心接口如下:
接口 | 含義 |
Scheduler | scheduler的主要API接口 |
Job | 任務實現接口,期望調度器能夠執行 |
JobDetail | 用於定義Job實例 |
Trigger | 調度器基於特定時間來執行指定任務的組件 |
JobBuilder | 用於定義、創建JobDetail實例 |
TriggerBuilder | 用於定義、創建Trigger實例 |
1. Scheduler
一個調度器的生命周期為通過SchedulerFactory創建,直到執行其shutdown()方法。當Scheduler創建之后,可以進行增加、刪除及顯示任務Job與觸發器Trigger,並且執行其他的調度相關的操作,如暫停一個觸發器Trigger。需要注意的是,直到調用start()方法時,Scheduler才正式開始執行job和trigger。
StdSchedulerFactory用於創建Scheduler,其依賴於一系列的屬性來決定如何產生Scheduler。可以通過四種途徑向StdSchedulerFactory提供屬性配置信息。
1) 通過java.util.Properties實例提供

package org.ws.quartz.test2; import java.util.Properties; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SchedulerExample { private static Logger logger = LoggerFactory.getLogger(SchedulerExample.class); public static void main(String[] args) { // 創建工廠實例 StdSchedulerFactory factory = new StdSchedulerFactory(); // 創建配置工廠的屬性對象 Properties props = new Properties(); props.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool"); // 線程池定義 props.put("org.quartz.threadPool.threadCount", "10"); // 默認Scheduler的線程數 try { // 使用定義的屬性初始化工廠 factory.initialize(props); Scheduler scheduler = factory.getScheduler(); scheduler.start(); logger.info("scheudler started, metadata: "+scheduler.getMetaData()); } catch (SchedulerException e) { e.printStackTrace(); } } }
執行后的結果:

2017-07-09 15:15:17 [INFO]-[org.ws.quartz.test2.SchedulerExample] scheudler started, metadata: Quartz Scheduler (v2.2.1) 'QuartzScheduler' with instanceId 'NON_CLUSTERED' Scheduler class: 'org.quartz.impl.StdScheduler' - running locally. Running since: Sun Jul 09 15:15:17 CST 2017 Not currently in standby mode. Number of jobs executed: 0 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads. Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
可以看到對應的配置屬性已經生效。
通過Properties設置工廠屬性的缺點在用硬編碼,假如需要修改例子中線程數量,將不得不修改代碼,然后重新編譯。后面幾種方法可以解決硬編碼的問題。
2) 通過外部屬性文件提供
使用方法:
public void initialize(String filename) throws SchedulerException;
3) 通過含有屬性文件內容的java.io.InputStream提供
使用方法:
public void initialize(InputStream propertiesStream) throws SchedulerException;
4) quartz.properties配置文件【推薦】
如果調用無參的initialize方法,StdSchedulerFactory會試圖從quartz.properties的文件中加載。quartz.properties相關配置后續文章會介紹,注意quartz.properties的加載順序為:
a. 檢查System.getProperty("org.quartz.properties")中是否設置其他屬性文件名
b. 如果a未設置,則將會從當前工作目錄中加載quartz.properties配置文件
c. 如果b未找到,則試圖從系統的classpath中加載該配置文件。
Scheduler在生命周期中也可執行其他操作,如查詢、設置standby模式、繼續執行、停止執行。standby模式會導致Scheduler暫時停止查找Job去執行。standby模式的設置直接使用scheudler.standby()即可。
Scheduler的停止方法為shutdown()方法,也可以使用有參shutdown(false),其中參數表示是否讓當前正在進行的job正常執行完成才停止Scheduler。
2. Job
Job即為為你執行一個任務的Java類。該任務可以是java編碼的任何功能,如使用JavaMail發送郵件、創建遠程接口並調用EJB上的方法等。
Java類僅需要實現org.quartz.job接口,將所需要實現的功能放在其execute方法中。execute方法的定義如下:
public void execute(JobExecutionContext context) throws JobExecutionException;
其中JobExecutionContext對象讓Job能訪問Quartz運行時環境的所有信息和Job本身的明細數據。運行時環境信息包括注冊到Scheduler上與該Job相關聯的JobDetail和Trigger。
例:Quartz使用(1) - 初識quartz示例中HelloWordJob獲取運行環境時信息如下:

package org.ws.quartz.test1; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HelloWorldJob implements Job{ private static Logger logger = LoggerFactory.getLogger(HelloWorldJob.class); @Override public void execute(JobExecutionContext context) throws JobExecutionException { logger.info("Hello World"); // 每一個Job都有其自己所屬的JobDetail JobDetail jobDetail = context.getJobDetail(); // JobDetail的名稱和組名 logger.info("Name and Group: "+jobDetail.getKey()); // 獲取Scheduler Scheduler scheduler = context.getScheduler(); try { logger.info("Scheduler name: "+scheduler.getSchedulerName()); } catch (SchedulerException e) { e.printStackTrace(); } logger.info("job class: "+jobDetail.getJobClass()); // 任務執行的時間 logger.info("Job fired at "+context.getFireTime()); // 任務下一次執行的時間 logger.info("Job nexe fire time: "+context.getNextFireTime()); } }
運行結果如下:

2017-07-09 16:00:59 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] init scheduler componets 2017-07-09 16:00:59 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] execute scheduler 2017-07-09 16:00:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Hello World 2017-07-09 16:00:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Name and Group: HelloWorld_Group.HelloWorld_Job 2017-07-09 16:00:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Scheduler name: DefaultQuartzScheduler 2017-07-09 16:00:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] job class: class org.ws.quartz.test1.HelloWorldJob 2017-07-09 16:00:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job fired at Sun Jul 09 16:00:59 CST 2017 2017-07-09 16:00:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job nexe fire time: Sun Jul 09 16:01:09 CST 2017 2017-07-09 16:01:09 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Hello World 2017-07-09 16:01:09 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Name and Group: HelloWorld_Group.HelloWorld_Job 2017-07-09 16:01:09 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Scheduler name: DefaultQuartzScheduler 2017-07-09 16:01:09 [INFO]-[org.ws.quartz.test1.HelloWorldJob] job class: class org.ws.quartz.test1.HelloWorldJob 2017-07-09 16:01:09 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job fired at Sun Jul 09 16:01:09 CST 2017 2017-07-09 16:01:09 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job nexe fire time: Sun Jul 09 16:01:19 CST 2017 2017-07-09 16:01:19 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Hello World 2017-07-09 16:01:19 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Name and Group: HelloWorld_Group.HelloWorld_Job 2017-07-09 16:01:19 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Scheduler name: DefaultQuartzScheduler 2017-07-09 16:01:19 [INFO]-[org.ws.quartz.test1.HelloWorldJob] job class: class org.ws.quartz.test1.HelloWorldJob 2017-07-09 16:01:19 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job fired at Sun Jul 09 16:01:19 CST 2017 2017-07-09 16:01:19 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job nexe fire time: Sun Jul 09 16:01:29 CST 2017 2017-07-09 16:01:29 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Hello World 2017-07-09 16:01:29 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Name and Group: HelloWorld_Group.HelloWorld_Job 2017-07-09 16:01:29 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Scheduler name: DefaultQuartzScheduler 2017-07-09 16:01:29 [INFO]-[org.ws.quartz.test1.HelloWorldJob] job class: class org.ws.quartz.test1.HelloWorldJob 2017-07-09 16:01:29 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job fired at Sun Jul 09 16:01:29 CST 2017 2017-07-09 16:01:29 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job nexe fire time: Sun Jul 09 16:01:39 CST 2017 2017-07-09 16:01:39 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Hello World 2017-07-09 16:01:39 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Name and Group: HelloWorld_Group.HelloWorld_Job 2017-07-09 16:01:39 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Scheduler name: DefaultQuartzScheduler 2017-07-09 16:01:39 [INFO]-[org.ws.quartz.test1.HelloWorldJob] job class: class org.ws.quartz.test1.HelloWorldJob 2017-07-09 16:01:39 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job fired at Sun Jul 09 16:01:39 CST 2017 2017-07-09 16:01:39 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job nexe fire time: Sun Jul 09 16:01:49 CST 2017 2017-07-09 16:01:49 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Hello World 2017-07-09 16:01:49 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Name and Group: HelloWorld_Group.HelloWorld_Job 2017-07-09 16:01:49 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Scheduler name: DefaultQuartzScheduler 2017-07-09 16:01:49 [INFO]-[org.ws.quartz.test1.HelloWorldJob] job class: class org.ws.quartz.test1.HelloWorldJob 2017-07-09 16:01:49 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job fired at Sun Jul 09 16:01:49 CST 2017 2017-07-09 16:01:49 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job nexe fire time: Sun Jul 09 16:01:59 CST 2017 2017-07-09 16:01:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Hello World 2017-07-09 16:01:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Name and Group: HelloWorld_Group.HelloWorld_Job 2017-07-09 16:01:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Scheduler name: DefaultQuartzScheduler 2017-07-09 16:01:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] job class: class org.ws.quartz.test1.HelloWorldJob 2017-07-09 16:01:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job fired at Sun Jul 09 16:01:59 CST 2017 2017-07-09 16:01:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Job nexe fire time: Sun Jul 09 16:02:09 CST 2017 2017-07-09 16:01:59 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] shut down scheduler
3. JobDetail
3.1 Job簡述
JobDetail是作為Job實例進行定義的,注意部署在Scheduler上的每一個Job只創建一個JobDetail實例。且需要注意的是注冊到Scheduler上的不是Job對象,而是JobDetail實例。
Job 的實例要到該執行它們的時候才會實例化出來。每次 Job 被執行,一個新的 Job 實例會被創建。其中暗含的意思就是你的 Job 不必擔心線程安全性,因為同一時刻僅有一個線程去執行給定 Job 類的實例,甚至是並發執行同一 Job 也是如此。
可以使用JobDataMap來定義Job的狀態,JobDataMap中可以存入key-value對,這些數據可以在Job實現類中進行傳遞和訪問。這是向你的Job傳送配置信息的便捷方法。
Job 能通過 JobExecutionContext 對象訪問 JobDataMap
例:在Quartz使用(1) - 初識quartz示例中,可以在等待時間修改為20s, SimpleQuartzExample.createJobDetail方法修改為:

protected JobDetail createJobDetail(){ return JobBuilder.newJob(HelloWorldJob.class) // 待執行的任務 .withIdentity("HelloWorld_Job", "HelloWorld_Group") // 名稱與組名組成Scheduler中任務的唯一標識 .usingJobData("message", "welcom to study quartz") // 存儲Job的狀態信息 .build(); // 構建 }
同時HelloWorldJob中的execute方法修改如下:

public void execute(JobExecutionContext context) throws JobExecutionException { logger.info("Hello World, "+context.getJobDetail().getJobDataMap().get("message")); }
執行結果:

2017-07-09 16:17:51 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] init scheduler componets 2017-07-09 16:17:51 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] execute scheduler 2017-07-09 16:17:51 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Hello World, welcom to study quartz 2017-07-09 16:18:01 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Hello World, welcom to study quartz 2017-07-09 16:18:11 [INFO]-[org.ws.quartz.test1.HelloWorldJob] Hello World, welcom to study quartz 2017-07-09 16:18:11 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] shut down scheduler
3.2 有狀態Job和無狀態Job
有狀態的Job可以理解為多次Job調用期間可以持有一些狀態信息,這些狀態信息存儲在JobDataMap中,而默認的無狀態job每次調用時都會創建一個新的JobDataMap。
有狀態的Job示例:
調度主方法:

package org.ws.quartz.test1; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SimpleQuartzExample { private static Logger logger = LoggerFactory.getLogger(SimpleQuartzExample.class); public static void main(String[] args) throws SchedulerException, InterruptedException { SimpleQuartzExample exam = new SimpleQuartzExample(); logger.info("init scheduler componets"); // 創建任務 JobDetail jobDetail = exam.createJobDetail(); // 創建觸發器 Trigger trigger = exam.createTrigger(); // 創建調度器 Scheduler scheduler = exam.createScheduler(); // 構建調度任務 scheduler.scheduleJob(jobDetail, trigger); logger.info("execute scheduler"); // 開啟調度器 scheduler.start(); // 一分鍾后關閉調度器 Thread.sleep(20000); scheduler.shutdown(); logger.info("shut down scheduler"); } protected Scheduler createScheduler() throws SchedulerException{ return StdSchedulerFactory.getDefaultScheduler(); } protected JobDetail createJobDetail(){ return JobBuilder.newJob(HelloWorldJob.class) // 待執行的任務 .withIdentity("HelloWorld_Job", "HelloWorld_Group") // 名稱與組名組成Scheduler中任務的唯一標識 .usingJobData("count", 0) // 將count初始化為0 .build(); // 構建 } protected Trigger createTrigger(){ return TriggerBuilder.newTrigger() .withIdentity("HelloWorld_Trigger", "HelloWorld_Group") // 名稱與組名組成Scheduler中觸發器的唯一標識 .withSchedule( SimpleScheduleBuilder.simpleSchedule() // 創建SimpleTrigger .withIntervalInSeconds(10) // 10秒間隔 .repeatForever() // 重復循環 ).build(); // 構建 } }
HelloWorldJob方法:

package org.ws.quartz.test1; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @PersistJobDataAfterExecution public class HelloWorldJob implements Job{ private static Logger logger = LoggerFactory.getLogger(HelloWorldJob.class); @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); int count = jobDataMap.getInt("count"); logger.info("count: "+count); ++count; jobDataMap.put("count", count); } }
執行結果:

2017-07-09 16:38:55 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] init scheduler componets 2017-07-09 16:38:55 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] execute scheduler 2017-07-09 16:38:55 [INFO]-[org.ws.quartz.test1.HelloWorldJob] count: 0 2017-07-09 16:39:05 [INFO]-[org.ws.quartz.test1.HelloWorldJob] count: 1 2017-07-09 16:39:15 [INFO]-[org.ws.quartz.test1.HelloWorldJob] count: 2 2017-07-09 16:39:15 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] shut down scheduler
如果不增加@PersistJobDataAfterExecution注解,運行結果為:

2017-07-09 16:40:58 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] init scheduler componets 2017-07-09 16:40:59 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] execute scheduler 2017-07-09 16:40:59 [INFO]-[org.ws.quartz.test1.HelloWorldJob] count: 0 2017-07-09 16:41:08 [INFO]-[org.ws.quartz.test1.HelloWorldJob] count: 0 2017-07-09 16:41:18 [INFO]-[org.ws.quartz.test1.HelloWorldJob] count: 0 2017-07-09 16:41:19 [INFO]-[org.ws.quartz.test1.SimpleQuartzExample] shut down scheduler
可見 @PersistJobDataAfterExecution的作用在於持久化保存在JobDataMap中的傳遞參數,使得多次執行Job,可以獲取傳遞參數的狀態信息。
3.3 @DisallowConcurrentExecution
quartz中另一個常用的注解為@DisallowConcurrentExecution,該注解可以同一個時刻,同一個任務只能執行一次,不能並行執行兩個或多個同一任務。但需要注意的是,多個不同的任務是可以同時執行的。