Loading...

Quartz 的基本应用

Quartz 简介

Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。

Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。

Quartz 允许程序开发人员根据时间的间隔来调度作业。

Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。

核心概念

  • Job 表示一个工作,要执行的具体内容。此接口中只有一个方法。每一个 job 类都必须实现该接口。

    // com.quartz.job
    void execute(JobExecutionContext context);
  • JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。

  • Trigger 触发器,代表一个调度参数的配置,它主要包含两种触发器: SimpleTriggerCronTrigger

  • Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

设计模式

  • Builder 模式
  • Factory 模式
  • 组件模式
  • 链式编程

体系结构

Quartz体系结构图

常用API

  • Scheduler 是用于与调度程序交互的主程序接口。

    Scheduler 调度程序-任务执行计划表,只有安排进执行计划的任务Job(通过scheduler.schedulejob方法安排进执行计划),当它预先定义的执行时间到了(任务触发trigger),该任务才会执行。

  • Job 是我们预先定义希望在未来时间能被调度程序执行的任务类。

  • JobDetail 使用 JobDetail 来定义任务的实例, JobDetail 实例是通过 JobBuilder 类创建的。

  • JobDataMap 可以包含不限量的(序列化的)数据对象,在job实例执行的时候,我们可以使用其中的数据; JobDataMap 是 Java Map 接口的一个实现,并且额外增加了一些便于存取基本类型的数据的方法。

  • Trigger 触发器,Trigger 对象是用来触发执行 Job 的。当调度一个 job 时,我们实例一个触发器,然后调度它的属性来满足 job 执行的条件。它表明任务在什么时候执行。

  • JobBuilder 用于声明一个任务实例,也可以用于定义一个该任务的详情,比如任务名、组名等,这个声明的实例将会作为一个世纪执行的任务。

  • TriggerBuilder 触发器创建器,用于创建触发器 trigger 实例。

  • JobListener 、 TriggerListener 、 SchedulerListener 监听器,用于对组件的监听。

使用实例

准备工作

创建测试项目 quartz-demo (此处示例使用 springboot 进行演示)。

完整依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xfc</groupId>
    <artifactId>quartz-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>quartz-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

任务调度实例

创建任务类 HelloJob.java

package com.xfc.quartz.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;

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

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext context) {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String dateStr = sdf.format(date);
        System.out.println("Hello World, Current Time Is : " + dateStr);
        System.out.println("-----------------------job info-----------------------");
        JobKey jobKey = context.getJobDetail().getKey();
        System.out.println("jobKey.getName() = " + jobKey.getName());
        System.out.println("jobKey.getGroup() = " + jobKey.getGroup());
        System.out.println("任务类名称 = " + context.getJobDetail().getJobClass().getName());
    }
}

创建测试类 SchedulerTest.java

package com.xfc.quartz.test;

import com.xfc.quartz.job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class SchedulerTest {

    public static void main(String[] args) throws SchedulerException {
        // 1. 创建调度器(Scheduler)从工厂中获取调度实例(默认:实例化new StdSchedulerFactory();)
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // 2. 创建任务实例(JobDetail)
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)// 加载任务类
                .withIdentity("job1", "group1")// 任务名称,任务组名
                .build();

        // 3. 创建触发器(Trigger)
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")// 触发器名称,触发器组名
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).withRepeatCount(5))// 每3秒调用1次,共5次
                .build();

        // 4. 调度器关联任务和触发器
        scheduler.scheduleJob(jobDetail, trigger);

        // 5. 启动调度器
        scheduler.start();
    }
}

运行 main 方法得到如下结果:

Hello World, Current Time Is : 2020-08-31 03:07:51

Hello World, Current Time Is : 2020-08-31 03:07:54

Hello World, Current Time Is : 2020-08-31 03:07:57

Hello World, Current Time Is : 2020-08-31 03:08:00

Hello World, Current Time Is : 2020-08-31 03:08:03

Hello World, Current Time Is : 2020-08-31 03:08:06

