1. 程式人生 > >Quartz中Trigger理解和使用

Quartz中Trigger理解和使用

(一)、Trigger觸發器

  Job中包含了任務執行的邏輯,Scheduler負責掃描需要執行的Job任務,那麼Scheduler如何知道何時執行這個Job任務呢?接下來就需要觸發器上場了。

  觸發器(org.quartz.Trigger)抽象類的幾個主要屬性和JobDetail差不多,這裡就不說明了,主要注意的是下面表格中的屬性和misfireInstruction這個屬性,misfireInstruction這個屬性的是觸發器錯失執行(misfire)後的一個錯失觸發機制標識。當執行緒池中沒有可用的執行緒執行任務時,就會錯過觸發時間,Trigger抽象類中預設錯失觸發機制是常量0(聰明的策略)。派生類有它們自己的錯失觸發機制。

下面對觸發器的子類分別進行講述,我們主要看Trigger的4個可用的派生類(注:共5個,UICronTrigger1.5版本後已廢棄),分別是:

  • org.quartz.SimpleTrigger
  • org.quartz.CronTrigger (常用)
  • org.quartz.DateIntervalTrigger
  • org.quartz.NthIncludedDayTrigger

以上派生類的幾個主要屬性:

屬性名 詳細介紹
startTime 首次觸發時間
endTime 截止時間,就算指定的執行次數沒有執行完也立即結束
nextFireTime 下一次觸發時間
previousFireTime 上一次觸發時間
repeatCount (不提供get/set)重複執行次數(不包含首次執行)
repeatInterval (不提供get/set) 重複執行間隔,單位為毫秒
timesTriggered (不提供get/set) 總觸發次數

瞭解了觸發器的幾個關鍵屬性,接下來通過程式碼來看看幾個觸發器具體的使用。
下面這個Job列印了觸發器的幾種時間,示例程式碼中的幾種觸發器都使用到了這個Job。


package com.xk.quartz.test;

import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Trigger;

/**
 * 演示job
 */
public class DemoJob implements Job{

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 獲取觸發器
        Trigger trigger = context.getTrigger();
        
        // 獲取幾種時間
        Date startTime = trigger.getStartTime();
        Date endTime = trigger.getEndTime();
        Date previousFireTime = trigger.getPreviousFireTime();
        Date nextFireTime = trigger.getNextFireTime();
        
        System.out.println("首次執行時間:" + startTime.toLocaleString());
        System.out.println("最後截止時間:" + (endTime != null ? endTime.toLocaleString() : null));
        System.out.println("上一次執行時間:" + previousFireTime.toLocaleString());
        System.out.println("下一次執行時間:" + (nextFireTime != null ? nextFireTime.toLocaleString() : null));
        System.out.println("*******************************");
    }
}

接下來對每個觸發器進行分別演示。

(1)、org.quartz.SimpleTrigger

  SimpleTrigger是一種設定和使用簡單的觸發器,它是在指定日期/時間且可能需要重複執行n次的時機下使用的。這種觸發器不適合每天定時執行任務這種場景,舉個栗子,假設指定任務首次觸發時間是中午12點整,間隔時間為1天,那麼理論上來講以後的每一天中午12點整都會準時執行,但是,只要出現了一次misfire,假設錯失執行後等待可用執行緒的時間為1分鐘,即此任務會在12點01分被恢復觸發,那麼觸發器記錄的此次執行時間就是12點01分鐘,又因為間隔時間設定的是1天,所以下一次執行時間也會被推算到第二天的12點01分。

為了方便演示,下面使用的Quartz框架沒有持久化配置,僅使用RAMJobStore(存放在記憶體中),這樣的好處是可以使用預設配置並且直接main方法啟動。

package com.xk.quartz.test;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;

/**
 * SimpleTrigger演示
 */
public class SimpleTriggerTest {

