Quartz(四) 整合springboot跷叉、動態(tài)定時任務左敌、監(jiān)聽器

一 SchedulerFactoryBean

SchedulerFactoryBean這個類的真正作用提供了對org.quartz.Scheduler的創(chuàng)建與配置瘾蛋,并且會管理它的生命周期Spring同步
org.quartz.Scheduler: 調(diào)度器矫限。所有的調(diào)度都是由它控制
該類可以幫助我們設置Scheduler的一些屬性

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, JobFactory jobFactory) throws IOException {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        //可選,QuartzScheduler啟動時更新己存在的Job,這樣就不用每次修改targetObject后刪除qrtz_job_details表對應記錄
        factory.setOverwriteExistingJobs(true);
        factory.setAutoStartup(true); //設置自行啟動
        factory.setDataSource(dataSource);
        factory.setJobFactory(jobFactory);
        factory.setQuartzProperties(quartzProperties());
        return factory;
    }

注入Scheduler


    @Bean(name = "scheduler")
    public Scheduler scheduler() {
        return schedulerFactoryBean().getScheduler();
    }

任務工廠JobFactory

job實例都是由quartzJobFactory創(chuàng)建的,默認情況下我們在job實現(xiàn)類中注入spring對象都是無效的哺哼。因為對象并沒有被spring納入管理佩抹。
這時我可以通過AutowireCapableBeanFactory來幫助我們實現(xiàn)

@Component
public class JobFactory extends AdaptableJobFactory {
    /**
     * AutowireCapableBeanFactory接口是BeanFactory的子類,可以連接和填充那些生命周期不被Spring管理的已存在的bean實例
     */
    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;
 
    /**
     * 創(chuàng)建Job實例
     * @param bundle
     * @return
     * @throws Exception
     */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 實例化對象
        Object jobInstance = super.createJobInstance(bundle);
        // 進行注入(Spring管理該Bean)
        capableBeanFactory.autowireBean(jobInstance);
        //返回對象
        return jobInstance;
    }
}

方式二
本質(zhì)都是借助AutowireCapableBeanFactory來實現(xiàn)

//配置JobFactory,為quartz作業(yè)添加自動連接支持
    public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
            ApplicationContextAware {
        private transient AutowireCapableBeanFactory beanFactory;
        @Override
        public void setApplicationContext(final ApplicationContext context) {
            beanFactory = context.getAutowireCapableBeanFactory();
        }
        @Override
        protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
            final Object job = super.createJobInstance(bundle);
            beanFactory.autowireBean(job);
            return job;
        }
    }

讀取quartz.properties

    //從quartz.properties文件中讀取Quartz配置屬性
    @Bean
    public Properties quartzProperties() throws IOException {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();
    }


然后factory.setQuartzProperties(quartzProperties());

其他配置

        //可選,QuartzScheduler啟動時更新己存在的Job,這樣就不用每次修改targetObject后刪除qrtz_job_details表對應記錄
        factory.setOverwriteExistingJobs(true);
        factory.setAutoStartup(true); //設置自行啟動
        factory.setDataSource(dataSource);

setOverwriteExistingJobs:設置是否任意一個已定義的Job會覆蓋現(xiàn)在的Job取董。默認為false棍苹,即已定義的Job不會覆蓋現(xiàn)有的Job。(用于集群)
setAutoStartup:設置自行啟動
setDataSource:設置數(shù)據(jù)源茵汰,使用與項目統(tǒng)一數(shù)據(jù)源
setStartupDelay:項目啟動完成后枢里,等待x秒后開始執(zhí)行調(diào)度器初始化

二 動態(tài)定時任務

TriggerState

Trigger.TriggerState.NONE

  • STATE_BLOCKED 4 阻塞
  • STATE_COMPLETE 2 完成
  • STATE_ERROR 3 錯誤
  • STATE_NONE -1 不存在
  • STATE_NORMAL 0 正常
  • STATE_PAUSED 1 暫停
TriggerKey triggerKey = new TriggerKey(name, group);
scheduler.getTriggerState(triggerKey).name())

Scheduler

Scheduler的創(chuàng)建方式

SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
 
DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
Scheduler scheduler1=factory.getScheduler();

