Quartz 2 定時任務(一):基本使用指南

版權聲明:本文由吳仙傑創做整理,轉載請註明出處:http://www.javashuo.com/article/p-xsencvmg-y.htmlhtml

1. Quartz 體系結構

Quartz 設計有三個核心類,分別是 Scheduler(調度器)Job(任務)和 Trigger (觸發器),它們是咱們使用 Quartz 的關鍵。java

1)Job:定義須要執行的任務。該類是一個接口,只定義一個方法 execute(JobExecutionContext context),在實現類的 execute 方法中編寫所須要定時執行的 Job(任務), JobExecutionContext 類提供了調度應用的一些信息。Job 運行時的信息保存在 JobDataMap 實例中。shell

2)Trigger:負責設置調度策略。該類是一個接口,描述觸發 job 執行的時間觸發規則。主要有 SimpleTrigger 和 CronTrigger 這兩個子類。當且僅當需調度一次或者以固定時間間隔週期執行調度,SimpleTrigger 是最適合的選擇;而 CronTrigger 則能夠經過 Cron 表達式定義出各類複雜時間規則的調度方案:如工做日週一到週五的 15:00~16:00 執行調度等。segmentfault

3)Scheduler:調度器就至關於一個容器,裝載着任務和觸發器。該類是一個接口,表明一個 Quartz 的獨立運行容器, Trigger 和 JobDetail 能夠註冊到 Scheduler 中, 二者在 Scheduler 中擁有各自的組及名稱, 組及名稱是 Scheduler 查找定位容器中某一對象的依據, Trigger 的組及名稱必須惟一, JobDetail 的組和名稱也必須惟一(但能夠和 Trigger 的組和名稱相同,由於它們是不一樣類型的)。Scheduler 定義了多個接口方法, 容許外部經過組及名稱訪問和控制容器中 Trigger 和 JobDetail。併發

Scheduler 能夠將 Trigger 綁定到某一 JobDetail 中, 這樣當 Trigger 觸發時, 對應的 Job 就被執行。一個 Job 能夠對應多個 Trigger, 但一個 Trigger 只能對應一個 Job。能夠經過 SchedulerFactory 建立一個 SchedulerFactory 實例。Scheduler 擁有一個 SchedulerContext,它相似於 SchedulerContext,保存着 Scheduler 上下文信息,Job 和 Trigger 均可以訪問 SchedulerContext 內的信息。SchedulerContext 內部經過一個 Map,以鍵值對的方式維護這些上下文數據,SchedulerContext 爲保存和獲取數據提供了多個 put()getXxx() 的方法。能夠經過 Scheduler#getContext() 獲取對應的 SchedulerContext 實例。ide

4)JobDetail:描述 Job 的實現類及其它相關的靜態信息,如:Job 名字、描述、關聯監聽器等信息。Quartz 每次調度 Job 時, 都從新建立一個 Job 實例, 因此它不直接接受一個 Job 的實例,相反它接收一個 Job 實現類,以便運行時經過 newInstance() 的反射機制實例化 Job。ui

5)ThreadPool:Scheduler 使用一個線程池做爲任務運行的基礎設施,任務經過共享線程池中的線程提升運行效率。.net

Job 有一個 StatefulJob 子接口(Quartz 2 後用 @PersistJobDataAfterExecution 註解代替),表明有狀態的任務,該接口是一個沒有方法的標籤接口,其目的是讓 Quartz 知道任務的類型,以便採用不一樣的執行方案。線程

  • 無狀態任務在執行時擁有本身的 JobDataMap 拷貝,對 JobDataMap 的更改不會影響下次的執行。設計

  • 有狀態任務共享同一個 JobDataMap 實例,每次任務執行對 JobDataMap 所作的更改會保存下來,後面的執行能夠看到這個更改,也即每次執行任務後都會對後面的執行發生影響。

正由於這個緣由,無狀態的 Job 併發執行,而有狀態的 StatefulJob 不能併發執行。這意味着若是前次的 StatefulJob 尚未執行完畢,下一次的任務將阻塞等待,直到前次任務執行完畢。有狀態任務比無狀態任務須要考慮更多的因素,程序每每擁有更高的複雜度,所以除非必要,應該儘可能使用無狀態的 Job。

6)Listener:Quartz 擁有完善的事件和監聽體系,大部分組件都擁有事件,如:JobListener 監放任務執行前事件、任務執行後事件;TriggerListener 監聽觸發器觸發前事件、觸發後事件;TriggerListener 監聽調度器開始事件、關閉事件等等,能夠註冊相應的監聽器處理感興趣的事件。

2. 調度示例

使用 Quartz 進行任務調度:

package org.quartz.examples;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class QuartzTest implements Job {

    /**
     * Quartz requires a public empty constructor so that the
     * scheduler can instantiate the class whenever it needs.
     */
    public QuartzTest() {
    }

    /**
     * 該方法實現須要執行的任務
     */
    @SuppressWarnings("unchecked")
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 從 context 中獲取 instName, groupName 以及 dataMap
        String instName = context.getJobDetail().getKey().getName();
        String groupName = context.getJobDetail().getKey().getGroup();
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        // 從 dataMap 中獲取 myDescription, myValue 以及 myArray
        String myDescription = dataMap.getString("myDescription");
        int myValue = dataMap.getInt("myValue");
        List<String> myArray = (List<String>) dataMap.get("myArray");
        System.out.println("---> Instance = " + instName + ", group = " + groupName
                + ", description = " + myDescription + ", value =" + myValue
                + ", array item[0] = " + myArray.get(0));
        System.out.println("Runtime: " + new Date().toString() + " <---");
    }

    public static void main(String[] args) throws SchedulerException, InterruptedException {
        // 經過 schedulerFactory 獲取一個調度器
        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler sched = sf.getScheduler();

        // 建立 jobDetail 實例,綁定 Job 實現類
        // 指明 job 的名稱,所在組的名稱,以及綁定 job 類
        JobDetail job = JobBuilder.newJob(QuartzTest.class).withIdentity("job1", "group1").build();

        // 定義調度觸發規則

        // SimpleTrigger,從當前時間的下 1 秒開始,每隔 1 秒執行 1 次,重複執行 2 次
        /*Trigger trigger = TriggerBuilder.newTrigger()
                // 指明 trigger 的 name 和 group
                .withIdentity("trigger1", "group1")
                // 從當前時間的下 1 秒開始執行,默認爲當即開始執行(.startNow())
                .startAt(DateBuilder.evenSecondDate(new Date()))
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(1) // 每隔 1 秒執行 1 次
                        .withRepeatCount(2)) // 重複執行 2 次,一共執行 3 次
                .build();*/


        // corn 表達式,先當即執行一次,而後每隔 5 秒執行 1 次
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))
                .build();

        // 初始化參數傳遞到 job
        job.getJobDataMap().put("myDescription", "Hello Quartz");
        job.getJobDataMap().put("myValue", 1990);
        List<String> list = new ArrayList<>();
        list.add("firstItem");
        job.getJobDataMap().put("myArray", list);

        // 把做業和觸發器註冊到任務調度中
        sched.scheduleJob(job, trigger);

        // 啓動計劃程序(實際上直到調度器已經啓動纔會開始運行)
        sched.start();

        // 等待 10 秒,使咱們的 job 有機會執行
        Thread.sleep(10000);

        // 等待做業執行完成時才關閉調度器
        sched.shutdown(true);
    }
}

運行結果(設置了 sleep 10 秒,故在 0 秒調度一次,5 秒調度一次, 10 秒調度最後一次):

---> Instance = job1, group = group1, description = Hello Quartz, value =1990, array item[0] = firstItem
Runtime: Wed Apr 19 11:24:15 CST 2017 <---
---> Instance = job1, group = group1, description = Hello Quartz, value =1990, array item[0] = firstItem
Runtime: Wed Apr 19 11:24:20 CST 2017 <---
---> Instance = job1, group = group1, description = Hello Quartz, value =1990, array item[0] = firstItem
Runtime: Wed Apr 19 11:24:25 CST 2017 <---

3. cronExpression 表達式

格式:[秒] [分] [時] [每個月的第幾日] [月] [每週的第幾日] [年]

字段名 必須的 容許值 容許的特殊字符
Seconds YES 0-59 , - * /
Minutes YES 0-59 , - * /
Hours YES 0-23 , - * /
Day of month YES 1-31 , - * ? / L W
Month YES 1-12 or JAN-DEC , - * /
Day of week YES 1-7 or SUN-SAT , - * ? / L #
Year NO empty, 1970-2099 , - * /

特殊字符說明:

字符 含義
* 用於 指定字段中的全部值。好比:* 在分鐘中表示 每一分鐘
? 用於 指定日期中的某一天,或是 星期中的某一個星期
- 用於 指定範圍。好比:10-12 在小時中表示 10 點,11 點,12 點
, 用於 指定額外的值。好比:MON,WED,FRI 在日期中表示 星期一, 星期三, 星期五
/ 用於 指定增量。好比:0/15 在秒中表示 0 秒, 15 秒, 30 秒, 45 秒5/15 在秒中表示 5 秒,20 秒,35 秒,50 秒
L 在兩個字段中擁有不一樣的含義。好比:L 在日期(Day of month)表示 某月的最後一天。在星期(Day of week)只表示 7SAT。可是,值L 在星期(Day of week)中表示 某月的最後一個星期幾。 好比:6L 表示 某月的最後一個星期五。也能夠在日期(Day of month)中指定一個偏移量(從該月的最後一天開始).好比:L-3 表示 某月的倒數第三天
W 用於指定工做日(星期一到星期五)好比:15W 在日期中表示 到 15 號的最近一個工做日。若是第十五號是週六, 那麼觸發器的觸發在 第十四號星期五。若是第十五號是星期日,觸發器的觸發在 第十六號週一。若是第十五是星期二,那麼它就會工做開始在 第十五號週二。然而,若是指定 1W 而且第一號是星期六,那麼觸發器的觸發在第三號週一,由於它不會 "jump" 過一個月的日子的邊界。
LW 能夠在日期(day-of-month)合使用,表示 月份的最後一個工做日
# 用於 指定月份中的第幾天。好比:6#3 表示 月份的第三個星期五(day 6 = Friday and "#3" = the 3rd one in the month)。其它的有,2#1 表示 月份第一個星期一4#5 表示 月份第五個星期三。注意: 若是隻是指定 #5,則觸發器在月份中不會觸發。

注意:字符不區分大小寫,MONmon 相同。

3.1 cronExpression 示例

表達式 含義
0 0 12 * * ? 天天中午 12 點
0 15 10 ? * * 天天上午 10 點 15 分
0 15 10 * * ? 天天上午 10 點 15 分
0 15 10 * * ? * 天天上午 10 點 15 分
0 15 10 * * ? 2005 在 2005 年裏的天天上午 10 點 15 分
0 * 14 * * ? 天天下午 2 點到下午 2 點 59 分的每一分鐘
0 0/5 14 * * ? 天天下午 2 點到 2 點 55 分每隔 5 分鐘
0 0/5 14,18 * * ? 天天下午 2 點到 2 點 55 分, 下午 6 點到 6 點 55 分, 每隔 5 分鐘
0 0-5 14 * * ? 天天下午 2 點到 2 點 5 分的每一分鐘
0 10,44 14 ? 3 WED 3 月每週三的下午 2 點 10 分和下午 2 點 44 分
0 15 10 ? * MON-FRI 每週一到週五的上午 10 點 15 分
0 15 10 15 * ? 每個月 15 號的上午 10 點 15 分
0 15 10 L * ? 每個月最後一天的上午 10 點 15 分
0 15 10 L-2 * ? 每個月最後兩天的上午10點15分
0 15 10 ? * 6L 每個月的最後一個星期五的上午 10 點 15 分
0 15 10 ? * 6L 2002-2005 2002 年到 2005 年每月的最後一個星期五的上午 10 點 15 分
0 15 10 ? * 6#3 每個月的第三個星期五的上午 10 點 15 分
0 0 12 1/5 * ? 每個月的 1 號開始每隔 5 天的中午 12 點
0 11 11 11 11 ? 每一年 11 月 11 號上午 11 點 11 分

4. Listener 示例

監聽器在運行時將其註冊到調度程序中,而且必須給出一個名稱(或者,他們必須經過他們的 getName() 來宣傳本身的名稱)。

4.1 TriggerListener 和 JobListener 示例

偵聽器與調度程序的 ListenerManager 一塊兒註冊,而且描述了監聽器想要接收事件的做業/觸發器的 Matcher。

1)註冊對特定做業的 JobListener:

sched.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("job1", "group1")));

2)註冊對特定組的全部做業的 JobListener:

sched.getListenerManager().addJobListener(new MyJobListener(), GroupMatcher.jobGroupEquals("group1"));

3)註冊對兩個特定組的全部做業的 JobListener:

sched.getListenerManager().addJobListener(new MyJobListener(), OrMatcher.or(GroupMatcher.jobGroupEquals("group1"), GroupMatcher.jobGroupEquals("group2")));

4)註冊一個對全部做業的 JobListener:

sched.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());

JobListener 實現類:

package org.quartz.examples;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.quartz.SchedulerException;

public class MyJobListener implements JobListener {