    public static void main(String[] args) throws SchedulerException {
        // 獲取預設排程器  內部載入順序在排程器章節提到過
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        // 啟動排程器
        scheduler.start();
        
        // 建立job詳情
        JobDetail jobDetail = new JobDetail("demoJob演示", DemoJob.class);
        
        // 以下是觸發器的不同構造
        // 立即執行
        SimpleTrigger simpleTrigger = new SimpleTrigger("simpleTrigger演示");
        
        // 立即執行
        // SimpleTrigger simpleTrigger = new SimpleTrigger("simpleTrigger演示", new Date());
        
        // 當前立即執行,指定重複次數3次,間隔500毫秒
        // 總執行次數為4次(首次執行+3次重複執行)
        // SimpleTrigger simpleTrigger = new SimpleTrigger("simpleTrigger演示", 3, 500);
        
        // 當前時間2秒後執行,當前時間5秒後結束,指定重複次數20次,間隔1000毫秒
        // 這裡雖然指定執行次數為20次,但是實際只能執行3次(首次執行+2次重複執行),因為有"當前時間5秒後結束"這個因素在
        // SimpleTrigger simpleTrigger = new SimpleTrigger("simpleTrigger演示", new Date(System.currentTimeMillis() + 2000), new Date(System.currentTimeMillis() + 5000), 20, 1000);
        
        // TODO
        // 只要明白上面幾種建立方式   其他沒有寫出的夠著就很容易明白
        
        // 註冊job和觸發器
        scheduler.scheduleJob(jobDetail, simpleTrigger);
    }
}

(2)、org.quartz.CronTrigger

  CronTrigger觸發器比SimpleTrigger觸發器強大很多,上面的程式碼可以看出,使用SimpleTrigger觸發器時,需要設定不同的屬性來支撐,程式碼編寫相對較多,而且不是很靈活。而接下來CronTrigger觸發器,可以設定複雜的觸發時間表,設定方式也很簡單,唯一的難點就是需要理解時間表達式如何編寫。
  為了更深刻的瞭解CronTrigger,我們需要了解一下Cron到底是什麼?
  Cron概念是來自UNIX(Linux也是基於UNIX的)作業系統的,它就是一個執行計劃任務的伺服器或程式,你只需要將你的計劃和執行時間交給它,它就會根據指定時間來排程工作任務。這裡不要混淆UNIX的Cron和Quartz的Cron,因為他們之間有一些很明顯的差別,UNIX cron 計劃僅支援至分鐘級,而Quartz支援到秒級的計劃。說這些就是讓大家瞭解下Quartz Cron的歷史。

下面先看一下幾個示例,會感覺也沒多難,很好理解:

package com.xk.quartz.test;

import java.text.ParseException;

import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;

/**
 * CronTrigger演示
 */
public class CronTriggerTest {

    public static void main(String[] args) throws SchedulerException {
        // 獲取預設排程器  內部載入順序在排程器章節提到過
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        // 啟動排程器
        scheduler.start();
        
        // 建立job詳情
        JobDetail jobDetail = new JobDetail("demoJob演示", DemoJob.class);
        
        // 建立觸發器  下面只演示一種觸發器構造   其他的構造也很好理解
        CronTrigger cronTrigger = null;
        try {
            // 每秒執行一次兩種寫法
            cronTrigger = new CronTrigger("cronTrigger演示", "DEFAULT", "* * * * * ?");
            // cronTrigger = new CronTrigger("cronTrigger演示", "DEFAULT", "0/1 * * * * ?");
            
            // 每分鐘的秒鐘等於30時執行
            // cronTrigger = new CronTrigger("cronTrigger演示", "DEFAULT", "30 * * * * ?");
            
            // 每5分鐘的整分時執行
            // cronTrigger = new CronTrigger("cronTrigger演示", "DEFAULT", "0 0/5 * * * ?");
            
            // 每天的9點和18點整執行  (通知下班啦☺)
            // cronTrigger = new CronTrigger("cronTrigger演示", "DEFAULT", "0 0 9,18 * * ?");
            
            // 每個月最後一天的23點59分59秒執行  (做月統計時使用)
            // cronTrigger = new CronTrigger("cronTrigger演示", "DEFAULT", "59 59 23 L * ?");
            
        } catch (ParseException e) {
            e.printStackTrace();
        }
        
        // 註冊job和觸發器
        scheduler.scheduleJob(jobDetail, cronTrigger);
    }
}

下面列出Quartz Cron表示式格式的7個域:

名稱 是否必須 允許值 特殊字元
0-59 , - * /
0-59 , - * /
0-23 , - * /
1-31 , - * ? / L W C
1-12或 JAN-DEC , - * /
1-7 或 SUN-SAT , - * ? / L C #
空 或 1970-2099 , - * /