常用方法

  • 添加一個定時任務:
    Date scheduleJob(JobDetail jobDetail,Trigger trigger)
  • 修改一個定時任務,主要是更改trigger:
    Date rescheduleJob(String triggerName, String groupName, Trigger newTrigger)
  • 刪除一個定時任務蹂午,同時也會將于該jobDetail關(guān)聯(lián)的trigger一并刪除:
    boolean deleteJob(String jobName,String jobGroup)
  • 取得所有的jobDetail組
    String[] getJobGroupNames()
  • 取得某個group下的所有的jobDetail
    String[] getJobNames(String groupName)
  • 取得指定的jobDetail
    JobDetail getJobDetail(String jobName, String jobGroup)
  • 取得指定的jobDetail的所有的Trigger
    Trigger[] getTriggersOfJob(String jobName, String groupName)
  • 取得指定的Trigger
    Trigger getTrigger(String triggerName, String triggerGroup)

rescheduleJob

Trigger trigger = newTrigger()
    .withIdentity("newTrigger", "group1")
    .startNow()
    .build();

// tell the scheduler to remove the old trigger with the given key, and put the new one in its place
sched.rescheduleJob(triggerKey("oldTrigger", "group1"), trigger);

注意:sched.rescheduleJob(triggerKey("oldTrigger", "group1"), trigger); 這個方法返回一個Date.
如果返回 null說明替換失敗栏豺,原因就是舊觸發(fā)器沒有找到,所以新的觸發(fā)器也不會設置進去
替換失敗的原因一般有兩種

  • 一種情況是傳入的triggerKey沒有與之匹配的豆胸,
  • 另外一種情況就是舊觸發(fā)器的觸發(fā)時間已經(jīng)全部完成奥洼,在觸發(fā)完成后調(diào)度引擎會自動清除無用的觸發(fā)器,這種情況也會匹配不到晚胡。

pauseAll

暫停所有任務

scheduler.pauseAll();

pauseJob

暫停某個任務

        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null) {
            return;
        }
        scheduler.pauseJob(jobKey);

resumeAll

恢復所有任務

 scheduler.resumeAll();

resumeJob

恢復某個任務

        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null) {
            return;
        }
        scheduler.resumeJob(jobKey);

deleteJob

刪除某個任務

        JobKey jobKey = new JobKey(name, group);
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null) {
            return;
        }
        scheduler.deleteJob(jobKey);

deleteJob

移除一個任務

            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            // 停止觸發(fā)器
            scheduler.pauseTrigger(triggerKey);
            // 移除觸發(fā)器
            scheduler.unscheduleJob(triggerKey);
            // 刪除任務
            scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));

GroupMatcher

分組匹配

        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);

三 監(jiān)聽器

分類

  • JobListener
  • TriggerListener
  • SchedulerListener

概念

  • 全局監(jiān)聽器
  • 非全局監(jiān)聽器(只能接收到在其上注冊的Job或Trigger的事件灵奖,不在其上注冊的Job或Trigger則不會進行監(jiān)聽)

JobListener

public interface JobListener {

    public String getName();

    public void jobToBeExecuted(JobExecutionContext context);

    public void jobExecutionVetoed(JobExecutionContext context);

    public void jobWasExecuted(JobExecutionContext context,JobExecutionException jobException);

}
  1. getName方法:用于獲取該JobListener的名稱
  2. jobToBeExecuted方法:Scheduler在JobDetail將要被執(zhí)行時調(diào)用這個方法估盘。
  3. jobExecutionVetoed方法:(這個方法正常情況下不執(zhí)行,但是如果當TriggerListener中的vetoJobExecution方法返回true時,那么執(zhí)行這個方法; 需要注意的是 如果方法(23執(zhí)行 那么(2),(4)這個倆個方法不會執(zhí)行,因為任務被終止了嘛.)
  4. jobWasExecuted方法:Scheduler在JobDetail被 執(zhí)行之后調(diào)用這個方法

全局監(jiān)聽

        // 創(chuàng)建并注冊一個全局的Job Listener
        scheduler.getListenerManager().addJobListener(new SimpleJobListener(), EverythingMatcher.allJobs());

局部監(jiān)聽

        // 創(chuàng)建并注冊一個指定任務的Job Listener
        scheduler.getListenerManager().addJobListener(new SimpleJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("HelloWorld1_Job", "HelloWorld1_Group")));

將同一任務組的任務注冊到監(jiān)聽器中

scheduler.getListenerManager().addJobListener(new SimpleJobListener(), GroupMatcher.jobGroupEquals("HelloWorld2_Group"));

將兩個任務組的任務注冊到監(jiān)聽器中