    @Override
    public String getName() {
        return "MyJobListener"; // 必定要設置名稱
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {

    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {

    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        if (jobException != null) {
            try {
                // 當即關閉調度器
                context.getScheduler().shutdown();
                System.out.println("Error occurs when executing jobs, shut down the scheduler.");
                // 給管理員發送郵件...
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        }
    }
}

Job 實現類:

package org.quartz.examples;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class QuartzTest implements Job {

    /**
     * Quartz requires a public empty constructor so that the
     * scheduler can instantiate the class whenever it needs.
     */
    public QuartzTest() {
    }

    /**
     * 該方法實現須要執行的任務
     */
    @SuppressWarnings("unchecked")
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 故意運行異常,觀察監聽器是否正常工做
        int i = 1/0;
        
        // 從 context 中獲取 instName, groupName 以及 dataMap
        String instName = context.getJobDetail().getKey().getName();
        String groupName = context.getJobDetail().getKey().getGroup();
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        // 從 dataMap 中獲取 myDescription, myValue 以及 myArray
        String myDescription = dataMap.getString("myDescription");
        int myValue = dataMap.getInt("myValue");
        List<String> myArray = (List<String>) dataMap.get("myArray");
        System.out.println("---> Instance = " + instName + ", group = " + groupName
                + ", description = " + myDescription + ", value =" + myValue
                + ", array item[0] = " + myArray.get(0));
        System.out.println("Runtime: " + new Date().toString() + " <---");
    }

    public static void main(String[] args) throws SchedulerException, InterruptedException {
        // 經過 schedulerFactory 獲取一個調度器
        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler sched = sf.getScheduler();

        // 建立 jobDetail 實例,綁定 Job 實現類
        // 指明 job 的名稱,所在組的名稱,以及綁定 job 類
        JobDetail job = JobBuilder.newJob(QuartzTest.class).withIdentity("job1", "group1").build();

        // 定義調度觸發規則

        // SimpleTrigger,從當前時間的下 1 秒開始,每隔 1 秒執行 1 次,重複執行 2 次
        /*Trigger trigger = TriggerBuilder.newTrigger()
                // 指明 trigger 的 name 和 group
                .withIdentity("trigger1", "group1")
                // 從當前時間的下 1 秒開始執行,默認爲當即開始執行(.startNow())
                .startAt(DateBuilder.evenSecondDate(new Date()))
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(1) // 每隔 1 秒執行 1 次
                        .withRepeatCount(2)) // 重複執行 2 次,一共執行 3 次
                .build();*/


        // corn 表達式,先當即執行 1 次,而後每隔 5 秒執行 1 次
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?"))
                .build();

        // 初始化參數傳遞到 job
        job.getJobDataMap().put("myDescription", "Hello Quartz");
        job.getJobDataMap().put("myValue", 1990);
        List<String> list = new ArrayList<>();
        list.add("firstItem");
        job.getJobDataMap().put("myArray", list);

        // 註冊對特定做業的監聽器
        sched.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("job1", "group1")));

        // 把做業和觸發器註冊到任務調度中
        sched.scheduleJob(job, trigger);

        // 啓動計劃程序(實際上直到調度器已經啓動纔會開始運行)
        sched.start();

        // 等待 10 秒,使咱們的 job 有機會執行
        Thread.sleep(10000);

        // 等待做業執行完成時才關閉調度器
        sched.shutdown(true);
    }
}

運行結果:

[ERROR] 19 四月 11:54:35.361 上午 DefaultQuartzScheduler_Worker-1 [org.quartz.core.JobRunShell]
Job group1.job1 threw an unhandled Exception: 

java.lang.ArithmeticException: / by zero
    at org.quartz.examples.QuartzTest.execute(QuartzTest.java:27)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)
[ERROR] 19 四月 11:54:35.361 上午 DefaultQuartzScheduler_Worker-1 [org.quartz.core.ErrorLogger]
Job (group1.job1 threw an exception.

org.quartz.SchedulerException: Job threw an unhandled exception. [See nested exception: java.lang.ArithmeticException: / by zero]
    at org.quartz.core.JobRunShell.run(JobRunShell.java:213)
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)
Caused by: java.lang.ArithmeticException: / by zero
    at org.quartz.examples.QuartzTest.execute(QuartzTest.java:27)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
    ... 1 more

Error occurs when executing jobs, shut down the scheduler.

...註冊 TriggerListener 的工做原理相同。

4.2 SchedulerListener 示例

SchedulerListener 在調度程序的 SchedulerListener 中註冊。SchedulerListener 幾乎能夠實現任何實現 org.quartz.SchedulerListener 接口的對象。

註冊對添加調度器時的 SchedulerListener:

scheduler.getListenerManager().addSchedulerListener(mySchedListener);

註冊對刪除調度器時的 SchedulerListener

scheduler.getListenerManager().removeSchedulerListener(mySchedListener);

5. 參考

PS:本文針對的 Quartz 版本爲 Quartz 2.2.3。官方下載地址:Quartz 2.2.3 .tar.gz

相關文章
相關標籤/搜索