Job & JobDetail

  • Job 工作任务调度的接口,任务类需要实现该接口,该接口中定义 execute 方法,类似 JDK 提供的 TimeTask 的 run 方法。
  • Job 实例在 Quartz 中的生命周期:每次调度执行 Job 时,它在调度 excute 方法前会创建一个新的 Job 实例,当调度完成后,关联的 Job 实例会被释放,释放的实例会被垃圾回收机制回收。
  • JobDetail 为 Job 实例提供了许多设置属性,以及 JobDetailMap 的成员变量属性,它用来储存特定的 job 实例的状态信息,调度器需要借助 JobDetail 对象来添加 job 实例。
  • jobDetail 重要属性:name、group、jobClass、jobDataMap。

JobExcutionContext

  • 当 Scheduler 调用一个 job ,就会将 JobExcutionContext 传递给 Job 的 excute() 方法。
  • Job 能通过 JobExcutionContext 对象访问到 Quartz 运行时候的环境以及 job 本身的明细数据。

JobDataMap

  1. 使用 Map 获取

    • JobDataMap 可以用来装载任何可序列化的数据对象,当 job 实例对象被执行时这些参数对象会传递给它。

    • JobDataMap 实现了 JDK 的 Map 接口,并且添加了非常方便的方法用来存储基本数据类型。

      通过 TriggerBuilder.newTrigger().usingJobData("key", "value")JobBuilder.newJob(HelloJob.class).usingJobData("key", "value") 方式可以放入自定义参数值。

      通过 JobExecutionContext.getJobDetail().getJobDataMap() 方法可以获取到 JobDetail 中的 JobDataMap 信息。

      同样地,也可以通过 JobExecutionContext.getTrigger().getJobDataMap() 方法获取到 Trigger 中的参数值信息。

  2. Job 实现类中添加 setter 方法对应 JobDataMap 的键值,Quartz 框架默认的 JobFactory 实现类在初始化 job 实例对象时会自动地调用这些 setter 方法。

    注:如果遇到同名的 key 值,同名的内容会被后赋值者覆盖。

有状态的 Job 和无状态的 Job

有状态的 Job 可以理解为多次 Job 调用期间可以持有一些状态信息,这些状态信息存储在 JobDataMap 中,而默认的无状态 Job 每一次调用时都会创建一个新的 JobDataMap 。

修改示例代码如下:

package com.xfc.quartz.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.PersistJobDataAfterExecution;

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

@PersistJobDataAfterExecution// 有状态job,多次调用job时,会对job进行持久化,即在以下示例中,带有此注解时,key1值在任务每次执行时都会更新
public class HelloJob implements Job {

    private String key1;
    private String key2;

    // Quartz框架使用usingJobData()放置参数时,会默认调用对应同名的setter方法
    public void setKey1(String key1) {
        this.key1 = key1;
    }

    public void setKey2(String key2) {
        this.key2 = key2;
    }

    @Override
    public void execute(JobExecutionContext context) {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String dateStr = sdf.format(date);
        System.out.println("Hello World, Current Time Is : " + dateStr);
        System.out.println("-----------------------job info-----------------------");
        JobKey jobKey = context.getJobDetail().getKey();
        System.out.println("jobKey.getName() = " + jobKey.getName());
        System.out.println("jobKey.getGroup() = " + jobKey.getGroup());
        System.out.println("任务类名称 = " + context.getJobDetail().getJobClass().getName());
        System.out.println("key1 = " + key1);
        System.out.println("key2 = " + key2);
        // 修改值并存储到JobDataMap中
        context.getJobDetail().getJobDataMap().put("key1", key1 + "_append_str");
    }
}
package com.xfc.quartz.test;

import com.xfc.quartz.job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class SchedulerTest {

    public static void main(String[] args) throws SchedulerException {
        // 1. 创建调度器(Scheduler)从工厂中获取调度实例(默认:实例化new StdSchedulerFactory();)
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        // 2. 创建任务实例(JobDetail)
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)// 加载任务类
                .withIdentity("job1", "group1")// 任务名称,任务组名
                .usingJobData("key1", "value1")
                .build();

        // 3. 创建触发器(Trigger)
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")// 触发器名称,触发器组名
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).withRepeatCount(5))// 每3秒调用1次,共5次
                .usingJobData("key2", "value2")
                .build();

        // 4. 调度器关联任务和触发器
        scheduler.scheduleJob(jobDetail, trigger);

        // 5. 启动调度器
        scheduler.start();
    }
}

