栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Quartz 学习笔记(一)任务调度框架 | 核心组件 Job、Trigger 和 Scheduler | 入门案例 | 监听器

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Quartz 学习笔记(一)任务调度框架 | 核心组件 Job、Trigger 和 Scheduler | 入门案例 | 监听器

文章目录
  • 一、Quartz
  • 二、Quartz 运行环境
  • 三、Quartz 核心组件
  • 四、Quartz 设计模式
  • 五、Quartz 的体系结构
  • 六、Quartz 的几个常用 API
  • 七、Quartz 入门案例
  • 八、Quartz组件详情
    • 8.1 Job
    • 8.2 JobDetail
    • 8.3 任务的形参 JobExecutionContext
    • 8.4 任务的数据传递 JobDataMap
    • 8.5 有状态的Job和无状态的Job
    • 8.5 Trigger
      • 8.5.1 SimpleTrigger 触发器 使用案例
      • 8.5.2 CronTrigger 触发器
  • 九、Quartz 任务调度
    • 9.1 配置、资源 SchedulerFactory
    • 9.2 Scheduler的两种创建方式
      • 9.2.1 StdSchedulerFacotry
      • 9.2.2 DirectSchedulerFacotry(了解)
    • 9.3 Quartz 配置文件详解
      • 9.3.1 自定义 quartz.properties
      • 9.3.2 代码配置
  • 十、Quartz 监听器
    • 10.1 JobListener
    • 10.2 TriggerListener
    • 10.3 SchedulerListener

参考资料: 视频资料

一、Quartz

Quartz 官网:scheduler.org
Quartz 是 OpenSymphon 开源组织发布的开源项目,可以和J2EE 和 J2SE 应用程序相结合,也可以单独使用。

Quartz 是开源且具有丰富特性的“任务调度库”,能够集成任何的Java应用,小到独立的应用,大到电子商业系统。Quartz既能创建简单,也能创建复杂的任务调度,以执行上十、上百,甚至上万的任务。任务 job 被定义为标准的Java组件,能够执行任何想要实现的功能。
Quartz 调度框架包含许多企业级的任务如 JTA 事务、 集群的支持。

简而言之,Quartz 就是基于Java实现的任务调度框架,用于执行自定义的任务。

二、Quartz 运行环境
  • Quartz 可以运行嵌入在另一个独立式应用程序
  • Quartz 可以在应用程序服务器(或Servlet容器)内被实例化,并且参与事务
  • Quartz 可以作为一个独立的程序运行(其自己的Java虚拟机内),可以通过RMI使用
  • Quartz 可以被实例化,作为独立的项目集群(负载均衡和故障转移功能),用于作业的执行
三、Quartz 核心组件
  • 任务 Job
    • Job 就是想要实现的任务类,每一个 Job 必须实现 org.quartz.job接口, 且只需要实现接口定义的 execute()方法
  • 触发器 Trigger
    • Trigger 为你执行任务的触发器,,比如每天定时3点发送一份统计邮件, Trigger将会设置3点进行执行该任务。
    • Trigger主要包含两种 SimpleTrigger 和 CronTrigger 两种
  • 调度器 Scheduler
    • Scheduler 为任务的调度器,它会将任务 Job 以及触发器 Trigger 整合起来,负责基于 Trigger 设定的时间来执行Job
四、Quartz 设计模式
  • Builder 模式
    官方案例中使用 newJob(字节码).withXXX.build()的方式创建工作

  • Factory 模式
    官方案例中通过 StdSchedulerFactory类获取 调度器对象

  • 组件模式
    Job、Trigger触发器 和 Scheduler调度器三个组件

  • 链式编程
    Quartz的类其中部分方法返回的结果就是当前对象实例,所以可以支持链式编程

五、Quartz 的体系结构

六、Quartz 的几个常用 API

以下是 Quartz 编程 API 的几个重要接口,也是 Quartz 的重要组件:

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

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

  • Job 我们预先定义的希望在未来时间能被调度程序执行的时候,可以自定义。

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

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

  • Trigger 触发器,Trigger对象是用来执行Job的。当调度一个 Job 时,我们实例一个触发器会调整它的属性来满足 job 执行的条件。表明任务在什么时候会执行,定义了一个已被安排的任务将会在什么时候执行的条件,比如每2秒执行一次

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

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

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

七、Quartz 入门案例

