java--Quartz 定時執行


第一步:引包(Maven)

 <!-- 定時任務 -->
    <dependency>
    
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.2.2</version>
    </dependency>

第二步:創建要被定執行的任務類

  這一步也很簡單,只需要創建一個實現了org.quartz.Job接口的類,並實現這個接口的唯一一個方法execute(JobExecutionContext arg0) throws JobExecutionException即可。如:

package com.fync.quartz;

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

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
 * 創建要被定執行的任務類
 * @author long
 *
 */
public class MyJob implements Job{

    public void execute(JobExecutionContext context)
            throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        System.out.println(sdf.format(new Date()));
    }

}

第三步:創建任務調度,並執行

package com.fync.quartz;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

/**
 * 創建任務調度,並執行
 * @author long    
 *
 */
public class MainScheduler {
    
    //創建調度器
    public static Scheduler getScheduler() throws SchedulerException{
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        return schedulerFactory.getScheduler();
    }
    
    
    public static void schedulerJob() throws SchedulerException{
        //創建任務
        JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build();
        //創建觸發器 每3秒鍾執行一次
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group3")
                            .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
                            .build();
        Scheduler scheduler = getScheduler();
        //將任務及其觸發器放入調度器
        scheduler.scheduleJob(jobDetail, trigger);
        //調度器開始調度任務
        scheduler.start();
        
    }
    
    public static void main(String[] args) throws SchedulerException {
        MainScheduler mainScheduler = new MainScheduler();
        mainScheduler.schedulerJob();
    }

} 
MainScheduler.main()依次創建了scheduler(調度器)、job(任務)、trigger(觸發器),其中,job指定了MyJob,trigger保存job的觸發執行策略(每隔3s執行一次),scheduler將job和trigger綁定在一起,最后scheduler.start()啟動調度,每隔3s觸發執行JobImpl.execute()
打印出當前時間。

除了SimpleScheduler之外,常用的還有CronTrigger.


原理分析:

1、job(任務)

job由若干個classinterface實現。

Job接口

開發者想要job完成什么樣的功能,必須且只能由開發者自己動手來編寫實現,比如demo中的MyJob,這點無容置疑。但要想讓自己的job被quartz識別,就必須按照quartz的規則來辦事,這個規則就是job實現類必須實現Job接口,比如MyJob就實現了Job

Job只有一個execute(JobExecutionContext)JobExecutionContext保存了job的上下文信息,比如綁定的是哪個trigger。job實現類必須重寫execute(),執行job實際上就是運行execute()

2、JobDetailImpl類 / JobDetail接口

JobDetailImpl類實現了JobDetail接口,用來描述一個job,定義了job所有屬性及其get/set方法。了解job擁有哪些屬性,就能知道quartz能提供什么樣的能力,下面用表格列出job若干核心屬性。

屬性名 說明
class 必須是job實現類(比如JobImpl),用來綁定一個具體job
name job名稱。如果未指定,會自動分配一個唯一名稱。所有job都必須擁有一個唯一name,如果兩個job的name重復,則只有最前面的job能被調度
group job所屬的組名
description job描述
durability 是否持久化。如果job設置為非持久,當沒有活躍的trigger與之關聯的時候,job會自動從scheduler中刪除。也就是說,非持久job的生命期是由trigger的存在與否決定的
shouldRecover 是否可恢復。如果job設置為可恢復,一旦job執行時scheduler發生hard shutdown(比如進程崩潰或關機),當scheduler重啟后,該job會被重新執行
jobDataMap 除了上面常規屬性外,用戶可以把任意kv數據存入jobDataMap,實現job屬性的無限制擴展,執行job時可以使用這些屬性數據。此屬性的類型是JobDataMap,實現了Serializable接口,可做跨平台的序列化傳輸

 

3、JobBuilder類

 JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build();

上面代碼是demo一個片段,可以看出JobBuilder類的作用:接收job實現類MyJob,生成JobDetail實例,默認生成JobDetailImpl實例。

這里運用了建造者模式:JobImpl相當於Product;JobDetail相當於Builder,擁有job的各種屬性及其get/set方法;JobBuilder相當於Director,可為一個job組裝各種屬性。

4、trigger(觸發器)

trigger由若干個classinterface實現。

SimpleTriggerImpl類 / SimpleTrigger接口 / Trigger接口

SimpleTriggerImpl類實現了SimpleTrigger接口SimpleTrigger接口繼承了Trigger接口,它們表示觸發器,用來保存觸發job的策略,比如每隔幾秒觸發job。實際上,quartz有兩大觸發器:SimpleTriggerCronTrigger,限於篇幅,本文僅介紹SimpleTrigger

Trigger諸類保存了trigger所有屬性,同job屬性一樣,了解trigger屬性有助於我們了解quartz能提供什么樣的能力,下面用表格列出trigger若干核心屬性。

在quartz源碼或注釋中,經常使用fire(點火)這個動詞來命名屬性名,表示觸發job。

屬性名 屬性類型 說明
name 所有trigger通用 trigger名稱
group 所有trigger通用 trigger所屬的組名
description 所有trigger通用 trigger描述
calendarName 所有trigger通用 日歷名稱,指定使用哪個Calendar類,經常用來從trigger的調度計划中排除某些時間段
misfireInstruction 所有trigger通用 錯過job(未在指定時間執行的job)的處理策略,默認為MISFIRE_INSTRUCTION_SMART_POLICY。詳見這篇blog[5]
priority 所有trigger通用 優先級,默認為5。當多個trigger同時觸發job時,線程池可能不夠用,此時根據優先級來決定誰先觸發
jobDataMap 所有trigger通用 同job的jobDataMap。假如job和trigger的jobDataMap有同名key,通過getMergedJobDataMap()獲取的jobDataMap,將以trigger的為准
startTime 所有trigger通用 觸發開始時間,默認為當前時間。決定什么時間開始觸發job
endTime 所有trigger通用 觸發結束時間。決定什么時間停止觸發job
nextFireTime SimpleTrigger私有 下一次觸發job的時間
previousFireTime SimpleTrigger私有 上一次觸發job的時間
repeatCount SimpleTrigger私有 需觸發的總次數
timesTriggered SimpleTrigger私有 已經觸發過的次數
repeatInterval SimpleTrigger私有 觸發間隔時間

5、TriggerBuilder類

Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group3")
                            .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
                            .build();

上面代碼是demo一個片段,可以看出TriggerBuilder類的作用:生成Trigger實例,默認生成SimpleTriggerImpl實例。同JobBuilder一樣,這里也運用了建造者模式。

6、scheduler(調度器)

scheduler主要由StdScheduler類Scheduler接口StdSchedulerFactory類SchedulerFactory接口QuartzScheduler類實現,它們的關系見下面UML圖。


scheduler UML圖
// 創建調度器
  SchedulerFactory schedulerFactory = new StdSchedulerFactory();
  Scheduler scheduler = schedulerFactory.getScheduler();
......
// 將任務及其觸發器放入調度器
  scheduler.scheduleJob(jobDetail, trigger);
// 調度器開始調度任務
  scheduler.start();

上面代碼是demo一個片段,可以看出這里運用了工廠模式,通過factory類(StdSchedulerFactory)生產出scheduler實例(StdScheduler)。scheduler是整個quartz的關鍵,為此,筆者把demo中用到的scheduler接口的源碼加上中文注釋做個講解。

  • StdSchedulerFactory.getScheduler()源碼
public Scheduler getScheduler() throws SchedulerException {
        // 讀取quartz配置文件,未指定則順序遍歷各個path下的quartz.properties文件
        // 解析出quartz配置內容和環境變量,存入PropertiesParser對象
        // PropertiesParser組合了Properties(繼承Hashtable),定義了一系列對Properties的操作方法,比如getPropertyGroup()批量獲取相同前綴的配置。配置內容和環境變量存放在Properties成員變量中
        if (cfg == null) {
            initialize();
        }
        // 獲取調度器池,采用了單例模式
        // 其實,調度器池的核心變量就是一個hashmap,每個元素key是scheduler名,value是scheduler實例
        // getInstance()用synchronized防止並發創建
        SchedulerRepository schedRep = SchedulerRepository.getInstance();

        // 從調度器池中取出當前配置所用的調度器
        Scheduler sched = schedRep.lookup(getSchedulerName());

        ......

        // 如果調度器池中沒有當前配置的調度器,則實例化一個調度器,主要動作包括:
        // 1)初始化threadPool(線程池):開發者可以通過org.quartz.threadPool.class配置指定使用哪個線程池類,比如SimpleThreadPool。先class load線程池類,接着動態生成線程池實例bean,然后通過反射,
     //   使用setXXX()方法將以org.quartz.threadPool開頭的配置內容賦值給bean成員變量;
// 2)初始化jobStore(任務存儲方式):開發者可以通過org.quartz.jobStore.class配置指定使用哪個任務存儲類,比如RAMJobStore。先class load任務存儲類,接着動態生成實例bean,然后通過反射,
    //   使用setXXX()方法將以org.quartz.jobStore開頭的配置內容賦值給bean成員變量;
// 3)初始化dataSource(數據源):開發者可以通過org.quartz.dataSource配置指定數據源詳情,比如哪個數據庫、賬號、密碼等。jobStore要指定為JDBCJobStore,dataSource才會有效; // 4)初始化其他配置:包括SchedulerPlugins、JobListeners、TriggerListeners等; // 5)初始化threadExecutor(線程執行器):默認為DefaultThreadExecutor; // 6)創建工作線程:根據配置創建N個工作thread,執行start()啟動thread,並將N個thread順序add進threadPool實例的空閑線程列表availWorkers中; // 7)創建調度器線程:創建QuartzSchedulerThread實例,並通過threadExecutor.execute(實例)啟動調度器線程; // 8)創建調度器:創建StdScheduler實例,將上面所有配置和引用組合進實例中,並將實例存入調度器池中 sched = instantiate(); return sched; }

 

上面有個過程是初始化jobStore,表示使用哪種方式存儲scheduler相關數據。quartz有兩大jobStore:RAMJobStoreJDBCJobStoreRAMJobStore把數據存入內存,性能最高,配置也簡單,但缺點是系統掛了難以恢復數據。JDBCJobStore保存數據到數據庫,保證數據的可恢復性,但性能較差且配置復雜。


兩種常見的任務存儲方式
  • QuartzScheduler.scheduleJob(JobDetail, Trigger)源碼
 public Date scheduleJob(JobDetail jobDetail,
            Trigger trigger) throws SchedulerException {
        // 檢查調度器是否開啟,如果關閉則throw異常到上層
        validateState();
        ......
        // 獲取trigger首次觸發job的時間,以此時間為起點,每隔一段指定的時間觸發job
        Date ft = trig.computeFirstFireTime(cal);

        if (ft == null) {
            throw new SchedulerException(
                    "Based on configured schedule, the given trigger '" + trigger.getKey() + "' will never fire.");
        }

        // 把job和trigger注冊進調度器的jobStore
        resources.getJobStore().storeJobAndTrigger(jobDetail, trig);
        // 通知job監聽者
        notifySchedulerListenersJobAdded(jobDetail);                
        // 通知調度器線程
        notifySchedulerThread(trigger.getNextFireTime().getTime());
        // 通知trigger監聽者
        notifySchedulerListenersSchduled(trigger);

        return ft;
    }
  • QuartzScheduler.start()源碼
public void start() throws SchedulerException {
        ......
        // 這句最關鍵,作用是使調度器線程跳出一個無限循環,開始輪詢所有trigger觸發job
        // 原理詳見“如何采用多線程進行任務調度”
        schedThread.togglePause(false);
        ......
    }

 

 
       


免責聲明!

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



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