Springcloud學習筆記37--任務調度框架Quartz 使用(Cron表達式)與@scheduled注解定時任務


1.Quartz簡介

Quartz是一款Java編寫的開源任務調度框架,同時它也是Spring默認的任務調度框架。基於定時、定期的策略來執行任務是它的核心功能,比如x年x月的每個星期五上午8點到9點,每隔10分鍾執行1次。

它的作用其實類似於Java中的Timer定時器以及JUC中的ScheduledExecutorService調度線程池,當然Quartz作為一個獨立的任務調度框架無疑在這方面表現的更為出色,功能更強大,能夠定義更為復雜的執行規則。

Quartz中主要用到了:Builder建造者模式、Factory工廠模式以及組件模式。

Quartz有3個核心要素:任務(Job)、觸發器(Trigger)、調度器(Scheduler)。

任務(Job,你需要做什么事?我們需要將具體的業務邏輯寫到實現了Job接口的實現類中)

觸發器Trigger,你什么時候去做?它定義了任務的執行規則,什么時候開始執行,什么時候結束執行)

調度器Scheduler,你什么時候需要去做什么事?通過傳入的任務Job和觸發器Trigger,以指定的規則執行任務)。

Quartz框架的核心是調度器。調度器負責管理Quartz應用運行時環境。調度器不是靠自己做所有的工作,而是依賴框架內一些非常重要的部件。Quartz不僅僅是線程和線程池管理。為確保可伸縮性,Quartz采用了基於多線程的架構。啟動時,框架初始化一套worker線程,這套線程被調度器用來執行預定的作業。這就是Quartz能並發運行多個作業的原理。Quartz依賴一套松耦合的線程池管理部件來管理線程環境。

2.Quartz的體系結構

JobDetail:quartz每次都會直接創建一個JobDetail,同時創建一個Job實例,它不直接接受一個Job的實例,但是它接受一個Job的實現類,通過new instance()的反射方式來實例一個Job,在這里Job是一個接口,我們需要自己編寫類去實現這個接口。

Trigger : 它由SimpleTrigger和CronTrigger組成,SimpleTrigger實現類似Timer的定時調度任務,CronTrigger可以通過cron表達式實現更復雜的調度邏輯·。

SimpleTrigger很方便,如果你需要一次性執行(只是在一個給定時刻執行job),或者如果你需要一個job在一個給定的時間,並讓它重復N次,並在執行之間延遲T。
CronTrigger是有用的,如果你想擁有引發基於當前日歷時間表,如每個星期五,中午或在每個月的第十天 10:15。
Scheduler:調度器,JobDetail和Trigger可以通過Scheduler綁定到一起。

3. Quartz重要組成部分

3.1 Job接口

Job接口很簡單,只有一個execute方法,這是我們自己的具體業務邏輯的入口。

要創建一個任務,我們需要編寫一個實現該接口的具體任務類:

public class HelloJob implements Job{

    public void execute(JobExecutionContext context) throws JobExecutionException {
    //編寫我們自己的業務邏輯
    }
}

3.2 JobDetail

JobDetail描述了Job對象的基本信息,主要包含四個重要的屬性:name(Job的名稱)、group(Job的組名稱)、jobClass(Job對應的類)以及jobDataMap(存儲一些用戶自定義的信息或對象)。在SchedulerJob的名稱name和組group組合必須是唯一的。

quartz每次都會直接創建一個JobDetail,同時創建一個Job實例,它不直接接受一個Job的實例,但是它接受一個Job的實現類,通過new instance()的反射方式來實例一個Job.可以通過下面的方式將一個Job實現類綁定到JobDetail中

// 指明job的名稱,所在組的名稱,以及綁定job類
JobDetail job = JobBuilder.newJob(HelloJob.class) //綁定job類
    .withIdentity("JobName", "JobGroupName") //指明job的名稱為JobName,所在組的名稱為JobGroupName
    .usingJobData(jobDataMap) //傳遞job執行時需要的數據
    .build();

3.3 JobBuiler

主要是用來創建jobDeatil實例

3.4 JobDataMap數據存儲類

通過查看JobDetailTriggerJobExecutionContext的源碼可以發現,他們中都存在JobDataMap這個類型,它是以Map的形式存儲我們的一些自定義數據的。當Job對象的execute方法被調用時,JobDataMap會通過JobExecutionContext傳遞給execute方法,它可以用來裝載任何可序列化的數據對象。JobDataMap實現了Java中的Map接口,提供了一些自己的方法來存儲數據。

這是JobDataMap的繼承樹:

可以看到JobDataMapDirtyFlagMap的子類,而DirtyFlagMap實際實現了Java中的java.util.Map類型:

// DirtyFlagMap是java.util.Map接口的子類
public class DirtyFlagMap<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { }

一句話:把它當Java中的map來用就對了!

3.5 trigger

前文講到它主要用來執行Job實現類的業務邏輯的,我們可以通過下面的代碼來創建一個Trigger實例