项目结构:

引入Maven依赖



    4.0.0

    groupId
    quartzDemo
    1.0-SNAPSHOT

    
        8
        8
    

    
        
            org.quartz-scheduler
            quartz
            2.3.2
        

        
            org.quartz-scheduler
            quartz-jobs
            2.3.2
        

        
            org.projectlombok
            lombok
            1.18.24
            compile
        

        
            org.apache.commons
            commons-lang3
            3.12.0
        
    

自定义Job

package cn.uni.job;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Date;


public class MyJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // 获取当前时间
        String t = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
        // 工作内容
        System.out.printf("[%s] 工作中...n", t);
    }
}

定义 Scheduler、Trigger与测试

package cn.uni.main;

import cn.uni.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class HelloSchedulerDemo {
    public static void main(String[] args) {
        try {
            // 1. 调度器 ( Scheduler ) 从工厂中获取调度的实例 (默认: 实例化 new StdSchedulerFactory())
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            // 2. 任务实例 ( JobDetail )
            JobDetail jobDetail = JobBuilder.newJob(MyJob.class) // 加载任务类, 与自定义的Job类进行绑定
                    // 参数1: 任务的名称 (唯一实例), 参数2:任务组名称
                    .withIdentity("job1", "jobGroup1")
                    .build();
            // 3. 触发器 (Trigger)
            SimpleTrigger trigger = TriggerBuilder.newTrigger()
                    // 参数1: 触发器的名称(唯一实例),参数2:触发器组的名称
                    .withIdentity("trigger1", "triggerGroup1")
                    // 设置每过 3 秒执行一次
                    .withSchedule(SimpleScheduleBuilder
                            .simpleSchedule()
                            .withIntervalInSeconds(3)
                            .repeatForever())
                    // 设置立马启动
                    .startNow()
                    .build();
            // 让调度器关联任务和触发器, 保证按照触发器定义的条件执行任务
            scheduler.scheduleJob(jobDetail, trigger);
            // 启动调度器
            scheduler.start();
            // 停止调度器
            // scheduler.shutdown();

        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果(每隔三秒执行一次自定义的Job):

[2022-07-08 11:23:09] 工作中...
[2022-07-08 11:23:12] 工作中...
[2022-07-08 11:23:15] 工作中...
八、Quartz组件详情
8.1 Job

Job:工作任务调度的接口,任务类需要实现该接口。该接口中定义 execute()方法,类似JDK提供的 TimeTask 类的run方法,在里面编写任务执行的业务逻辑

Job实例在Quartz中的生命周期:每次调度器执行Job时,它在调用execute方法前会创建一个新的Job实例,当调用完成后,关联的Job对象实例会被释放,释放的实例将会被垃圾回收机制回收。

比如在入门案例七的基础上修改Job类如下:

package cn.uni.job;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Date;


public class MyJob implements Job {

    public MyJob(){
        System.out.println("hello, job!");
    }
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // 获取当前时间
        String t = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
        // 工作内容
        System.out.printf("[%s] 工作中...n", t);
    }
}

运行结果:

hello, job!
[2022-07-08 12:34:38] 工作中...
hello, job!
[2022-07-08 12:34:41] 工作中...

从这里可以看出,Job类每次执行前都被重新定义过。

8.2 JobDetail

JobDetail为Job实例提供了许多设置属性,以及 JobDataMap成员变量属性,它用来存储特定 job 实例的状态信息,调度器需要借助 jobDetail 对象来添加 job 实例。

JobDetail jobDetail = JobBuilder.newJob(MyJob.class) // 加载任务类, 与自定义的Job类进行绑定
    // 参数1: 任务的名称 (唯一实例), 参数2:任务组名称
    .withIdentity("job1", "jobGroup1")
    .build();
System.out.println("名称: " + jobDetail.getKey().getName());
// 如果没有指定组名, 则默认为: DEFAULT
System.out.println("组的名称: " + jobDetail.getKey().getGroup());
System.out.println("任务类: " + jobDetail.getJobClass().getName());

运行结果:

名称: job1
组的名称: jobGroup1
任务类: cn.uni.job.MyJob

JobDetail 重要属性:name、group、jobClass、jobDataMap

8.3 任务的形参 JobExecutionContext
  • 当 Scheduler 调用一个Job,就会将 JobExecutionContext对象传递给 Job的execute() 方法
  • Job 能通过 JobExecutionContext 对象访问到 Quartz 运行时候的环境以及 Job 本身的详细数据
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    JobDetail jobDetail = jobExecutionContext.getJobDetail();
    JobKey key = jobDetail.getKey();
    System.out.println("工作名称: " + key.getName());
    System.out.println("工作组: " + key.getGroup());
}
8.4 任务的数据传递 JobDataMap

1)可通过 Map 获取

  • 当进行任务调度时,JobDataMap 存储在 JobExecutionContext 中,非常方便获取
  • JobDataMap 可以用来装在任何可序列化的数据对象,当 job 实例对象被执行时,这些参数对象都会传递给它
  • JobDataMap 实现了 JDK 的 Map接口,并且添加了非常方便的方法来存取基本数据类型
    JobDataMap.java
