1. 程式人生 > 其它 >定時任務——quartz

定時任務——quartz

基本使用

官網:http://www.quartz-scheduler.org/

先理解quartz的模型關係
  一個排程器可以排程多個觸發器
  一個觸發器只可以觸發一個任務詳情
  一個任務詳情可以被多個觸發器呼叫
  一個Job可以被多個任務詳情包含(一般情況下不會用到這種場景)

  一般會定義兩種觸發器,一種是自動觸發器,一種是手動觸發器


跟著官網文件走,匯入資料庫,然後就可以進行案例實踐了
這裡引入一個logback配置,方便檢視日誌資訊

<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="false" scan="false">
    <!--  Console log output  -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!--  WARN級別  -->
    <root level="WARN">
        <appender-ref ref = "console"/>
    </root>
</configuration>

官網上的Demo

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Date;
import java.util.StringJoiner;

/**
 * Author: gzy
 * Date: 2022/11/22-10:52
 * Description:
 *  quartz中的job都要實現Job介面
 *  quartz所有的定時任務都要實現Job介面
 */
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        StringJoiner outStr = new StringJoiner(" ")
                .add("Hello.execute")
                .add(new Date().toString())
                .add("======" + Thread.currentThread().getName())
                .add(jobExecutionContext.getTrigger().getKey().getName());        // 觸發器的名稱
        // System.out.println("HelloJobExecute: " + new Date() + "======" + Thread.currentThread().getName());
        System.out.println(outStr);
    }
}
/**
 * Author: gzy
 * Date: 2022/11/22-10:41
 * Description:
 *  排程器 ->>> 觸發器 ->>> 任務詳情(任務)
 *      為什麼中間有一層觸發器
 */
public class QuartzTest {

    public static void main(String[] args) {

        try {
            // Grab the Scheduler instance from the Factory
            // 排程器工廠獲取一個預設的排程器
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // and start it off
            // 啟動
            scheduler.start();

            // define the job and tie it to our HelloJob class
            //
            JobDetail job = newJob(HelloJob.class)
                    .withIdentity("job1", "group1")
                    .build();

            // Trigger the job to run now, and then repeat every 40 seconds
            // 觸發 jobDetail 觸發器
            Trigger trigger = newTrigger()
                    .withIdentity("trigger1", "group1")
                    .startNow()         // 啟動
                    .withSchedule(simpleSchedule()          // 排程策略
                            .withIntervalInSeconds(5)      // 5s一次
                            .repeatForever())               // 永遠迴圈下去
                    .build();

            // Tell quartz to schedule the job using our trigger
            // job 被 排程器排程
            scheduler.scheduleJob(job, trigger);

            // 主執行緒先睡一下, 以看到程式的效果
            TimeUnit.SECONDS.sleep(20);

            // 關閉
            scheduler.shutdown();

        } catch (SchedulerException | InterruptedException se) {
            se.printStackTrace();
        }
    }
}

接下來的案例驗證排程器、觸發器、作業詳情之間的關係
兩個觸發器呼叫一個JobDetail

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import java.util.concurrent.TimeUnit;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

/**
 * Author: KongXiao
 * Date: 2022/11/22-10:41
 * Description:
 *  排程器 ->>> 觸發器 ->>> 任務詳情(任務)
 *      問什麼中間有一層觸發器
 *          一對多的關係 一個排程器 多個觸發器;    多個觸發器 一個任務詳情
 *          一個觸發器只能排程一個任務
 (          一個 Job 可以被多個 JobDetail 引用
 */
public class QuartzTest2 {

    public static void main(String[] args) {

        try {
            // Grab the Scheduler instance from the Factory
            // 排程器工廠獲取一個預設的排程器
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // and start it off
            // 啟動
            scheduler.start();

            // define the job and tie it to our HelloJob class
            //
            JobDetail job = newJob(HelloJob.class)
                    .withIdentity("job1", "group1")
                    .build();

            // Trigger the job to run now, and then repeat every 40 seconds
            // 觸發 jobDetail 觸發器
            Trigger trigger = newTrigger()
                    .withIdentity("trigger1", "group1")
                    .startNow()
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(1)
                            .repeatForever())
                    .build();

            Trigger trigger2 = newTrigger()
                    .withIdentity("trigger2", "group1")
                    .startNow()         // 啟動
                    .forJob("job1", "group1")       // 指定 Job name 和 group,指定要觸發的 job
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(3)
                            .repeatForever())
                    .build();