CronTrigger trigger = (CronTrigger) TriggerBuilder
                .newTrigger()
                .withIdentity("myTrigger", "group1")    //創建一個標識符
                .startAt(date)//什么時候開始觸發
                //每秒鍾觸發一次任務
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? *"))
                .build();

3.6 Scheduler

創建Scheduler有兩種方式
通過StdSchedulerFactory來創建

SchedulerFactory sfact=new StdSchedulerFactory();
Scheduler scheduler=sfact.getScheduler();

通過DirectSchedulerFactory來創建

DiredtSchedulerFactory factory=DirectSchedulerFactory.getInstance();
Scheduler scheduler=factory.getScheduler();

Scheduler 配置參數一般存儲在quartz.properties中,我們可以修改參數來配置相應的參數。通過調用getScheduler()方法就能創建和初始化調度對象。

Scheduler的主要函數介紹:

Date schedulerJob(JobDetail,Trigger trigger);返回最近觸發的一次時間
void standby()暫時掛起
void shutdown()完全關閉,不能重新啟動了
shutdown(true)表示等待所有正在執行的job執行完畢之后,再關閉scheduler
shutdown(false)即直接關閉scheduler

在這里我們不得不提一下quartz.properties這個資源文件,在org.quartz這個包下,當我們程序啟動的時候,它首先會到我們的根目錄下查看是否配置了該資源文件,如果沒有就會到該包下讀取相應信息,當我們咋實現更復雜的邏輯時,需要自己指定參數的時候,可以自己配置參數來實現。下面我們簡單看一下這個資源文件:

org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

org.quartz.jobStore.misfireThreshold: 60000

org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

該資源文件主要組成部分:
①調度器屬性
②線程池屬性
③作業存儲設置
④插件設置

<1>調度器屬性:
org.quartz.scheduler.instanceName屬性用來區分特定的調度器實例,可以按照功能用途來給調度器起名。
org.quartz.scheduler.instanceId屬性和前者一樣,也允許任何字符串,但這個值必須是在所有調度器實例中是唯一的,尤其是在一個集群當中,作為集群的唯一key,假如你想quartz幫你生成這個值的話,可以設置我Auto

<2>線程池屬性:
threadCount設置線程的數量

threadPriority設置線程的優先級

org.quartz.threadPool.class 線程池的實現

<3>作業存儲設置:
描述了在調度器實例的聲明周期中,job和trigger信息是怎么樣存儲的

<4>插件配置:
滿足特定需求用到的quartz插件的配置

4. Cron表達式 

在這里,我們着重講解一下cron表達式,quartz之所以能夠實現更加復雜的業務邏輯,主要在依賴於cron表達式。 
cron表達式編寫的順序一次是”秒 分 時 日 月 周 年”。 

在線Cron生成表達式:http://cron.qqe2.com/

cron 一定有七位數,最后一位是年,SpringBoot 定時方案只需要設置六位即可:

  • 第一位, 表示秒, 取值是0 ~ 59,允許的字符為,- * /
  • 第二位, 表示分. 取值是0 ~ 59,允許的字符為 ,- * /
  • 第三位, 表示小時, 取值是0 ~ 23,允許的字符為,- * /
  • 第四位, 表示天/日, 取值是0 ~ 31,允許的字符為,- * ? / L W C
  • 第五位, 表示月份, 取值是1 ~ 12,允許的字符為 ,- * /
  • 第六位, 表示星期, 取值是1 ~ 7, 星期一,星期二…, 還有 1 表示星期日,允許的字符為,- * ? / L W C
  • 第七位, 年份, 可以留空, 取值是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 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期間的每1分鍾觸發   
"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:05期間的每1分鍾觸發   
"0 10,44 14 ? 3 WED"    每年三月的星期三的下午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 ? * 6L"    每月的最后一個星期五上午10:15觸發     
"0 15 10 ? * 6L 2002-2005"    2002年至2005年的每月的最后一個星期五上午10:15觸發   
"0 15 10 ? * 6#3"    每月的第三個星期五上午10:15觸發    
  
特殊字符   意義   
*    表示所有值;   
?    表示未說明的值,即不關心它為何值;   
-    表示一個指定的范圍;   
,    表示附加一個可能值;   
/    符號前表示開始時間,符號后表示每次遞增的值;   
L("last")    ("last") "L" 用在day-of-month字段意思是 "這個月最后一天";用在 day-of-week字段, 它簡單意思是 "7" or "SAT"。 如果在day-of-week字段里和數字聯合使用,它的意思就是 "這個月的最后一個星期幾" – 例如: "6L" means "這個月的最后一個星期五". 當我們用“L”時,不指明一個列表值或者范圍是很重要的,不然的話,我們會得到一些意想不到的結果。   
W("weekday")    只能用在day-of-month字段。用來描敘最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近這個月第15天的工作日”,即如果這個月第15天是周六,那么觸發器將會在這個月第14天即周五觸發;如果這個月第15天是周日,那么觸發器將會在這個月第16天即周一觸發;如果這個月第15天是周二,那么就在觸發器這天觸發。注意一點:這個用法只會在當前月計算值,不會越過當前月。“W”字符僅能在day-of-month指明一天,不能是一個范圍或列表。也可以用“LW”來指定這個月的最后一個工作日。    
#    只能用在day-of-week字段。用來指定這個月的第幾個周幾。例:在day-of-week字段用"6#3"指這個月第3個周五(6指周五,3指第3個)。如果指定的日期不存在,觸發器就不會觸發。    
C    指和calendar聯系后計算過的值。例:在day-of-month 字段用“5C”指在這個月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在這周日或之后包括calendar的第一天  