public class JobDataMap extends StringKeyDirtyFlagMap implements Serializable {
     ...
}
StringKeyDirtyFlagMap.java

public class StringKeyDirtyFlagMap extends DirtyFlagMap {
    ...
}

DirtyFlagMap.java

public class DirtyFlagMap implements Map, Cloneable, Serializable {
JobDataMap-> StringKeyDirtyFlagMap -> DirtyFlagMap -> Map

JobDetail 和调度器在定义时,都支持通过usingJobData 方法来传递数据给待执行的Job

通过 JobDetail 或 Trigger传递的JobDataMap可在自定义的Job类里接收

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
    JobDetail jobDetail = context.getJobDetail();
    JobKey key = jobDetail.getKey();
    JobDataMap map = context.getMergedJobDataMap();
    for (Map.Entry entry : map.entrySet()) {
        System.out.println("key=" + entry.getKey() + ", value=" + entry.getValue());
    }
}

这里发现 JobExecutionContext 对象提供了 getMergedJobDataMap() 方法,也就是获取归并后的JobDataMap数据
注意:这里归并后的JobDataMap会对传递的Key做去重操作,如果JobDetail和Trigger 对 job传递了重复的key,那么将会留下最后的Trigger传递的JobDataMap中的key和value。

如果想要获取各自的所有 jobDataMap参数,那么可以通过 JobExecutionContext 对象获取到JobDetail或Trigger。

JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
for (Map.Entry entry : jobDataMap.entrySet()) {
    System.out.println(entry.getKey() + "," + entry.getValue());
}

JobDataMap triggerDataMap = context.getTrigger().getJobDataMap();
for (Map.Entry entry : triggerDataMap.entrySet()) {
    System.out.println(entry.getKey() + "," + entry.getValue());
}

jobDataMap的简化传递

在之前我们是通过 JobExecutionContext 应用上下文对象去获取 JobDataMap的,除此之外,我们可以在自定义的Job类里定义相应的属性,以及对应的一个set方法(可以用 lombok的@Setter),这样在Job执行时,会自动将 JobDataMap对应的参数的值设置到 Job类中。

  • 传递参数
JobDetail jobDetail = JobBuilder.newJob(MyJob.class) 
    .usingJobData("task", "任务详情")
    .build();
- 获取参数
@Data
public class MyJob implements Job {

    private String task ;

    public MyJob(){ }
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println(task);
    }
}

注:如果这里的Key和Value不匹配,那么就不会执行set方法。

8.5 有状态的Job和无状态的Job

● 无状态的Job
○ 默认情况下,每次调用时都会创建一个新的 jobDataMap
● 有状态的 Job
○ 多次调用 job,可以获取上次的 jobDataMap,这些状态信息存储在 jobDataMap中
@PersistjobDataAfterExecution注解: 保存Job执行后的 JobDataMap

案例:使用 Job 实现对 1 + 2 + 3… + 10 的和

自定义 Job

package cn.uni.job;

import lombok.Data;
import lombok.Setter;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.*;

import java.util.Arrays;
import java.util.Date;
import java.util.Map;


@Setter
@PersistJobDataAfterExecution
public class MyJob implements Job {
    private int i;
    private int sum;
    public MyJob(){ }
    @Override
    public void execute(JobExecutionContext context) {
        // 获取当前时间
        String t = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
        // 工作内容
        System.out.printf("[%s] %dn", t, sum += (i++));
        // 更新 JobDataMap
        context.getJobDetail().getJobDataMap().put("i", i);
        context.getJobDetail().getJobDataMap().put("sum", sum);
    }
}