域之間通過空格進行分隔,年份不是必須項,月份和星期的名稱不區分大小寫,比如SUN和sun是一樣的。

  • 星號( * )表示該域上所有合法的值都會被觸發,如果使用在秒域上,就表示 每秒鐘
  • 問號( ? )表示不為該域指定值。?號只能用在日和周兩個域上,但不能兩個域同時使用,只要記住一點,在這兩個域的其中一個指定了值,那另外一個值就放一個?問號,這樣不會出現混亂
  • 逗號( , )表示該域上指定一個值列表,例如秒的域上使用值0,20,40 表示秒鐘等於0、20或40時觸發
  • 斜槓( / )表示時間表遞增,上面逗號舉例20秒觸發一次,也可以在秒域上寫成 0/20
  • 中劃線( - )表示取值範圍,例如時域上指定9-18,那麼表示上午9點到下午6點每小時觸發一次
  • 字母( L )表示合法範圍的最後一個值,只能在日和周兩個域上使用,用在日上時表示指定月份的最後一天;用在週上時表示周的最後一天,注意是數字7或者星期六(國外週日是星期的第一天),用在週上時還可以用一個數字與 L 連起來表示月份的最後一個星期 X。例如,表示式 0 0 0 ? * 1L表示在每個月的最後一個星期日觸發
  • 字母( W )表示指定日的最近的一個工作日(週一到週五),並且只能用在日域上。假如日欄位指定的是15W,那麼就表示該月15號最相近的工作日執行。如果15號是週六,那麼就是14號週五觸發;如果15號是週日,那麼就會在16號週一觸發;如果15號是週三,那就當天觸發
  • 井號( # )表示指定月份中第幾周的第幾天,只能用在周域上。例如值為1#2,表示某月第二週的星期日觸發,2表示第二週,1表示星期日。如果把2改為大於4的數字,比如5或6,那麼就不會被觸發,因為一個月中不會超過4周
  • 字母( C )表示計算需要依賴日曆(org.quartz.impl.calendar.CronCalendar)類的計算結果,只能用在日和周兩個域上。

下面是一些例子,可以照葫蘆畫瓢進行延伸擴充套件:

表示式 含義
0 0 12 * * ? 每天中午12點整觸發
0 0 12 ? * * 每天中午12點整觸發
0 0 12 * * ? 2018 2018年的每天中午12點整觸發
0 0/5 12 * * ? 每天12點開始到12點55分結束每5分鐘觸發
0 0-5 12 * * ? 每天12:00到12:05每分鐘觸發
0 0 12,13,14 * * ? 每天12點、13點、14點整點時各觸發
0 0 12 ? 6 WED 6月份的每個週三中午12點整觸發
0 0 12 L * ? 每個月最後一天12點整觸發
0 0 12 15W * ? 每個月15號最近的工作日(週一到週五)觸發一次
0 0 12 ? * 2#3 每個月第三週的星期一觸發

(3)、org.quartz.DateIntervalTrigger

  這是quartz1.7版本以後加入的觸發器,該觸發器適用於每小時,每幾周,每幾月重複執行的任務。
  舉個例子,假如任務需求是需要每5個月間隔時觸發一次,如果用Cron來實現,那表示式就是“0 0 12 * 0/5 ?“,啟動時會報一個解析異常ParseException: Month values must be between 1 and 12。雖然我們也可以使用SimpleTrigger觸發器,但是上面提到,SimpleTrigger當出現misfire時,下一次執行的時間也會向後延遲。而DateIntervalTrigger就不會有這些問題。使用DateIntervalTrigger時只要注意一個問題,如果使用的Unit單位為月,開始執行時間是1月31日,那麼第二次執行將會是2月28日(可能29日),第三次執行會是3月28日,以後都是28日,而不會出現31日了,這是需要注意點。如果想要實現每個月最後一天觸發,那就使用CronTrigger觸發器去實現(示例程式碼中有提到)。

上程式碼:


package com.xk.quartz.test;

import org.quartz.DateIntervalTrigger;
import org.quartz.DateIntervalTrigger.IntervalUnit;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;

/**
 * DateIntervalTrigger演示
 */
public class DateIntervalTriggerTest {

    public static void main(String[] args) throws SchedulerException {
        // 獲取預設排程器  內部載入順序在排程器章節提到過
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        // 啟動排程器
        scheduler.start();
        
        // 建立job詳情
        JobDetail jobDetail = new JobDetail("demoJob演示", DemoJob.class);
        
        // 建立觸發器   只演示一種構造方法  其他構造屬性和以上觸發器屬性類似
        // 每一個月觸發一次   IntervalUnit.MONTH是個列舉常量  1表示重複間隔
        DateIntervalTrigger dateIntervalTrigger = new DateIntervalTrigger("dateIntervalTrigger演示", IntervalUnit.MONTH, 1);
        
        // TODO
        // 下面就不一一列舉了 IntervalUnit的其他常量很容易理解,每個常量對應了一個日期域

        // 註冊job和觸發器
        scheduler.scheduleJob(jobDetail, dateIntervalTrigger);
    }
}

(4)、org.quartz.NthIncludedDayTrigger

NthIncludedDayTrigger適用於在每一個間隔型別(月或年等)的第N天觸發。直接上程式碼:

package com.xk.quartz.test;

import org.quartz.Calendar;
import org.quartz.JobDetail;
import org.quartz.NthIncludedDayTrigger;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;

/**
 * NthIncludedDayTrigger演示
 */
public class NthIncludedDayTriggerTest {

    public static void main(String[] args) throws SchedulerException {
        // 獲取預設排程器  內部載入順序在排程器章節提到過
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        // 啟動排程器
        scheduler.start();
        
        // 建立job詳情
        JobDetail jobDetail = new JobDetail("demoJob演示", DemoJob.class);
        
        // 建立觸發器  
        // 下面觸發器表示每個月的第10天中午12點整觸發
        NthIncludedDayTrigger nthIncludedDayTrigger = new NthIncludedDayTrigger("nthIncludedDayTrigger演示");
        nthIncludedDayTrigger.setN(10);
        nthIncludedDayTrigger.setIntervalType(NthIncludedDayTrigger.INTERVAL_TYPE_MONTHLY);
        nthIncludedDayTrigger.setFireAtTime("12:00:00");
        
        // 註冊job和觸發器
        scheduler.scheduleJob(jobDetail, nthIncludedDayTrigger);
    }
}

  四種觸發器的建立方式都演示了,大家平時需要考慮的就是哪些情況下使用哪種觸發器。這需要大家在開發的過程中自己進行研究和除錯。大家使用最多的是CronTrigger,通過Cron表示式幾乎可以完成工作業務中的絕大多數任務。

(二)、使用TriggerUtils工具類建立觸發器

  下面介紹一個常用的觸發器工具類(org.quartz.TriggerUtils)。可以很方便建立一些簡單的觸發器。上程式碼:

package com.xk.quartz.test;

import org.quartz.Trigger;
import org.quartz.TriggerUtils;

/**
 * TriggerUtils演示
 */
public class TriggerUtilsTest {

    public static void main(String[] args) {
        
        // 建構函式有很多  下面只列舉常用的幾種
        
        // 建立一個每2秒執行一次 重複執行5次的觸發器
        Trigger trigger = TriggerUtils.makeSecondlyTrigger("trigger", 2, 5);
        
        // 建立一個每5分鐘執行一次的觸發器
        Trigger trigger2 = TriggerUtils.makeMinutelyTrigger(5);
        trigger2.setName("trigger2");
        
        // 建立一個每2小時執行一次 重複執行5次的觸發器
        Trigger trigger3 = TriggerUtils.makeHourlyTrigger(2, 5);
        trigger3.setName("trigger3");
        
        // 建立一個每天中午12點10分執行的觸發器
        Trigger trigger4 = TriggerUtils.makeDailyTrigger("trigger4", 12, 10);
        
        // 建立一個每個月5號中午12點10執行的觸發器
        Trigger trigger5 = TriggerUtils.makeMonthlyTrigger("trigger5", 5, 12, 10);
        
        // 建立一個每週三中午12點整執行的觸發器
        Trigger trigger6 = TriggerUtils.makeWeeklyTrigger("trigger6", 4, 12, 0);
                
        // 建立一個立即觸發的觸發器
        Trigger trigger7 = TriggerUtils.makeImmediateTrigger("trigger7", 0, 0);
    }
}

下面是工具類中的其他方法,瞭解一下含義:

  • computeFireTimes(Trigger trigg, org.quartz.Calendar cal, int numTimes)
    返回下次執行的日期列表
  • computeFireTimesBetween(Trigger trigg, org.quartz.Calendar cal, Date from, Date to)
    返回指定時間段內下次執行的日期列表
  • computeEndTimeToAllowParticularNumberOfFirings(Trigger trigg, org.quartz.Calendar cal, int numTimes)
    返回第n次觸發後的第1秒時間
  • getDateOf(int second, int minute, int hour)
    返回指定時分秒的一個今天的日期物件,此方法有包含日,月,年引數過載方法
  • getEvenHourDate(Date date)
    返回一個舍入到下一個小時以上的日期。比如:入參 09:35:12 返回10:00:00
    此方法也有多個過載。