Spring Boot2.0 集成 Quartz

在項目開發中,經常需要定時任務來幫助我們來做一些內容,比如定時派息、跑批對賬、業務監控等。Spring Boot 體系中現在有兩種方案可以選擇,第一種是 Spring Boot 內置的方式簡單註解就可以使用,當然如果需要更復雜的應用場景還是得 Quartz 上場,Quartz 目前是 Java 體系中最完善的定時方案。

首先來看看 Spring Boot 自帶的定時方案。

Spring Boot 內置定時

pom 包配置

pom 包裏面只需要引入 Spring Boot Starter 包即可,Spring Boot Starter 包中已經內置了定時的方法。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
</dependencies>

啓動類開啓定時

在啓動類上面加上 @EnableScheduling 即可開啓定時:

@Spring BootApplication
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

創建定時任務實現類

使用 Spring Boot 自帶的定時非常的簡單,只需要在方法上面添加 @Scheduled 註解即可。

定時任務1:

@Component
public class SchedulerTask {

    private int count=0;

    @Scheduled(cron="*/6 * * * * ?")
    private void process(){
        System.out.println("this is scheduler task runing  "+(count++));
    }

}

設置 process() 每隔六秒執行一次,並統計執行的次數。

我們還有另外的一種方案來設置,固定時間週期執行方法,來看定時任務2:

@Component
public class Scheduler2Task {

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    @Scheduled(fixedRate = 6000)
    public void reportCurrentTime() {
        System.out.println("現在時間:" + dateFormat.format(new Date()));
    }

}

啓動項目之後,就會在控制檯看到打印的結果。

結果如下:

this is scheduler task runing  0
現在時間:09:44:17
this is scheduler task runing  1
現在時間:09:44:23
this is scheduler task runing  2
現在時間:09:44:29
this is scheduler task runing  3
現在時間:09:44:35

說明兩個方法都按照固定 6 秒的頻率來執行。

參數說明

@Scheduled 參數可以接受兩種定時的設置,一種是我們常用的 cron="*/6 * * * * ?",一種是 fixedRate = 6000,兩種都可表示固定週期執行定時任務。

fixedRate 說明

  • @Scheduled(fixedRate = 6000):上一次開始執行時間點之後 6 秒再執行。
  • @Scheduled(fixedDelay = 6000):上一次執行完畢時間點之後 6 秒再執行。
  • @Scheduled(initialDelay=1000, fixedRate=6000):第一次延遲 1 秒後執行,之後按 fixedRate 的規則每 6 秒執行一次。

cron 說明

cron 一共有七位,最後一位是年,Spring Boot 定時方案中只需要設置六位即可:

  • 第一位,表示秒,取值 0 ~ 59;
  • 第二位,表示分,取值 0 ~ 59;
  • 第三位,表示小時,取值 0 ~ 23;
  • 第四位,日期天/日,取值 1 ~ 31;
  • 第五位,日期月份,取值 1~12;
  • 第六位,星期,取值 1 ~ 7,星期一,星期二…,注,不是第 1 周、第 2 周的意思,另外,1 表示星期天,2 表示星期一;
  • 第七位,年份,可以留空,取值 1970 ~ 2099。

cron 中,還有一些特殊的符號,含義如下:

  • (*)星號,可以理解爲每的意思,每秒、每分、每天、每月、每年…。
  • (?)問號,問號只能出現在日期和星期這兩個位置,表示這個位置的值不確定,每天 3 點執行,因此第六位星期的位置,是不需要關注的,就是不確定的值;同時,日期和星期是兩個相互排斥的元素,通過問號來表明不指定值,比如 1 月 10 日是星期一,如果在星期的位置另指定星期二,就前後衝突矛盾了。
  • (-)減號,表達一個範圍,如在小時字段中使用“10 ~ 12”,則表示從 10 到 12 點,即 10、11、12。
  • (,)逗號,表達一個列表值,如在星期字段中使用“1、2、4”,則表示星期一、星期二、星期四。
  • (/)斜槓,如 x/y,x 是開始值,y 是步長,比如在第一位(秒),0/15 就是從 0 秒開始,每隔 15 秒執行一次,最後就是 0、15、30、45、60,另 */y,等同於 0/y。

