一、什么是Quartz
Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,完全由Java開發,可以用來執行定時任務,類似於java.util.Timer。但是相較於Timer, Quartz增加了很多功能:
持久性作業 - 就是保持調度定時的狀態;
作業管理 - 對調度作業進行有效的管理;
大部分公司都會用到定時任務這個功能。
拿火車票購票來說,當你下單后,后台就會插入一條待支付的task(job),一般是30分鍾,超過30min后就會執行這個job,去判斷你是否支付,未支付就會取消此次訂單;當你支付完成之后,后台拿到支付回調后就會再插入一條待消費的task(job),Job觸發日期為火車票上的出發日期,超過這個時間就會執行這個job,判斷是否使用等。
在我們實際的項目中,當Job過多的時候,肯定不能人工去操作,這時候就需要一個任務調度框架,幫我們自動去執行這些程序。那么該如何實現這個功能呢?
(1)首先我們需要定義實現一個定時功能的接口,我們可以稱之為Task(或Job),如定時發送郵件的task(Job),重啟機器的task(Job),優惠券到期發送短信提醒的task(Job),實現接口如下:
(2)有了任務之后,還需要一個能夠實現觸發任務去執行的觸發器,觸發器Trigger最基本的功能是指定Job的執行時間,執行間隔,運行次數等。
(3)有了Job和Trigger后,怎么樣將兩者結合起來呢?即怎樣指定Trigger去執行指定的Job呢?這時需要一個Schedule,來負責這個功能的實現。
上面三個部分就是Quartz的基本組成部分:
- 調度器:Scheduler
- 任務:JobDetail
- 觸發器:Trigger,包括SimpleTrigger和CronTrigger
二、Quartz Demo搭建
下面來利用Quartz搭建一個最基本的Demo。
1、導入依賴的jar包:
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
2、新建一個能夠打印任意內容的Job:
/** * Created by wanggenshen * Date: on 2018/7/7 16:28. * Description: 打印任意內容 */ public class PrintWordsJob implements Job{ @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date()); System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100)); } }
3、創建Schedule,執行任務:
/** * Created by wanggenshen * Date: on 2018/7/7 16:31. * Description: XXX */ public class MyScheduler { public static void main(String[] args) throws SchedulerException, InterruptedException { // 1、創建調度器Scheduler SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); // 2、創建JobDetail實例,並與PrintWordsJob類綁定(Job執行內容) JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class) .withIdentity("job1", "group1").build(); // 3、構建Trigger實例,每隔1s執行一次 Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1") .startNow()//立即生效 .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1)//每隔1s執行一次 .repeatForever()).build();//一直執行 //4、執行 scheduler.scheduleJob(jobDetail, trigger); System.out.println("--------scheduler start ! ------------"); scheduler.start(); //睡眠 TimeUnit.MINUTES.sleep(1); scheduler.shutdown(); System.out.println("--------scheduler shutdown ! ------------"); } }
運行程序,可以看到程序每隔1s會打印出內容,且在一分鍾后結束:
三、Quartz核心詳解
下面就程序中出現的幾個參數,看一下Quartz框架中的幾個重要參數:
Job和JobDetail
JobExecutionContext
JobDataMap
Trigger、SimpleTrigger、CronTrigger
(1)Job和JobDetail
Job是Quartz中的一個接口,接口下只有execute方法,在這個方法中編寫業務邏輯。
接口中的源碼:
JobDetail用來綁定Job,為Job實例提供許多屬性:
name
group
jobClass
jobDataMap
JobDetail綁定指定的Job,每次Scheduler調度執行一個Job的時候,首先會拿到對應的Job,然后創建該Job實例,再去執行Job中的execute()的內容,任務執行結束后,關聯的Job對象實例會被釋放,且會被JVM GC清除。
為什么設計成JobDetail + Job,不直接使用Job
JobDetail定義的是任務數據,而真正的執行邏輯是在Job中。
這是因為任務是有可能並發執行,如果Scheduler直接使用Job,就會存在對同一個Job實例並發訪問的問題。而JobDetail & Job 方式,Sheduler每次執行,都會根據JobDetail創建一個新的Job實例,這樣就可以規避並發訪問的問題。
(2)JobExecutionContext
JobExecutionContext中包含了Quartz運行時的環境以及Job本身的詳細數據信息。
當Schedule調度執行一個Job的時候,就會將JobExecutionContext傳遞給該Job的execute()中,Job就可以通過JobExecutionContext對象獲取信息。
主要信息有:
(3)JobExecutionContext
JobDataMap實現了JDK的Map接口,可以以Key-Value的形式存儲數據。
JobDetail、Trigger都可以使用JobDataMap來設置一些參數或信息,
Job執行execute()方法的時候,JobExecutionContext可以獲取到JobExecutionContext中的信息:
如:
JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class).usingJobData("jobDetail1", "這個Job用來測試的").withIdentity("job1", "group1").build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1") .usingJobData("trigger1", "這是jobDetail1的trigger") .startNow()//立即生效 .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1)//每隔1s執行一次 .repeatForever()).build();//一直執行
Job執行的時候,可以獲取到這些參數信息:
@Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println(jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail1")); System.out.println(jobExecutionContext.getTrigger().getJobDataMap().get("trigger1")); String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date()); System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100)); }
(4)Trigger、SimpleTrigger、CronTrigger
- Trigger
Trigger是Quartz的觸發器,會去通知Scheduler何時去執行對應Job。
new Trigger().startAt():表示觸發器首次被觸發的時間; new Trigger().endAt():表示觸發器結束觸發的時間;
- SimpleTrigger
SimpleTrigger可以實現在一個指定時間段內執行一次作業任務或一個時間段內多次執行作業任務。
下面的程序就實現了程序運行5s后開始執行Job,執行Job 5s后結束執行:
Date startDate = new Date(); startDate.setTime(startDate.getTime() + 5000); Date endDate = new Date(); endDate.setTime(startDate.getTime() + 5000); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1") .usingJobData("trigger1", "這是jobDetail1的trigger") .startNow()//立即生效 .startAt(startDate) .endAt(endDate) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1)//每隔1s執行一次 .repeatForever()).build();//一直執行
- CronTrigger
CronTrigger功能非常強大,是基於日歷的作業調度,而SimpleTrigger是精准指定間隔,所以相比SimpleTrigger,CroTrigger更加常用。CroTrigger是基於Cron表達式的,先了解下Cron表達式:
由7個子表達式組成字符串的,格式如下:
[秒] [分] [小時] [日] [月] [周] [年]
Cron表達式的語法比較復雜,
如:* 30 10 ? * 1/5 *
表示(從后往前看)
[指定年份] 的[ 周一到周五][指定月][不指定日][上午10時][30分][指定秒]
又如:00 00 00 ? * 10,11,12 1#5 2018
表示2018年10、11、12月的第一周的星期五這一天的0時0分0秒去執行任務。
下面是給的一個例子:
可通過在線生成Cron表達式的工具:http://cron.qqe2.com/ 來生成自己想要的表達式。
下面的代碼就實現了每周一到周五上午10:30執行定時任務
/** * Created by wanggenshen * Date: on 2018/7/7 20:06. * Description: XXX */ public class MyScheduler2 { public static void main(String[] args) throws SchedulerException, InterruptedException { // 1、創建調度器Scheduler SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); // 2、創建JobDetail實例,並與PrintWordsJob類綁定(Job執行內容) JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class) .usingJobData("jobDetail1", "這個Job用來測試的") .withIdentity("job1", "group1").build(); // 3、構建Trigger實例,每隔1s執行一次 Date startDate = new Date(); startDate.setTime(startDate.getTime() + 5000); Date endDate = new Date(); endDate.setTime(startDate.getTime() + 5000); CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1") .usingJobData("trigger1", "這是jobDetail1的trigger") .startNow()//立即生效 .startAt(startDate) .endAt(endDate) .withSchedule(CronScheduleBuilder.cronSchedule("* 30 10 ? * 1/5 2018")) .build(); //4、執行 scheduler.scheduleJob(jobDetail, cronTrigger); System.out.println("--------scheduler start ! ------------"); scheduler.start(); System.out.println("--------scheduler shutdown ! ------------"); } }
原文:https://blog.csdn.net/noaman_wgs/article/details/80984873