传参

package cn.uni.main;

import cn.uni.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class HelloSchedulerDemo {
    public static void main(String[] args) {
        try {
            // 1. 调度器 ( Scheduler ) 从工厂中获取调度的实例 (默认: 实例化 new StdSchedulerFactory())
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            // 2. 任务实例 ( JobDetail )
            JobDetail jobDetail = JobBuilder.newJob(MyJob.class) // 加载任务类, 与自定义的Job类进行绑定
                    .usingJobData("i", 0)
                    .usingJobData("sum", 0)
                    .build();
            // 3. 触发器 (Trigger)
            SimpleTrigger trigger = TriggerBuilder.newTrigger()
                    // 设置每过 1 秒执行一次, 执行 10 次
                    .withSchedule(SimpleScheduleBuilder
                            .simpleSchedule()
                            .withIntervalInSeconds(1)
                            .withRepeatCount(10))
                    // 设置立马启动
                    .startNow()
                    .build();
            // 让调度器关联任务和触发器, 保证按照触发器定义的条件执行任务
            scheduler.scheduleJob(jobDetail, trigger);
            // 启动调度器
            scheduler.start();
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果:

[2022-07-08 14:21:07] 0
[2022-07-08 14:21:08] 1
[2022-07-08 14:21:09] 3
[2022-07-08 14:21:10] 6
[2022-07-08 14:21:11] 10
...后面都差不多,一共执行了10次
8.5 Trigger

Quartz 有一些不同的触发器类型,用的最多的是 SimpleTriggerImpl 和 CronTriggerImpl
1) JobKey
表示 Job 实例的表示,触发器被触发时,JobKey指定的 Job 实例会被执行
2)startTime
表示触发器的时间表,第一次开始被触发的时间,它的数据类型是 java.util.Date
3)endTime
指定触发器终止被触发的时间,它的数据类型是 java.util.Date
案例:

SimpleTrigger trigger = TriggerBuilder.newTrigger()
    .withIdentity("trigger1", "group1")
    ...
    .build();
public void execute(JobExecutionContext context) {
    // 获取当前时间
    String t = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
    System.out.printf("[%s] 任务执行 n", t);
    Trigger trigger = context.getTrigger();
    // JobKey jobKey = trigger.getJobKey();  // 通过 trigger 可获取到 jobKey
    TriggerKey triggerKey = trigger.getKey();
    System.out.println("jobKey的名称: " + triggerKey.getName());
    System.out.println("jobKey的组名: " + triggerKey.getGroup());
    System.out.println("任务开始时间: " + trigger.getStartTime());
    System.out.println("任务结束时间: " + trigger.getEndTime());
}

触发器可以自定义控制任务的开始时间、结束时间、执行次数等参数

// 3. 触发器 (Trigger)

Date endDate = new Date();
endDate.setTime(endDate.getTime() + 10000); // 10秒后停止
Trigger trigger = TriggerBuilder.newTrigger()
    .withIdentity("trigger1", "group1")
    .endAt(endDate)
    // 设置立马启动
    .startNow()
    .build();

这里是通过 endAt 来指定任务的结束时间, 这样比较麻烦,而且只能执行一次,除此之外可以使用SchedulerBuilder的实现类来指定任务的时间

.withSchedule(SimpleScheduleBuilder.simpleSchedule().xxx)
8.5.1 SimpleTrigger 触发器 使用案例

SimpleTrigger 对于设置和使用是最简单的一种 QuartzTrigger
适合需要在特定的日期/时间启动的场景,且以一个可能的间隔时间重复执行n次的 Job

接下来的案例均以相同的自定义Job类:

package cn.uni.job;

import lombok.Data;
import lombok.Setter;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.quartz.*;

import java.util.Arrays;
import java.util.Date;
import java.util.Map;


@Setter
@PersistJobDataAfterExecution
public class MyJob implements Job {
    private int count;
    public MyJob(){ }
    @Override
    public void execute(JobExecutionContext context) {
        // 获取当前时间
        String t = DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
        System.out.printf("[%s] 任务第 %d 次执行 n", t, ++count);
        context.getJobDetail().getJobDataMap().put("count", count);
    }
}

例1:设置触发器,让任务在两秒后开始执行

package cn.uni.main;

import cn.uni.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.Date;

public class HelloSchedulerDemo {
    public static void main(String[] args) {
        try {
            // 1. 调度器 ( Scheduler ) 从工厂中获取调度的实例 (默认: 实例化 new StdSchedulerFactory())
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            // 2. 任务实例 ( JobDetail )
            JobDetail jobDetail = JobBuilder.newJob(MyJob.class) // 加载任务类, 与自定义的Job类进行绑定
                    .usingJobData("count", 0)
                    .build();
            // 3. 触发器 (Trigger)
            Date startDate = new Date();
            startDate.setTime(startDate.getTime() + 2000);
            Trigger trigger = TriggerBuilder.newTrigger()
                    // 设置 2秒后开始执行, 执行1次
                    .startAt(startDate)
                    .build();
            scheduler.scheduleJob(jobDetail, trigger);
            // 启动调度器
            scheduler.start();
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果:

[2022-07-08 15:07:39] 任务第 1 次执行 

例2:设置触发器,让任务在两秒后开始执行,并且每次间隔一秒重复执行三次
(代码其他部分跟上述相同,故忽略,下同)

Trigger trigger = TriggerBuilder.newTrigger()
    // 设置 2秒后开始执行, 执行1次
    .startAt(startDate)
    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                  .withIntervalInSeconds(1)   // 间隔1秒
                  .withRepeatCount(2))    // 重复两次
    .build();

运行结果:

[2022-07-08 15:13:18] 任务第 1 次执行 
[2022-07-08 15:13:19] 任务第 2 次执行 
[2022-07-08 15:13:20] 任务第 3 次执行 

例3:设置触发器,每3秒执行1次任务,设置重复执行4次,但指定任务在10秒后就结束

Date startDate = new Date();
Date endDate = new Date();
startDate.setTime(startDate.getTime() + 2000);
endDate.setTime(startDate.getTime() + 10000);
Trigger trigger = TriggerBuilder.newTrigger()
    // 设置2秒后开始执行
    .startAt(startDate)
    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                  .withIntervalInSeconds(3)                // 间隔3秒
                  .withRepeatCount(4))    // 重复执行四次
    .endAt(endDate)
    .build();

运行结果:可以发现,尽管设置重复执行四次,但是结束时间在重复执行第四次前就到了,任务就不会继续执行,这里是以任务的结束时间为准。

[2022-07-08 15:19:32] 任务第 1 次执行 
[2022-07-08 15:19:35] 任务第 2 次执行 
[2022-07-08 15:19:38] 任务第 3 次执行 
[2022-07-08 15:19:41] 任务第 4 次执行 

SimpleTrigger 需要注意的点:
● SimpleTrigger的属性有:开始时间、结束时间、重复次数 和 重复的时间间隔
● 重复次数属性的值可以为 0 、正整数、或常量 SimpleTrigger.REPEAT_INDEFINITELY
● 重复的时间间隔属性值必须为0或长整型的正整数,以毫秒作为时间单位,当重复的时间间隔为 0 时,意味着与Trigger同时触发执行。
● 如果有执行结束时间属性值,刚结束时间属性优先于重复次数属性,这样的好处在于:当我们需要创建每一个间隔10秒钟触发一次直到指定的结束时间的Trigger,而无需去计算从开始到结束的所重复的次数,只需要简单的指定结束时间,和 SimpleTrigger.REPEAT_INDEFINITELY 作为重复次数的属性值即可。
● 其中的 withRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY)) 可以用 .repeatForever() 代替,效果都是一样的,指定任务一直执行

8.5.2 CronTrigger 触发器

适合类似日历那样来按日程触发任务,而不是像 SimpleTrigger 那样每隔特定的间隔时间触发,CronTriggers 通常比 SimpleTrigger 更有效果,因为它是基于日历的作业调度器。

使用 CtronTrigger ,可以指定诸如:“每个周五的中午12点”,或者 “每个工作日的 9:00” 或者 " 从每个周一、周三、周五的上午 9:00 到 上午 10 : 00 之间每隔五分钟" 这样日程安排来触发。

CronTrigger 可以和 SimpleTrigger一样,startTime指定日程开始时间,也有可选的endTime指定日程结束时间。

Cron Expressions —— Cron 表达式

可以直接利用工具,在线生成 在线QuartzCron定时任务表达式在线生成工具 - ToolTT在线工具箱

九、Quartz 任务调度
9.1 配置、资源 SchedulerFactory