下面列舉幾個常用的例子。

  • 0 0 3 * * ? :每天 3 點執行;
  • 0 5 3 * * ?:每天 3 點 5 分執行;
  • 0 5 3 ? * *:每天 3 點 5 分執行,與上面作用相同;
  • 0 5/10 3 * * ?:每天 3 點的 5 分、15 分、25 分、35 分、45 分、55 分這幾個時間點執行;
  • 0 10 3 ? * 1:每週星期天,3 點 10 分執行,注,1 表示星期天;
  • 0 10 3 ? * 1#3:每個月的第三個星期,星期天執行,# 號只能出現在星期的位置。

以上就是 Spring Boot 自定的定時方案,使用起來非常的簡單方便。

Quartz

Quartz 介紹

Quartz 是 OpenSymphony 開源組織在 Job Scheduling 領域又一個開源項目,是完全由 Java 開發的一個開源任務日程管理系統,“任務進度管理器”就是一個在預先確定(被納入日程)的時間到達時,負責執行(或者通知)其他軟件組件的系統。 Quartz 是一個開源的作業調度框架,它完全由 Java 寫成,並設計用於 J2SE 和 J2EE 應用中,它提供了巨大的靈活性而不犧牲簡單性。

當定時任務愈加複雜時,使用 Spring 註解 @Schedule 已經不能滿足業務需要。

Quartz 的優點

  • 豐富的 Job 操作 API;
  • 支持多種配置;
  • Spring Boot 無縫集成;
  • 支持持久化;
  • 支持集羣;
  • Quartz 還支持開源,是一個功能豐富的開源作業調度庫,可以集成到幾乎任何 Java 應用程序中。

Quartz 體系結構

明白 Quartz 怎麼用,首先要了解 Job(任務)、JobDetail(任務信息)、Trigger(觸發器)和 Scheduler(調度器)這 4 個核心的概念。

(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 執行調度等。

(4)Scheduler:調度器就相當於一個容器,裝載着任務和觸發器,該類是一個接口,代表一個 Quartz 的獨立運行容器,Trigger 和 JobDetail 可以註冊到 Scheduler 中,兩者在 Scheduler 中擁有各自的組及名稱,組及名稱是 Scheduler 查找定位容器中某一對象的依據,Trigger 的組及名稱必須唯一,JobDetail 的組和名稱也必須唯一(但可以和 Trigger 的組和名稱相同,因爲它們是不同類型的)。Scheduler 定義了多個接口方法,允許外部通過組及名稱訪問和控制容器中 Trigger 和 JobDetail。

四者其關係如下圖所示:

Job 爲作業的接口,爲任務調度的對象;JobDetail 用來描述 Job 的實現類及其他相關的靜態信息;Trigger 做爲作業的定時管理工具,一個 Trigger 只能對應一個作業實例,而一個作業實例可對應多個觸發器;Scheduler 做爲定時任務容器,是 Quartz 最上層的東西,它提攜了所有觸發器和作業,使它們協調工作,每個 Scheduler 都存有 JobDetail 和 Trigger 的註冊,一個 Scheduler 中可以註冊多個 JobDetail 和多個 Trigger。

Spring Boot 和 Quartz

Spring Boot 2.0 提供了 spring-boot-starter-quartz 組件集成 Quartz,讓我們在項目中使用 Quartz 變得簡單。

配置內容

配置 pom.xml

添加 spring-boot-starter-quartz 組件:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

簡單示例

配置完成之後先來做一個最簡單的示例,使用 Quartz 定時輸出 Hello World。

首先定義一個 Job 需要繼承 QuartzJobBean,示例中 Job 定義一個變量 Name,用於在定時執行的時候傳入。

public class SampleJob extends QuartzJobBean {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {
        System.out.println(String.format("Hello %s!", this.name));
    }

}

接下來構建 JobDetail,並且構建時傳入 name 屬性的值,構建 JobTrigger 和 scheduleBuilder,最後使用 Scheduler 啓動定時任務。

@Configuration
public class SampleScheduler {

    @Bean
    public JobDetail sampleJobDetail() {
        return JobBuilder.newJob(SampleJob.class).withIdentity("sampleJob")
                .usingJobData("name", "World").storeDurably().build();
    }

    @Bean
    public Trigger sampleJobTrigger() {
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(2).repeatForever();

        return TriggerBuilder.newTrigger().forJob(sampleJobDetail())
                .withIdentity("sampleTrigger").withSchedule(scheduleBuilder).build();
    }
}
  • JobBuilder 無構造函數,只能通過 JobBuilder 的靜態方法 newJob(Class jobClass)生成 JobBuilder 實例。
  • withIdentity 方法可以傳入兩個參數 withIdentity(String name,String group) 來定義 TriggerKey,也可以不設置,像上文示例中會自動生成一個獨一無二的 TriggerKey 用來區分不同的 Trigger。

啓動項目後每隔兩秒輸出:Hello World!

Hello World!
Hello World!
Hello World!
...

CronSchedule 示例

CronSchedule 可以設置更靈活的使用方式,定時設置可以參考上面的 cron 表達式。

首先定義兩個 Job:

public class ScheduledJob implements Job {
    @Override  
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("schedule job1 is running ...");
    }  
} 