5.Quartz框架實戰

5.1 maven依賴

添加quartz的依賴:

    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.0</version>
    </dependency>

核心類:

Scheduler :調度器,所有Job的調度都是由它控制;

JobDetail :生成Job對象的實例,存儲Job對象需要的參數;

Job :執行業務邏輯;

Trigger :定義觸發的條件;

幫助類

SimpleScheduleBuilder:用於構建Scheduler:

JobBuilder :用於構建JobDetail:

TriggerBuilder :用於構建Trigger;

5.2 創建job

首先創建一個Quartz任務,任務中從JobExecutionContext中獲取到了JobDetailTrigger中的JobDataMap,並從中取到了客戶端QuartzScheduler中傳入的數據:

/**
 * @Author lucky
 * @Date 2021/12/27 9:18
 */
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDetail detail = jobExecutionContext.getJobDetail();
        String name = detail.getJobDataMap().getString("name");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //顯示的格式
        String date = sdf.format(new Date());
        System.out.println(date+":  say hello " + name );
    }
}

5.3 任務調度測試案例

創建Quartz客戶端,構建JobDetailTrigger並使用Scheduler開始任務調度(這里要注意的是Scheduler實例創建后處於“待機”狀態,所以別忘了調用start方法啟動調度器,否則任務是不會執行的!):

/**
 * @Author lucky
 * @Date 2021/12/27 9:23
 */
public class JobTest {
    public static void main(String[] args) throws InterruptedException {

        // 創建工廠
        SchedulerFactory schedulerfactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            // 通過schedulerFactory獲取一個調度器
            scheduler = schedulerfactory.getScheduler();

            JobDataMap jobDataMap=new JobDataMap();
            jobDataMap.put("name","quartz" );


            // 創建一個JobDetail實例,指明job的名稱,所在組的名稱,以及綁定job類
            JobDetail job = JobBuilder.newJob(HelloJob.class) //綁定job類
                    .withIdentity("JobName", "JobGroupName") //指定JobDetail的名稱和組名稱
                    .usingJobData(jobDataMap) //使用jobDataMap存儲用戶數據, jobDataMap為JobDetail傳遞的文本數據
                    .build();
            // 構建一個Trigger(定義觸發的條件),指定Trigger名稱和組,規定該Job立即執行,且3秒鍾重復執行一次
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("CronTrigger1", "CronTriggerGroup") //指定Trigger名稱和組
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever()) // 設置運行規則,每隔3秒執行一次,一直重復下去
                    .startNow() // 執行的時機,立即執行
                    .build();

            //綁定JobDetail和Trigger
            scheduler.scheduleJob(job, trigger);

            //開始任務調度
            scheduler.start();

            Thread.sleep(30000);

            // 停止任務調度
            scheduler.shutdown();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }

    }
}

控制台輸出:

6. @scheduled注解定時任務

在 Spring Boot 中使用 @Scheduled 注解創建定時任務非常簡單,只需要兩步操作就可以創建一個定時任務:

(1)在定時任務類上增加 @EnableScheduling 注解

(2)在要執行任務的方法上增加 @Scheduled 注解

(3)ShedLock的作用,確保任務在同一時刻最多執行一次。如果一個任務正在一個節點上執行,則它將獲得一個鎖,該鎖將阻止從另一個節點(或線程)執行同一任務。如果一個任務已經在一個節點上執行,則在其他節點上的執行不會等待,只需跳過它即可

package com.ttbank.flep.core.job;

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @Author lucky
 * @Date 2022/1/27 10:37
 */
@Component
@Configurable
@EnableScheduling
public class ScheduledTasks {

    /**
     * 每6秒執行一次
     **/
    @Scheduled(cron = "*/6 * *  * * * ")
    public void reportCurrentByCron(){
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println ("Scheduling Tasks Examples By Cron: The time is now " + sdf.format (new Date()));
    }

}

控制台輸出:

參考文獻:

https://zhuanlan.zhihu.com/p/133208221---非常好

https://zhuanlan.zhihu.com/p/133211946

https://blog.csdn.net/chengqiuming/article/details/84187419---非常好

https://blog.csdn.net/cyan20115/article/details/106550915

https://blog.csdn.net/java_hanyu_tel/article/details/79697161----非常好

https://www.cnblogs.com/haw2106/p/9950826.html-----非常好

https://www.cnblogs.com/loong-hon/p/10912741.html-----------非常好

https://www.cnblogs.com/niceyoo/p/10917461.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM