1. 介紹
Quartz體系結構:
明白Quartz怎么用,首先要了解Scheduler(調度器)、Job(任務)和Trigger(觸發器)這3個核心的概念。
1. Job: 是一個接口,只定義一個方法execute(JobExecutionContext context),在實現接口的execute方法中編寫所需要定時執行的Job(任務), JobExecutionContext類提供了調度應用的一些信息。Job運行時的信息保存在JobDataMap實例中;
2. JobDetail: Quartz每次調度Job時, 都重新創建一個Job實例, 所以它不直接接受一個Job的實例,相反它接收一個Job實現類(JobDetail:描述Job的實現類及其它相關的靜態信息,如Job名字、描述、關聯監聽器等信息),以便運行時通過newInstance()的反射機制實例化Job。
3. Trigger: 是一個類,描述觸發Job執行的時間觸發規則。主要有SimpleTrigger和CronTrigger這兩個子類。當且僅當需調度一次或者以固定時間間隔周期執行調度,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表達式定義出各種復雜時間規則的調度方案:如工作日周一到周五的15:00~16:00執行調度等;
Cron表達式的格式:秒 分 時 日 月 周 年(可選)。
字段名 允許的值 允許的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小時 0-23 , - * /
日 1-31 , - * ? / L W C
月 1-12 or JAN-DEC , - * /
周幾 1-7 or SUN-SAT , - * ? / L C # SUN, MON, TUE, WED, THU, FRI and SAT
年 (可選字段) empty, 1970-2099 , - * /
“?”字符:表示不確定的值
“,”字符:指定數個值
“-”字符:指定一個值的范圍
“/”字符:指定一個值的增加幅度。n/m表示從n開始,每次增加m
“L”字符:用在日表示一個月中的最后一天,用在周表示該月最后一個星期X
“W”字符:指定離給定日期最近的工作日(周一到周五)
“#”字符:表示該月第幾個周X。6#3表示該月第3個周五
Cron表達式范例:
每隔5秒執行一次:*/5 * * * * ?
每隔1分鍾執行一次:0 */1 * * * ?
每天23點執行一次:0 0 23 * * ?
每天凌晨1點執行一次:0 0 1 * * ?
每月1號凌晨1點執行一次:0 0 1 1 * ?
每月最后一天23點執行一次:0 0 23 L * ?
每周星期六凌晨1點實行一次:0 0 1 ? * L
在26分、29分、33分執行一次:0 26,29,33 * * * ?
每天的0點、13點、18點、21點都執行一次:0 0 0,13,18,21 * * ?
4. 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分別針對每年、每月和每周進行定義;
5. 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實例;
6. 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表示對應有狀態的任務:
2. 創建簡單的定時任務
使用Quartz需要導入jar包:
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.3</version> </dependency>
2.1創建工作類
創建一個工作類,需要實現Job接口
public class HelloQuartz implements Job { //執行 public void execute(JobExecutionContext context) throws JobExecutionException { //創建工作詳情 JobDetail detail=context.getJobDetail(); //獲取工作的名稱 String name=detail.getJobDataMap().getString("name"); String job=detail.getKey().getGroup(); System.out.println("任務調度:組:"+job+",工作名:"+name+"---->今日整點搶購,不容錯過!"); } }
2.2測試任務調度
public class QuartzTest { public static void main(String[] args) { try{ //創建scheduler,執行計划 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); //定義一個Trigger,觸發條件類 Trigger trigger = TriggerBuilder.newTrigger(). withIdentity("trigger1", "group1") //定義name/group .startNow()//一旦加入scheduler,立即生效 .withSchedule(SimpleScheduleBuilder.simpleSchedule() //使用SimpleTrigger .withIntervalInSeconds(1) //每隔一秒執行一次 .repeatForever()) //一直執行,奔騰到老不停歇 .build(); //定義一個JobDetail JobDetail job = JobBuilder.newJob(HelloQuartz.class) //定義Job類為HelloQuartz類,這是真正的執行邏輯所在 .withIdentity("job1", "group1") //定義name/group .usingJobData("name", "quartz") //定義屬性 .build(); //加入這個調度 scheduler.scheduleJob(job, trigger); //啟動任務調度 scheduler.start(); }catch (Exception ex){ ex.printStackTrace(); } } }
3. 動態操作任務
3.1新建任務類
public class MyJob implements Job { private static int counter = 1; public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("(第 " + counter + " 次,預告通知)"); counter++; } }
3.2編寫任務工具類
public class QuartzManager { private static SchedulerFactory schedulerFactory = new StdSchedulerFactory(); /** * @Description: 添加一個定時任務 * * @param jobName 任務名 * @param jobGroupName 任務組名 * @param triggerName 觸發器名 * @param triggerGroupName 觸發器組名 * @param jobClass 任務 * @param cron 時間設置,參考quartz說明文檔 */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass, String cron) { try { Scheduler sched = schedulerFactory.getScheduler(); // 任務名,任務組,任務執行類 JobDetail jobDetail= JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build(); // 觸發器 TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger(); // 觸發器名,觸發器組 triggerBuilder.withIdentity(triggerName, triggerGroupName); triggerBuilder.startNow(); // 觸發器時間設定 triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron)); // 創建Trigger對象 CronTrigger trigger = (CronTrigger) triggerBuilder.build(); // 調度容器設置JobDetail和Trigger sched.scheduleJob(jobDetail, trigger); // 啟動 if (!sched.isShutdown()) { sched.start(); } } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description: 修改一個任務的觸發時間 * * @param jobName * @param jobGroupName * @param triggerName 觸發器名 * @param triggerGroupName 觸發器組名 * @param cron 時間設置,參考quartz說明文檔 */ public static void modifyJobTime(String jobName, String jobGroupName, String triggerName, String triggerGroupName, String cron) { try { Scheduler sched = schedulerFactory.getScheduler(); TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName); CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey); if (trigger == null) { return; } String oldTime = trigger.getCronExpression(); if (!oldTime.equalsIgnoreCase(cron)) { /** 方式一 :調用 rescheduleJob 開始 */ // 觸發器 TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger(); // 觸發器名,觸發器組 triggerBuilder.withIdentity(triggerName, triggerGroupName); triggerBuilder.startNow(); // 觸發器時間設定 triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron)); // 創建Trigger對象 trigger = (CronTrigger) triggerBuilder.build(); // 方式一 :修改一個任務的觸發時間 sched.rescheduleJob(triggerKey, trigger); /** 方式一 :調用 rescheduleJob 結束 */ /** 方式二:先刪除,然后在創建一個新的Job */ //JobDetail jobDetail = sched.getJobDetail(JobKey.jobKey(jobName, jobGroupName)); //Class<? extends Job> jobClass = jobDetail.getJobClass(); //removeJob(jobName, jobGroupName, triggerName, triggerGroupName); //addJob(jobName, jobGroupName, triggerName, triggerGroupName, jobClass, cron); } } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description: 移除一個任務 * * @param jobName * @param jobGroupName * @param triggerName * @param triggerGroupName */ public static void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) { try { Scheduler sched = schedulerFactory.getScheduler(); TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName); sched.pauseTrigger(triggerKey);// 停止觸發器 sched.unscheduleJob(triggerKey);// 移除觸發器 sched.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 刪除任務 } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description:啟動所有定時任務 */ public static void startJobs() { try { Scheduler sched = schedulerFactory.getScheduler(); sched.start(); } catch (Exception e) { throw new RuntimeException(e); } } /** * @Description:關閉所有定時任務 */ public static void shutdownJobs() { try { Scheduler sched = schedulerFactory.getScheduler(); if (!sched.isShutdown()) { sched.shutdown(); } } catch (Exception e) { throw new RuntimeException(e); } } }
3.3測試類
public class SimpleTest { public static String JOB_NAME = "動態任務調度"; public static String TRIGGER_NAME = "動態任務觸發器"; public static String JOB_GROUP_NAME = "XLXXCC_JOB_GROUP"; public static String TRIGGER_GROUP_NAME = "XLXXCC_JOB_GROUP"; public static void main(String[] args) { try { System.out.println("【系統啟動】開始(每1秒輸出一次)..."); QuartzManager.addJob(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME, MyJob.class, "0/1 * * * * ?"); Thread.sleep(5000); System.out.println("【修改時間】開始(每5秒輸出一次)..."); QuartzManager.modifyJobTime(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME, "0/5 * * * * ?"); Thread.sleep(6000); System.out.println("【移除定時】開始..."); QuartzManager.removeJob(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME); System.out.println("【移除定時】成功"); } catch (Exception e) { e.printStackTrace(); } } }
4. Spring整合Quartz
導入Spring核心包:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.11.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.3.11.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.11.RELEASE</version> </dependency>
4.1 創建工作類
public class MyJob implements Job { private static int counter = 1; public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("(第 " + counter + " 次,預告通知)"); counter++; } }
4.2Spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- Spring整合Quartz進行配置遵循下面的步驟: 1:定義工作任務的Job 2:定義觸發器Trigger,並將觸發器與工作任務綁定 3:定義調度器,並將Trigger注冊到Scheduler --> <!-- 1:定義任務的bean ,這里使用JobDetailFactoryBean,也可以使用MethodInvokingJobDetailFactoryBean ,配置類似--> <bean name="myJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <!-- 指定job的名稱 --> <property name="name" value="job1"/> <!-- 指定job的分組 --> <property name="group" value="group1"/> <!-- 指定具體的job類 --> <property name="jobClass" value="spring.MyJob"/> <!-- 必須設置為true,如果為false,當沒有活動的觸發器與之關聯時會在調度器中會刪除該任務 --> <property name="durability" value="true"/> <!-- 指定spring容器的key,如果不設定在job中的jobmap中是獲取不到spring容器的 --> <property name="applicationContextJobDataKey" value="applicationContext"/> </bean> <!-- 2.2:定義觸發器的bean,定義一個Cron的Trigger,一個觸發器只能和一個任務進行綁定 --> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <!-- 指定Trigger的名稱 --> <property name="name" value="my_trigger"/> <!-- 指定Trigger的名稱 --> <property name="group" value="my_trigger_group"/> <!-- 指定Tirgger綁定的Job --> <property name="jobDetail" ref="myJob"/> <!-- 指定Cron 的表達式 ,當前是每隔5s運行一次 --> <property name="cronExpression" value="0/5 * * * * ?" /> </bean> <!-- 3.定義調度器,並將Trigger注冊到調度器中 --> <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger"/> </list> </property> <property name="autoStartup" value="false" /> </bean> </beans>
4.3測試代碼
public class SpringTest { public static void main(String[] args) throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-quartz.xml"); StdScheduler scheduler = (StdScheduler) applicationContext.getBean("scheduler"); scheduler.start(); System.in.read(); } }