ScheduledJob2 和 ScheduledJob 代碼基本一致。

按照使用 Quartz 的邏輯,構建 jobDetail、CronTrigger,最後使用 scheduler 關聯 jobDetail 和 CronTrigger。scheduleJob1 設置每間隔 6 秒執行一次。

private void scheduleJob1(Scheduler scheduler) throws SchedulerException{
    JobDetail jobDetail = JobBuilder.newJob(ScheduledJob.class) .withIdentity("job1", "group1").build();
    CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/6 * * * * ?");
    CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") .withSchedule(scheduleBuilder).build();
    scheduler.scheduleJob(jobDetail,cronTrigger);   
}  
  • CronScheduleBuilder.cronSchedule("0/6 * * * * ?"),按照 cron 表達式設置定時任務的執行週期。

ScheduleJob2 的內容和 ScheduleJob1 基本一致,時間設置爲間隔 12 秒執行一次。

使用 Scheduler 啓動兩個定時任務。

public void scheduleJobs() throws SchedulerException {
    Scheduler scheduler = schedulerFactoryBean.getScheduler();
    scheduleJob1(scheduler);
    scheduleJob2(scheduler);   
}  

何時觸發定時任務

我們有兩種方案來觸發 CronSchedule 定時任務,一種是啓動時調用 scheduleJobs() 來啓動定時任務,另外一種方案使用 Spring Boot 自帶的 Scheduled 在特定時間觸發啓動。

第一種方案,啓動時觸發定時任務:

@Component
public class MyStartupRunner implements CommandLineRunner {

    @Autowired
    public CronSchedulerJob scheduleJobs;

    @Override
    public void run(String... args) throws Exception {
        scheduleJobs.scheduleJobs();
        System.out.println(">>>>>>>>>>>>>>>定時任務開始執行<<<<<<<<<<<<<");
    }
}

定時一個 Runner,繼承 CommandLineRunner 並重新 run 方法,在 run 方法中調用 scheduleJobs() 來啓動定時任務。

第二種方案,特定時間啓動定時任務:

@Configuration
@EnableScheduling
@Component
public class SchedulerListener {  
    @Autowired
    public CronSchedulerJob scheduleJobs;
    @Scheduled(cron="0 30 11 25 11 ?")
    public void schedule() throws SchedulerException {
        scheduleJobs.scheduleJobs();
     }      
}

啓動項目後每隔 6 秒輸出 job1 內容,每隔 12 秒輸出 job2 內容,再加上上面示例每兩秒輸出的 Hello World,輸出內容如下:

Hello World!
Hello World!
Hello World!
schedule job1 is running ...
Hello World!
Hello World!
Hello World!
schedule job1 is running ...
schedule job2 is running ...
...

一般情況下,建議使用第一種方案來啓動定時任務;第二種方案設置固定日期時,需要考慮重複啓動定時任務的情況,重複啓動定時任務會報錯。

注意,兩種啓動方案,在項目中選擇一種使用即可,否則會導致重複啓動定時任務而報錯。

總結

通過上面的示例可以看出,如果僅需要執行簡單定時任務,就可以使用 Spring Boot 自帶 Scheduled,非常簡單、方便;但如果需要在項目中執行大量的批任務處理時,可以採用 Quartz 來解決,Spring Boot 2.0 中提供了對 Quartz 的支持,讓我們在項目使用的過程中更加的靈活簡潔。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章