1. 程式人生 > >【轉載】SpringBoot整合Quartz定時任務

【轉載】SpringBoot整合Quartz定時任務

Quartz是完全基於Java的,可用於進行定時任務排程的開源框架,Scheduler是Quartz的大腦,所有任務都是由它來控制。那什麼時候用到Quartz呢,比如現在寫一個介面,公司需要每10分鐘呼叫一次,我們就可以用Quartz。

1.新增SpringBoot整合Quartz所需依賴

pom.xml檔案中新增如下配置:

<dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency><!-- 該依賴必加,裡面有sping對schedule的支援 -->
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>

2.新增QuartzConfig配置

import com.foresealife.dbaas.task.TestOneJob;
import com.foresealife.dbaas.task.TestTwoJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration
public class QuartzConfig {

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(){
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setOverwriteExistingJobs(true);
        //任務排程監聽類
        //factory.setGlobalTriggerListeners(triggerListenerLogMonitor());
        return factory;
    }

//    @Bean
//    public TriggerListenerLogMonitor triggerListenerLogMonitor() {
//        return new TriggerListenerLogMonitor();
//    }


    @Bean
    public Scheduler scheduler(){
        Scheduler scheduler = schedulerFactoryBean().getScheduler();
        //新增同步任務
        addTestOneJob(scheduler);
        addTestTwoJob(scheduler);
        return scheduler;
    }

    private void addTestOneJob(Scheduler scheduler){
        String startJob = "true";//是否開始
        String jobName = "TestOneJob";
        String jobGroup = "TestOneJob";
        String cron = "0/10 * * * * ?";//定時的時間設定
        String className = TestOneJob.class.getName();
        if (startJob != null && startJob.equals("true")) {
            addCommonCronJob(jobName, jobGroup, cron, scheduler, className);
        } else {
            deleteCommonJob(jobName, jobGroup, scheduler);
        }
    }

    private void addTestTwoJob(Scheduler scheduler){
        String startJob = "true";//是否開始
        String jobName = "TestTwoJob";
        String jobGroup = "TestTwoJob";
        String cron = "0/2 * * * * ?";//定時的時間設定
        String className = TestTwoJob.class.getName();
        if (startJob != null && startJob.equals("true")) {
            addCommonCronJob(jobName, jobGroup, cron, scheduler, className);
        } else {
            deleteCommonJob(jobName, jobGroup, scheduler);
        }
    }

    private void deleteCommonJob(String jobName, String jobGroup, Scheduler scheduler) {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        try {
            scheduler.pauseJob(jobKey);//先暫停任務
            scheduler.deleteJob(jobKey);//再刪除任務
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    private void addCommonCronJob(String jobName, String jobGroup, String cron, Scheduler scheduler, String className) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
            //任務觸發
            Trigger checkExist = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (checkExist == null) {
                JobDetail jobDetail = null;
                jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(className))
                        .requestRecovery(true)//當Quartz服務被中止後,再次啟動或叢集中其他機器接手任務時會嘗試恢復執行之前未完成的所有任務
                        .withIdentity(jobName, jobGroup)
                        .build();
                jobDetail.getJobDataMap().put("jobName", jobName);
                jobDetail.getJobDataMap().put("jobGroup", jobGroup);
                CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
                /*withMisfireHandlingInstructionDoNothing
                ——不觸發立即執行
                ——等待下次Cron觸發頻率到達時刻開始按照Cron頻率依次執行
                withMisfireHandlingInstructionIgnoreMisfires
                ——以錯過的第一個頻率時間立刻開始執行
                ——重做錯過的所有頻率週期後
                ——當下一次觸發頻率發生時間大於當前時間後,再按照正常的Cron頻率依次執行
                withMisfireHandlingInstructionFireAndProceed
                ——以當前時間為觸發頻率立刻觸發一次執行
                ——然後按照Cron頻率依次執行*/
                Trigger trigger = TriggerBuilder.newTrigger()
                        .withIdentity(jobName, jobGroup)
                        .withSchedule(cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires())
                        .build();
                scheduler.scheduleJob(jobDetail, trigger);
            } else {
                scheduler.deleteJob(JobKey.jobKey(jobName, jobGroup));
                addCommonCronJob(jobName, jobGroup, cron, scheduler, className);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

3.新增定時任務Job

Job可以理解為就是一個工作任務,程式碼中就是一個實現了org.quartz.Job或org.quartz.StatefulJob介面的java類。當Scheduler決定執行Job時,executeInternal()方法就會被執行。

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

@PersistJobDataAfterExecution
public class TestOneJob extends QuartzJobBean {
    private static final Logger LOG = LoggerFactory.getLogger(TestOneJob.class);
    private int count;

    @Override
    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {
        this.count++;
        LOG.info("{}Hi, I am still alive!============="+this.count);
        context.getJobDetail().getJobDataMap().put("count",this.count);
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

@PersistJobDataAfterExecution
public class TestTwoJob extends QuartzJobBean {
    private static final Logger LOG = LoggerFactory.getLogger(TestOneJob.class);
    private int count;

    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {
        this.count++;
        LOG.info("{}Hi, I am still alive too!============="+this.count);
        context.getJobDetail().getJobDataMap().put("count",this.count);
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

注:如果需要持久化Job狀態,如成員變數的值,則可以新增@PersistJobDataAfterExecution註解。

4.新增任務排程的監聽器

import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.util.Date;
 
/**
 * 任務排程監聽類
 */
@Component
class TriggerListenerLogMonitor implements TriggerListener {
 
 
    @Override
    public String getName() {
        return "TriggerListenerLogMonitor";
    }
 
    //當與監聽器相關聯的 Trigger 被觸發,Job 上的 execute() 方法將要被執行時,Scheduler 就呼叫這個方法。
    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        System.out.println("TriggerListenerLogMonitor類:" + context.getTrigger().getKey().getName() + " 被執行");
    }
 
    /**
     * 在 Trigger 觸發後,Job 將要被執行時由 Scheduler 呼叫這個方法。
     * TriggerListener 給了一個選擇去否決 Job 的執行。
     * 假如這個方法返回 true,這個 Job 將不會為此次 Trigger 觸發而得到執行。
     */
    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        return false;
    }
 
    /**
     * Scheduler 呼叫這個方法是在 Trigger 錯過觸發時。
     * 如這個方法的 JavaDoc 所指出的,你應該關注此方法中持續時間長的邏輯:
     *      在出現許多錯過觸發的 Trigger 時,
     *      長邏輯會導致骨牌效應。
     *      你應當保持這上方法儘量的小。
     */
    @Override
    public void triggerMisfired(Trigger trigger) {
        System.out.println("Job錯過觸發");
    }
    /*多米諾骨牌效應(骨牌效應):該效應產生的能量是十分巨大的。
    這種效應的物理道理是:骨牌豎著時,重心較高,倒下時重心下降,倒下過程中,
    將其重力勢能轉化為動能,它倒在第二張牌上,這個動能就轉移到第二張牌上,
    第二張牌將第一張牌轉移來的動能和自已倒下過程中由本身具有的重力勢能轉化來的動能之和,
    再傳到第三張牌上......所以每張牌倒下的時候,具有的動能都比前一塊牌大,因此它們的速度一個比一個快,
    也就是說,它們依次推倒的能量一個比一個大*/
 
 
    /**
     * Trigger 被觸發並且完成了 Job 的執行時,Scheduler 呼叫這個方法。
     * 這不是說這個 Trigger 將不再觸發了,而僅僅是當前 Trigger 的觸發(並且緊接著的 Job 執行) 結束時。
     * 這個 Trigger 也許還要在將來觸發多次的。
     */
    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
        System.out.println("Job執行完畢,Trigger完成");
    }
}

注:在QuartzConfig中注入TriggerListenerLogMonitor,將上面的註釋程式碼去掉即可。

5.設定定時任務的時間

在QuartzConfig的addmyTestJob方法中設定cron,有一個很不錯的線上Cron表示式生成器,推薦給大家   點選開啟

*星號:可以理解為每的意思,每秒,每分,每天,每月,每年... ?問號:問號只能出現在日期和星期這兩個位置,表示這個位置的值不確定,每天3點執行,所以第六位星期的位置,我們是不需要關注的,就是不確定的值。同時:日期和星期是兩個相互排斥的元素,通過問號來表明不指定值。比如,1月10日,比如是星期1,如果在星期的位置是另指定星期二,就前後衝突矛盾了。 -減號:表達一個範圍,如在小時欄位中使用“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 1 * * ?    每天1點觸發  0 0 12 * * ?    每天12點觸發  0 15 10 ? * *    每天10點15分觸發  0 15 10 * * ?    每天10點15分觸發  0 15 10 * * ? *    每天10點15分觸發 0 15 10 L * ?    每月最後一天的10點15分觸發  0 15 10 15 * ?    每月15號上午10點15分觸發  0 0-5 14 * * ?    每天下午的 2點到2點05分每分觸發  0 0 12 1/5 * ?    每月的第一個中午開始每隔5天觸發一次  0 0/2 * * * ?    每兩分鐘觸發一次