Trigger

Quartz 有一些不同的触发器,常用有两种: SimpleTriggerCronTrigger

  • jobKey 表示 job 实例的标识,触发器被触发时,其指定的 job 实例会被执行。
  • startTime 表示触发器的时间表,第一次开始被触发的时间,其数据类型是 java.util.Date 。
  • endTime 表示触发器终止被触发的时间,其数据类型是 java.util.Date 。

获取 jobKey , startTime , endTime

context.getTrigger().getJobKey().getName();

context.getTrigger().getStartTime();

context.getTrigger().getEndTime();

SimpleTrigger

SimpleTrigger 触发器对于设置和使用是最为简单的一种 QuartzTrigger 。

它是为那种需要在特定日期或时间启动,且以一个可能的时间间隔重复执行多次的 job 而设计的。

注意:

  • SimpleTrigger 的属性有:开始时间、结束时间、重复次数和重复的时间间隔。
  • 重复次数属性的值可以为0、正整数或常量 SimpleTrigger.REPEAT_INDEFINITELY
  • 重复的时间间隔属性值必须大于0或长整型的正整数,以毫秒作为时间单位,当重复的时间间隔为0时,意味着与 Trigger 同时触发执行。
  • 如果有指定的结束时间属性值,则结束时间属性优先于重复次数属性。

CronTrigger

如果需要像日历一样按日程来触发任务,而非像 SimpleTrigger 间隔特定时间触发,CronTrigger 会是更优选择,因为它是基于日历的作业任务调度器。

  • Cron Expression(Cron表达式)

    Cron表达式被用来配置 CronTrigger 实例。Cron表达式是一个由7个子表达式组成的字符串,每个子表达式都描述了一个单独的日程细节,每个子表达式之间用空格分隔,它们分别表示:

    • Seconds
    • Minutes
    • Hours
    • Day-of-Month(一月中的某一天)
    • Month
    • Day-of-Week(一周中的某一天)
    • Year

    在线工具:https://cron.qqe2.com

配置、资源 SchedulerFactory

Scheduler 的创建方式

StdSchedulerFactory:Quartz 默认的 SchedulerFactory 。

  • 使用一组参数(java.util.Properties)来创建和初始化 Quartz 调度器。

  • 配置参数一般存储在 quartz.properties 文件中。

  • 调用 getScheduler 方法就能创建和初始化调度器对象。

    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    Scheduler scheduler = schedulerFactory.getScheduler();
    // ...
    scheduler.start();
    // scheduler.standby();// 任务挂起
    // scheduler.shutdown();// 关闭任务,布尔参数,是否等待所有任务执行后才关闭

Quartz.properties

  • 调度器属性

    org.quartz.scheduler.instanceName 属性用来区分特定的调度器实例。

    org.quartz.scheduler.instanceId 和前者一样,也允许任何字符串,但这个值必须在所有调度器实例中是唯一的,尤其是在一个集群环境中,作为集群唯一的 key 值。

  • 线程池属性

    • threadCount

      处理 Job 线程的个数,至少为1 ,但不建议超过100个。

    • threadPriority

      线程的优先级,优先级别高的线程比优先级别低的线程优先得到执行,最小为1,最大为10,默认为5。

    • org.quartz.threadPool.class

      它是一个实现了 org.quartz.spi.ThreadPool 接口的类,Quartz 自带的线程实现类是 org.quartz.smpl.SimpleThreadPool

  • 作业存储设置

    描述了在调度器实例的生命周期中, Job 和 Trigger 信息是如何被存储的。

  • 插件配置

其他

除去在配置文件中配置相关属性外,也可以通过 springboot 的配置类。

示例:

package com.jason.quartz.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.sql.DataSource;
import java.util.Properties;

/**
 * 定时任务配置
 *
 * @author Jason Chen
 */