Quartz 以模块方式构建,因此在运行时几个组件必须配合使用。所有的 Scheduler 实例由 SchedulerFactory 创建
Quartz 的三个核心概念:调度器、任务和触发器,三者之间的关系是:

Job 三大要素:Scheduler、JobDetail、Trigger,而Trigger对于job而言就好比一个驱动器;
没有触发器来定时驱动作业,作业就无法运行;
对于 Job 而言,一个Job可以对应多个Trigger,但对于Trigger而言,一个Trigger只能对应一个Job。
所以一个Trigger 只能被指派给一个Job;如果需要一个更复杂的触发计划,就可以创建多个 Trigger 并指派它们给同一个 Job

9.2 Scheduler的两种创建方式 9.2.1 StdSchedulerFacotry

Quartz 默认的 SchedulerFactory有以下两个特点

  • 使用一组参数(java.util.Properties)来创建和初始化 Quartz 调度器
  • 配置参数一般存储在 quartz.properties 文件中

调用该调度器的位置在 org.quartz.impl下的 StdSchedulerFactory.java 类中的 initialize() 方法
● 调用 getSchedular 方法就能创建和初始化调度器对象

SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();

等同于

Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

用法一:输出调度器开始的时间(重要:使得任务和触发器进行关联)

Date scheduleJob(JobDetail jobDetail, Trigger trigger)
// 绑定 job 和 trigger
Date date = scheduler.scheduleJob(jobDetail, trigger);
System.out.println("Scheduler's start time is " + DateFormatUtils.format(date, "yyyy-MM-dd HH:mm:ss"));

用法二:启动任务调度
void start();

scheduler.start();

用法三:任务调度器挂起,即暂停操作
void standby();

scheduler.standby();

用法四:关闭任务调度
void shutdown();

shutdown(true) :表示等待所有正在执行的 job 执行完毕之后,再关闭 Scheduler
shutdown(false):表示直接关闭 Scheduler
9.2.2 DirectSchedulerFacotry(了解)

DirectSchedulerFacotry 是对 SchedulerFactory的直接实现,通过它可直接构建 Scheduler、Threadpool等

9.3 Quartz 配置文件详解

默认路径: quartz-2.3.2.jar包下的 org.quartz目录中,文件名为 quartz.properties
使用时候可在项目的资源目录 resources 中添加 quartz.properties配置文件,去覆盖底层默认的配置文件

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

配置文件的组成部分:

● 调度器属性
org.quartz.scheduler.instanceName 属性用来区分特定的调度器实例,可以按照功能用途来给调度器起名。
org.quartz.scheduler.instanceId 属性和前者一样,也允许任何字符串,但这个值必须在所有调度器实例中是唯一的,尤其是在一个集群环境中,作为集群的唯一key,假如想让Quartz帮助生成这个值,可以设置为 AUTO。
● 线程池属性
threadCount
处理 Job 的线程个数, 至少为1,最多建议不超过100,否则容易卡顿
threadPriority
线程的优先级,优先级别高的线程比级别低的线程优先得到执行。最小为1,最大为10,默认为5
org.quartz.threadPool.class
一个实现了 org.quartz.spi.ThreadPool 接口的类,Quartz 自带的线程池实现类是 org.quartz.smpl.SimpleThreadPool
● 作业存储设置
描述了再调度器实例的生命周期中, Job 和 Trigger 信息是如何被存储的。
● 插件配置
满足特定需求用到的Quartz插件的配置
Quartz配置的两种方式:① 配置文件 ② Java配置类

9.3.1 自定义 quartz.properties

这里配置了 threadCount 线程的个数为负,故一启动就报错了,说明我们配置的内容是有用的。

9.3.2 代码配置

通过 Properties 对象可设置 Quertz框架的参数。

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

import java.util.Properties;