scheduler.getListenerManager().addJobListener(new SimpleJobListener(), OrMatcher.or(GroupMatcher.jobGroupEquals("HelloWorld1_Group"), GroupMatcher.jobGroupEquals("HelloWorld2_Group")));

TriggerListener

任務調(diào)度過程中瓷患,與觸發(fā)器Trigger相關(guān)的事件包括:觸發(fā)器觸發(fā)、觸發(fā)器未正常觸發(fā)忿檩、觸發(fā)器完成等尉尾。TriggerListener的接口如下:

public interface TriggerListener {

    public String getName();

    public void triggerFired(Trigger trigger, JobExecutionContext context);

    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);

    public void triggerMisfired(Trigger trigger);

    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            int triggerInstructionCode);
}
  1. getName方法:用于獲取觸發(fā)器的名稱
  2. triggerFired方法:當與監(jiān)聽器相關(guān)聯(lián)的Trigger被觸發(fā)爆阶,Job上的execute()方法被執(zhí)行時燥透,Scheduler就調(diào)用該方法苫纤。
  3. vetoJobExecution方法:在 Trigger 觸發(fā)后畔规,Job 要被執(zhí)行時由 Scheduler 調(diào)用這個方法荡短。TriggerListener 給了一個選擇否決 Job 的執(zhí)行敛劝。假如這個方法返回 true继薛,這個 Job 將不會為此次 Trigger 觸發(fā)而得到執(zhí)行乞娄。
  4. triggerMisfired方法:Scheduler 調(diào)用這個方法是在 Trigger 錯過觸發(fā)時惧互。你應該關(guān)注此方法中持續(xù)時間長的邏輯:在出現(xiàn)許多錯過觸發(fā)的 Trigger 時鳍刷,長邏輯會導致骨牌效應鱼的。你應當保持這上方法盡量的小理盆。
  5. triggerComplete方法:Trigger 被觸發(fā)并且完成了 Job 的執(zhí)行時,Scheduler 調(diào)用這個方法凑阶。

簡單實現(xiàn)

package org.ws.quartz.test3;

import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.Trigger.CompletedExecutionInstruction;
import org.quartz.TriggerListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimpleTriggerListener implements TriggerListener{
    
    private static Logger logger = LoggerFactory.getLogger(SimpleTriggerListener.class);
    
    private String name;
    
    public SimpleTriggerListener(String name) {
        this.name = name;
    }
    
    @Override
    public String getName() {
        return name;
    }

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        String triggerName = trigger.getKey().getName();
        logger.info(triggerName + " was fired");
    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        String triggerName = trigger.getKey().getName();
        logger.info(triggerName + " was not vetoed");
        return false;
    }

    @Override
    public void triggerMisfired(Trigger trigger) {
        String triggerName = trigger.getKey().getName();
        logger.info(triggerName + " misfired");
    }

    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            CompletedExecutionInstruction triggerInstructionCode) {
        String triggerName = trigger.getKey().getName();
        logger.info(triggerName + " is complete");
    }
}

各種注冊

        // 創(chuàng)建并注冊一個全局的Trigger Listener
        scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener("SimpleTrigger"), EverythingMatcher.allTriggers());
        
        // 創(chuàng)建并注冊一個局部的Trigger Listener
//        scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener("SimpleTrigger"), KeyMatcher.keyEquals(TriggerKey.triggerKey("HelloWord1_Job", "HelloWorld1_Group")));
        
        // 創(chuàng)建并注冊一個特定組的Trigger Listener
//        scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener("SimpleTrigger"), GroupMatcher.groupEquals("HelloWorld1_Group"));

SchedulerListener

SchedulerListener會在Scheduler的生命周期中關(guān)鍵事件發(fā)生時被調(diào)用猿规。與Scheduler有關(guān)的事件包括:增加一個job/trigger,刪除一個job/trigger宙橱,scheduler發(fā)生嚴重錯誤姨俩,關(guān)閉scheduler等蘸拔。

public interface SchedulerListener {

    public void jobScheduled(Trigger trigger);

    public void jobUnscheduled(String triggerName, String triggerGroup);

    public void triggerFinalized(Trigger trigger);

    public void triggersPaused(String triggerName, String triggerGroup);

    public void triggersResumed(String triggerName, String triggerGroup);

    public void jobsPaused(String jobName, String jobGroup);

    public void jobsResumed(String jobName, String jobGroup);

    public void schedulerError(String msg, SchedulerException cause);