            // Tell quartz to schedule the job using our trigger
            // job 被 排程器排程
            scheduler.scheduleJob(job, trigger);
            scheduler.scheduleJob(trigger2);        // 如果是一個未被排程過的Job,使用 forJob的方式,然後排程器想要排程它,但是隻傳了一個觸發器,會出異常

            // 主執行緒先睡一下
            TimeUnit.SECONDS.sleep(3);

            // 關閉
            scheduler.shutdown();

        } catch (SchedulerException | InterruptedException se) {
            se.printStackTrace();
        }
    }
}

兩個排程器呼叫兩個作業詳情

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import java.util.concurrent.TimeUnit;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

/**
 * Author: KongXiao
 * Date: 2022/11/22-10:41
 * Description:
 *  排程器 ->>> 觸發器 ->>> 任務詳情(任務)
 *      問什麼中間有一層觸發器
 *          一對多的關係 一個排程器 多個觸發器;    多個觸發器 一個任務詳情
 *          一個觸發器只能排程一個任務
 *          一個 Job 可以被多個 JobDetail 引用
 *
 *  一個排程器對應倆觸發器,一個觸發器對應一個jobDetail,一個jobDetail可以被多個觸發器呼叫,一個JobDetail包含一個Job
 */
public class QuartzTest3 {

    public static void main(String[] args) {

        try {
            // Grab the Scheduler instance from the Factory
            // 排程器工廠獲取一個預設的排程器
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // and start it off
            // 啟動
            scheduler.start();

            // define the job and tie it to our HelloJob class
            //
            JobDetail job = newJob(HelloJob.class)
                    .withIdentity("job1", "group1")
                    .build();

            JobDetail job2 = newJob(HelloJob.class)         // 兩個JobDetail包含了一個Job
                    .withIdentity("job2", "group1")
                    .build();

            // Trigger the job to run now, and then repeat every 40 seconds
            // 觸發 jobDetail 觸發器
            Trigger trigger = newTrigger()
                    .withIdentity("trigger1", "group1")
                    .startNow()
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(1)
                            .repeatForever())
                    .build();

            Trigger trigger2 = newTrigger()
                    .withIdentity("trigger2", "group1")
                    .startNow()         // 啟動
                    .forJob("job2", "group1")       // 指定 Job name 和 group,指定要觸發的 job
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(3)
                            .repeatForever())
                    .build();


            // Tell quartz to schedule the job using our trigger
            // job 被 排程器排程
            scheduler.scheduleJob(job, trigger);
            scheduler.scheduleJob(job2, trigger2);      // 如果只傳入一個觸發器,會出異常
            // scheduler.scheduleJob(trigger2);   // org.quartz.JobPersistenceException: The job (group1.job2) referenced by the trigger does not exist.

            // 主執行緒先睡一下
            TimeUnit.SECONDS.sleep(3);

            // 關閉
            scheduler.shutdown();

        } catch (SchedulerException | InterruptedException se) {
            se.printStackTrace();
        }
    }
}

簡單理解一下JobDetail和Trigger的name屬性和group屬性

name屬性主要是用來方便標識的
group屬性主要用來方便管理和標識的
多個作業可以放在一個群組下面進行管理
可以不為group賦值,但是底層會為其賦一個 DEFAULT 值

只有一個JobDetail和一個Trigger時,可以不指定name和group,如下
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

import java.util.concurrent.TimeUnit;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