@Configuration
public class ScheduleConfig {
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);

        // quartz参数
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        // 线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "20");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        // JobStore配置
        prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        // 集群配置
        prop.put("org.quartz.jobStore.isClustered", "true");
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");

        // sqlserver 启用
        // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
        factory.setQuartzProperties(prop);

        factory.setSchedulerName("RuoyiScheduler");
        // 延时启动
        factory.setStartupDelay(1);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        // 可选,QuartzScheduler
        // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
        factory.setOverwriteExistingJobs(true);
        // 设置自动启动,默认为true
        factory.setAutoStartup(true);

        return factory;
    }
}

Quartz 监听器

概念

Quartz 的监听器用于当任务调度你所关注的事件发生时,能够及时获取这一事件的通知。Quartz 的监听器主要有 JobListenerTriggerListenerSchedulerListener 三种,它们分别表示任务、触发器、调度器对应的监听器。

JobListener

在调度过程中,与任务相关的 Job 事件包括:Job 将要执行时的提示和 Job 执行完成后的提示。

使用:

  1. 创建一个实现 JobListener 接口的监听器类,并重写其方法。

    • getName() 获取该 JobListener 的名称。
    • jobToBeExecuted() 在 JobDetail 将要执行时调用。
    • jobExecutionVetoed() 在 JobDetail 即将被执行但被 TriggerListener 否决时调用。
    • jobWasExecuted() 在 JobDetail 被执行之后调用。
  2. 创建并注册 JobListener

    // 创建并注册JobListener监听所有任务
    scheduler.getListenerManager().addJobListener(new MyJobListener(), EveryThingMatcher.allJobs());
    // 对指定的任务进行监听
    scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("job1", "group1")));

TriggerListener

在调度过程中,与触发器 Trigger 相关的事件包括:触发器触发、触发器未正常触发、触发器完成等。

  • getName() 用于获取触发器名称。
  • triggerFired() 当与监听器相关联的 Trigger 被触发, Job 上的 excute() 方法被执行时调用。
  • vetoJobException() 在 Trigger 被触发后, Job 将要被执行时由 Scheduler 调用这个方法。( TriggerListener 可以否决一个 Job 的执行)。
  • triggerMisfired() 在 Trigger 错过被触发时调用。
  • triggerComplete() Trigger 被触发并且执行 Job 完成时调用。

SchedulerListener

SchedulerListener 会在 Scheduler 的生命周期中关键事件发生时调用。与 Scheduler 有关的事件包括:增加或删除一个 job/trigger , scheduler 发生严重错误,关闭 scheduler 等。

  1. jobScheduler

    用于部署 JobDetail 时调用。

  2. jobUnscheduled

    用于卸载 JobDetail 时调用。

  3. triggerFinalized

    当一个 Trigger 进入再也不会触发的状态时调用这个方法。除非这个 Job 已设置成了持久性,否则它就会从 Scheduler 中移出。

  4. triggersPaused

    Scheduler 掉哦那个这个方法是发生在一个 Trigger 或 Trigger 组被暂停时。假如是 Trigger 组的话, triggerName 参数将为 null 。

  5. triggersResumed

    Scheduler 调用这个方法是发生在一个 Trigger 或 Trigger 组从暂停中恢复时。假如是 Trigger 组的话, triggerName 参数将为 null 。

  6. jobsPaused

    当一个或一组 JobDetail 暂停时调用这个方法。

  7. jobsResumed

    当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组, jobName 参数将为 null 。

  8. schedulerError

    在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。

  9. schedulerStarted

    当 Scheduler 开启时调用这个方法。

  10. schedulerInStandbyMode

    当 Scheduler 处于 StandBy 模式时调用这个方法。

  11. schedulerShutdown

    当 Scheduler 停止时调用这个方法。

  12. schedulingDataCleared

    当 Scheduler 中的数据被清除时调用这个方法。

参考

版权声明

本文链接:https://www.chinmoku.cc/java/extension/quartz-tutorial/

本博客中的所有内容,包括但不限于文字、图片、音频、视频、图表和其他可视化材料,均受版权法保护。未经本博客所有者书面授权许可,禁止在任何媒体、网站、社交平台或其他渠道上复制、传播、修改、发布、展示或以任何其他方式使用此博客中的任何内容。

Press ESC to close