    public void schedulerStarted();

    public void schedulerInStandbyMode();

    public void schedulerShutdown();

    public void schedulingDataCleared();
}
  1. jobScheduled方法:用于部署JobDetail時調(diào)用
  2. jobUnscheduled方法:用于卸載JobDetail時調(diào)用
  3. triggerFinalized方法:當一個 Trigger 來到了再也不會觸發(fā)的狀態(tài)時調(diào)用這個方法。除非這個 Job 已設置成了持久性环葵,否則它就會從 Scheduler 中移除调窍。
  4. triggersPaused方法:Scheduler 調(diào)用這個方法是發(fā)生在一個 Trigger 或 Trigger 組被暫停時。假如是 Trigger 組的話张遭,triggerName 參數(shù)將為 null邓萨。
  5. triggersResumed方法:Scheduler 調(diào)用這個方法是發(fā)生成一個 Trigger 或 Trigger 組從暫停中恢復時。假如是 Trigger 組的話帝璧,假如是 Trigger 組的話先誉,triggerName 參數(shù)將為 null。參數(shù)將為 null的烁。
  6. jobsPaused方法:當一個或一組 JobDetail 暫停時調(diào)用這個方法褐耳。
  7. jobsResumed方法:當一個或一組 Job 從暫停上恢復時調(diào)用這個方法。假如是一個 Job 組渴庆,jobName 參數(shù)將為 null铃芦。
  8. schedulerError方法:在 Scheduler 的正常運行期間產(chǎn)生一個嚴重錯誤時調(diào)用這個方法。
  9. schedulerStarted方法:當Scheduler 開啟時襟雷,調(diào)用該方法
  10. schedulerInStandbyMode方法: 當Scheduler處于StandBy模式時刃滓,調(diào)用該方法
  11. schedulerShutdown方法:當Scheduler停止時,調(diào)用該方法
  12. schedulingDataCleared方法:當Scheduler中的數(shù)據(jù)被清除時耸弄,調(diào)用該方法咧虎。

注冊于注銷

        // 創(chuàng)建SchedulerListener
        scheduler.getListenerManager().addSchedulerListener(new SimpleSchedulerListener());
        
        // 移除對應的SchedulerListener
//        scheduler.getListenerManager().removeSchedulerListener(new SimpleSchedulerListener());

參考

  1. SpringBoot整合Quartz作為調(diào)度中心完整實用例子
  2. SpringBoot集成Quartz動態(tài)定時任務(解決quartz的job無法注入spring對象的問題)
  3. spring boot2 整合(四)定時任務Scheduled || Quartz并持久化
  4. http://blog.sina.com.cn/s/blog_45411cf30100po3j.html
  5. Quartz Scheduler 更新任務觸發(fā)器
  6. Quartz使用(4) - Quartz監(jiān)聽器Listerner
  7. Quartz JobListener 任務監(jiān)聽器
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市计呈,隨后出現(xiàn)的幾起案子砰诵,更是在濱河造成了極大的恐慌,老刑警劉巖捌显,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茁彭,死亡現(xiàn)場離奇詭異,居然都是意外死亡扶歪,警方通過查閱死者的電腦和手機理肺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來善镰,“玉大人妹萨,你說我怎么就攤上這事§牌郏” “怎么了乎完?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長竣稽。 經(jīng)常有香客問我囱怕,道長霍弹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任娃弓,我火速辦了婚禮典格,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘台丛。我一直安慰自己耍缴,他們只是感情好,可當我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布挽霉。 她就那樣靜靜地躺著防嗡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侠坎。 梳的紋絲不亂的頭發(fā)上蚁趁,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機與錄音实胸,去河邊找鬼他嫡。 笑死,一個胖子當著我的面吹牛庐完,可吹牛的內(nèi)容都是我干的钢属。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼门躯,長吁一口氣:“原來是場噩夢啊……” “哼淆党!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起讶凉,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤染乌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缀遍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慕匠,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡饱须,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年域醇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蓉媳。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡譬挚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出酪呻,到底是詐尸還是另有隱情减宣,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布玩荠,位于F島的核電站漆腌,受9級特大地震影響贼邓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜闷尿,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一塑径、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧填具,春花似錦统舀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盟广,卻和暖如春闷串,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背筋量。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工窿克, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毛甲。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓年叮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親玻募。 傳聞我的和親對象是個殘疾皇子只损,可洞房花燭夜當晚...
    茶點故事閱讀 45,047評論 2 355