/**
 * Author: KongXiao
 * Date: 2022/11/22-10:41
 * Description:
 *  排程器 ->>> 觸發器 ->>> 任務詳情(任務)
 *      問什麼中間有一層觸發器
 *          一對多的關係 一個排程器 多個觸發器;    多個觸發器 一個任務詳情  一個觸發器只能排程一個任務
 *          一個 Job 可以被多個 JobDetail 引用
 *
 *
 *  JobDetail和Trigger的name和group的作用:
 *      name是用來標記的
 *      group是用來標記、方便管理的(比如,一個群組下也可以有多個job)
 *      可以不為 group賦值,那麼 group底層會賦 DEFAULT 值
 *
 *      如果只有一個觸發器、一個JobDetail,那麼可以不指定name和group,可以試一試, 那麼此時執行緒名是MD5加密的串
 *
 *
 *      那麼現在的模型大概是這樣的
 *          一個排程器可以呼叫多個觸發器
 *          一個觸發器只可以呼叫一個任務詳情
 *          一個任務詳情可以被多個觸發器觸發
 *          一個任務可以被多個任務詳情包含
 *
 *          一般情況下,不會使用多個JobDetail去包含一個Job的場景,會編寫兩種觸發器,一個自動觸發器,一個手動觸發器.
 */
public class QuartzTest4 {

    public static void main(String[] args) {

        try {
            // Grab the Scheduler instance from the Factory
            // 排程器工廠獲取一個預設的排程器
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // and start it off
            // 啟動
            scheduler.start();

            // define the job and tie it to our HelloJob class
            //
            JobDetail job = newJob(HelloJob.class)
                    // .withIdentity("job1", "group1")
                    .build();


            // Trigger the job to run now, and then repeat every 40 seconds
            // 觸發 jobDetail 觸發器
            Trigger trigger = newTrigger()
                   // .withIdentity("trigger1", "group1")
                    .startNow()
                    .withSchedule(simpleSchedule()
                            .withIntervalInSeconds(1)
                            // .repeatForever())
                            .withRepeatCount(1))            // 指定重複執行的次數
                    .build();



            // Tell quartz to schedule the job using our trigger
            // job 被 排程器排程
            scheduler.scheduleJob(job, trigger);

            // 主執行緒先睡一下
            TimeUnit.SECONDS.sleep(3);

            // 關閉
            scheduler.shutdown();

        } catch (SchedulerException | InterruptedException se) {
            se.printStackTrace();
        }
    }
}

幾個觸發器

CalendarIntervalTriggerImpl (org.quartz.impl.triggers)
SimpleTriggerImpl (org.quartz.impl.triggers)
DailyTimeIntervalTriggerImpl (org.quartz.impl.triggers)
CronTriggerImpl (org.quartz.impl.triggers)  重點、常用!!!

CronExpression cron表示式

Cron表示式生成網站 https://cron.qqe2.com/
...

傳參以及依賴注入

Job的需要一個無參構造方法才可以被例項化,Job的例項化不是我們建立的,是quartz去幫我們實現的

傳入一個Job.class到newJob中可以創建出一個Job的例項

在Job例項化的時候傳參可以從JobDetail和Trigger下手,呼叫usingJobData()方法,底層是個Map。在Job中取值,可以從Job上下文中呼叫 getJobDetail()/getTrigger().getJobDataMap().get(key);
getMergedJobDataMap()可以獲取合併後的資料,如果JobDetail和Trigger的key相同,那麼會以Trigger的key優先,獲取到的就是Trigger相應的值

Job中定義一個變數並定義相應的set方法,也可以獲取值

如果在Job中注入Spring中的Bean該怎麼操作呢?
Spring的Bean是由Spring管理的,而quartz是不認這個的,所以直接在Job中注入依賴是獲取不到值的
@Component
public class SpringDIUtils implements ApplicationContextAware {
    
    public static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

Job中獲取值
xxx = (xxx) SpringDIUtils.applicationContext.getBean(StringUtils.uncapitalize(xxx.class.getSimpleName()));

quartz的配置檔案
quartz的配置檔案是quartz.properties,如果不自行配置的話會預設使用自帶的配置檔案, 在 quartz的jar包的 org.quartz包下

# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#

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