public class QuartzConfig {
    public static void main(String[] args) throws SchedulerException {
        // 创建工厂实例
        StdSchedulerFactory factory = new StdSchedulerFactory();
        // 创建配置工厂的属性的对象
        Properties prop = new Properties();
        prop.put(StdSchedulerFactory.AUTO_GENERATE_INSTANCE_ID, "AUTO");
        try{
            // 加载属性
            factory.initialize(prop);
            Scheduler scheduler = factory.getScheduler();
            scheduler.start();
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }
}
十、Quartz 监听器

QUartz监听器用于当任务调度时,及时获取事件的通知,类似于任务执行过程中的邮件、短信类的提醒。Quartz监听器主要由 JobListener、TriggerListener、SchedulerListener 三种,分别表示任务、触发器和调度器对应的监听器。三者的使用方法类似。

监听器分为 全局监听器 和 非全局监听器,二者的区别在于:

● 全局监听器能够接收到所有的 Job / Trigger 的事件通知,
● 非全局监听器只能接收到其上注册的 Job 或 Trigger 的事件,不在其上注册的 Job 或 Trigger 则不会进行监听。

10.1 JobListener

在任务调度过程中,与任务 Job 相关的事件包括: job 开始要执行的提示; job 执行完成的提示。

自定义监听器,需实现 JobListener 接口

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

public class MyJobListener implements JobListener {
    public String getName() {
        String name = this.getClass().getSimpleName();
        System.out.println("监听器名称: " + name);
        return name;
    }

    public void jobToBeExecuted(JobExecutionContext context) {
        String jobName = context.getJobDetail().getKey().getName();
        System.out.println("job名称是: " + jobName + ", Scheduler在JobDetail将被执行时调用的方法");
    }
    public void jobExecutionVetoed(JobExecutionContext context) {
        String jobName = context.getJobDetail().getKey().getName();
        System.out.println("job名称是: " + jobName + ", Scheduler在JobDetail将被执行时, 但又被 TriggerListener 否决时调用的方法");
    }

    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        String jobName = context.getJobDetail().getKey().getName();
        System.out.println("job名称是: " + jobName + ", Scheduler在JobDetail被执行后调用这个方法");
    }
}

● 通过调度器定义全局的Job监听器

// 创建并注册全局的 JobListener
scheduler.getListenerManager().addJobListener(
    new MyJobListener(), EverythingMatcher.allJobs());

● 通过调度器定义局部的Job监听器

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.EverythingMatcher;
import org.quartz.impl.matchers.KeyMatcher;

public class Demo {
    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                .withIdentity("job1", "jobGroup1")
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(1)
                        .repeatForever())
                .startNow()
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        // 创建并注册局局的 JobListener
        scheduler.getListenerManager().addJobListener(
                new MyJobListener(),
                KeyMatcher.keyEquals(JobKey.jobKey("job1", "jobGroup1")));

        scheduler.start();
    }
}

JobListener 接口各方法作用总结:
● getName方法,用于获取 JobListener 的名称
● jobToBeExecuted方法:Scheduler 在 jobDetail 将要被执行时调用的方法
● jobExecutionVetoed方法:Scheduler 在 JobDetail即将被执行,但又被 TriggerListener 否决时会调用的方法
● jobWasExecuted方法:Scheduler在JobDetail被执行之后调用的方法

10.2 TriggerListener

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

package org.quartz;

import org.quartz.Trigger.CompletedExecutionInstruction;

public interface TriggerListener {

    String getName();

    void triggerFired(Trigger trigger, JobExecutionContext context);

    boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
    
    void triggerMisfired(Trigger trigger);

    void triggerComplete(Trigger trigger, JobExecutionContext context,
            CompletedExecutionInstruction triggerInstructionCode);
}

其中:
● getName方法,用于获取触发器的名称
● triggerField方法,当与监听器相关联的 Trigger被触发,Job 上的 execute() 方法被执行时,Scheduler 就调用该方法。
● vetoJobExecution 方法,在 Trigger触发后,Job 将要被执行时由 Scheduler 调用这个方法。
● triggerMisfired 方法,Scheduler 调用这个方法是在 Trigger 错过触发时。 应该关注此方法中持续事件长的逻辑:在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应,应该保持这个上方法尽量小。
● triggerCompelet 方法:Trigger 被触发并且完成了 Job的执行时,Scheduler 调用这个方法。

自定义触发器监听器

import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;

public class MyTriggerListener implements TriggerListener {
    public String getName() {
        return this.getClass().getSimpleName();
    }

    public void triggerFired(Trigger trigger, JobExecutionContext context) {
        String name = trigger.getKey().getName();
        System.out.println("触发器: " + name + " 被触发. ");
    }

    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
        String name = trigger.getKey().getName();
        System.out.println(name + "没有被触发");
        return true;   // false 表示会执行 Job , true 表示不会执行 Job
    }

    public void triggerMisfired(Trigger trigger) {
        String name = trigger.getKey().getName();
        System.out.println(name + "错过触发");
    }

    public void triggerComplete(Trigger trigger, JobExecutionContext context, Trigger.CompletedExecutionInstruction triggerInstructionCode) {
        String name = trigger.getKey().getName();
        System.out.println(name + "完成之后触发");
    }
}

定义全局的 Trigger Listener

scheduler.getListenerManager().addTriggerListener(
    new MyTriggerListener(), EverythingMatcher.allTriggers());
定义局部的 Trigger Listener
scheduler.getListenerManager().addTriggerListener(
                new MyTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "group1")));
10.3 SchedulerListener

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

自定义调度器监听器:

import com.sun.xml.internal.fastinfoset.util.StringIntMap;
import org.quartz.*;

public class MySchedulerListener implements SchedulerListener {
    // 部署 JobDetail时调用
    public void jobScheduled(Trigger trigger) {
        String name = trigger.getKey().getName();
        System.out.println(name + " jobDetail完成部署");
    }
    // 卸载 JobDetail时调用
    public void jobUnscheduled(TriggerKey triggerKey) {
        String name = triggerKey.getName();
        System.out.println(name + " 完成卸载");
    }

    // 当 Trigger不再被触发的时候执行的方法
    public void triggerFinalized(Trigger trigger) {
        System.out.println("触发器被移除");
    }
    // 当 一个Trigger被暂停时执行的方法,假如是 Trigger组的话,triggerName参数将为 null
    public void triggerPaused(TriggerKey triggerKey) {
        String name = triggerKey.getName();
        System.out.println(name + " 正在被暂停");
    }

    // 当一个Trigger组被暂停时执行的方法,假如是 Trigger组的话,triggerName参数将为 null
    public void triggersPaused(String triggerGroup) {
        System.out.println("触发器组 " + triggerGroup + " 被暂停");
    }
    // Trigger 从暂停中恢复
    public void triggerResumed(TriggerKey triggerKey) {
        String name = triggerKey.getName();
        System.out.println(name + " 正在从暂停中恢复");
    }

    // Trigger 组从暂停中恢复
    public void triggersResumed(String triggerGroup) {
        System.out.println("触发器组: " + triggerGroup + " 正在从暂停中恢复");
    }
    // 添加任务
    public void jobAdded(JobDetail jobDetail) {
        System.out.println("添加工作任务: " + jobDetail.getKey().getName());
    }
    // 删除任务
    public void jobDeleted(JobKey jobKey) {
        System.out.println("删除工作任务: " + jobKey.getName());
    }
    // 暂停任务
    public void jobPaused(JobKey jobKey) {
        System.out.println("暂停工作任务: " + jobKey.getName());
    }

    // 暂停任务组
    public void jobsPaused(String jobGroup) {
        System.out.println("暂停工作任务组: " + jobGroup);
    }
    // 恢复任务
    public void jobResumed(JobKey jobKey) {
        System.out.println("恢复工作任务: " + jobKey.getName());
    }
    // 恢复任务组
    public void jobsResumed(String jobGroup) {
        System.out.println("工作组 " + jobGroup + " 恢复了");
    }

    // scheduler 发生报错时
    public void schedulerError(String msg, SchedulerException cause) {
        System.out.println("调度器报错: " + msg + cause.getUnderlyingException());
    }

    // scheduler 被挂起时
    public void schedulerInStandbyMode() {
        System.out.println("调度器被挂起");
    }
    // scheduler 被开启时
    public void schedulerStarted() {
        System.out.println("调度器开启");
    }

    // scheduler 正在开启
    public void schedulerStarting() {
        System.out.println("调度器正在开启");
    }

    // scheduler 关闭
    public void schedulerShutdown() {
        System.out.println("调度器被关闭");
    }

    // scheduler 正在被关闭
    public void schedulerShuttingdown() {
        System.out.println("调度器正在被关闭");
    }

    // scheduler 数据被清除
    public void schedulingDataCleared() {
        System.out.println("调度器数据被清除的时候调用");
    }
}


// 添加监听器
scheduler.getListenerManager().addSchedulerListener(
    new MySchedulerListener()
);

// 移除监听器
scheduler.getListenerManager().removeSchedulerListener(
    new MySchedulerListener()
);
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1010592.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号