侧边栏壁纸
博主头像
张种恩的技术小栈博主等级

行动起来,活在当下

  • 累计撰写 747 篇文章
  • 累计创建 65 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录

任务调度框架Quartz

zze
zze
2018-05-05 / 0 评论 / 0 点赞 / 598 阅读 / 44101 字

不定期更新相关视频,抖音点击左上角加号后扫一扫右方侧边栏二维码关注我~正在更新《Shell其实很简单》系列

介绍

概述

定时任务,无论是互联网公司还是传统的软件行业都是必不可少的。Quartz,它是好多优秀的定时任务开源框架的基础,使用它,我们可以使用最简单基础的配置来轻松的使用定时任务。

Quartz 是 OpenSymphony 开源组织在 Job scheduling 领域的又一个开源项目,它可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。

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

简而言之,Quartz 就是基于 java 实现的任务调度框架,用于执行你想要执行的任何任务。

Quart 官网 : http://www.quartz-scheduler.org

运行环境

  1. Quartz 可以运行嵌入在另一个独立式应用程序。
  2. Quartz 可以在应用程序服务器(或 Servlet 容器)内被实例化,并且参与事务。
  3. Quartz 可以作为一个独立的程序运行(其自己的 Java 虚拟机内),可以通过 RMI 调用。
  4. Quartz 可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行。

用到的设计模式

  1. Builder 模式
  2. Factory 模式
  3. 组件模式
  4. 链式编程

核心元素

  • Job(任务)
    Job 就是你想要实现的任务类,每一个 Job 必须实现 org.quartz.job 接口,且只需实现接口 定义的 execute 方法。

  • Trigger(触发器)
    Trigger 是为你执行任务的触发器,比如你想每天定时 3 点发送一封统计邮件,Trigger 将会设置 3 点执行该任务。
    Trigger 主要包含两种 SimpleTriggerCronTrigger 两种。

  • Scheduler(调度器)
    Scheduler 是任务的调度器,它会将任务 Job 及触发器 Trigger 整合起来,负责基于 Trigger 设定的时间来执行 Job

体系结构图

image.png

常用API

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

Scheduler:用于与调度程序交互的主程序接口。它也是调度程序-任务执行计划表,只有安排进执行计划的任务 Job (通过 scheduler,scheduleJob 方法安排进执行计划),当它预先定义的执行时间到了的时候(任务触发 Trigger),该任务才会执行。

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

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

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

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

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

TriggerBuilder:触发器创建器,用于创建触发器 Trigger 实例。

JobListenerTriggerListenerSchedulerListener 监听器,用于对组件的监听。

使用

入门案例

1、创建 Maven Java 项目,引入如下依赖:

<!-- pom.xml -->
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zze.quertz</groupId>
    <artifactId>quartz_test1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!--Quartz 依赖-->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.3.0</version>
        </dependency>
        <!--整合 log4j-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.12</version>
        </dependency>
        <!--log4j 依赖-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <target>1.8</target>
                    <source>1.8</source>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2、创建任务类:

// com.zze.quartz.job.HelloJob
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

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

/**
 * 输出当前时间任务类
 */
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Date date = new Date();
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);
    }
}

3、编写执行类:

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

public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        // 1、调度器(Scheduler),从工厂中获取调度器实例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        // 2、任务实例(JobDetail)
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) // 加载任务类,与 HelloJob 完成绑定,需实现 org.quartz.Job 接口
                .withIdentity("job1", "group1") // param1 : 任务的名称(唯一标示),必须指定 param2 : 任务组名称,未指定时默认为 DEFAULT
                .build();
        // 3、触发器(Trigger)
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1") // param1 : 触发器名称(唯一标示) param2 : 触发器组名称
                .startNow() // 马上启动触发器
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(5)) // 每 5 秒重复执行
                .build();
        // 4、让调度器关联任务和触发器,保证按照触发器定义的条件执行任务
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start(); // 启动
//        scheduler.shutdown(); // 停止
    }
}

Job 和 JobDetail

  • Job:工作任务调度的接口,任务类需要实现该接口。该接口中定义 execute 方法,类似 JDK 提供的 TimeTask 类的 run 方法,在里面编写任务执行的业务逻辑。
  • Job 实例在 Quartz 中的生命周期:每次调度器执行 Job 时,它在调用 execute 方法前会创建一个新的 Job 实例,当调用完成后,关联的 Job 对象实例会被释放,释放的实例会被垃圾回收机制回收。
  • JobDetailJobDetail 为 Job 实例提供了许多设置属性,以及 JobDataMap 成员变量属性,它用来存储特定 Job 实例的状态信息,调度器需要借助 JobDetail 对象来添加 Job 实例。
  • JobDetail 的重要属性:namegroupjobClassjobDataMap
String jobName = jobDetail.getKey().getName();// 任务名称
String jobGroupName = jobDetail.getKey().getGroup();// 组名称
String jobClassName = jobDetail.getJobClass().getName();// 任务类
System.out.println(jobName); // job1
System.out.println(jobGroupName); // group1
System.out.println(jobClassName); // com.zze.quartz.job.HelloJob

JobExecutingContext

  • Scheduler 调用一个 Job,就会将 JobExecutionContext 传递给 Jobexecute() 方法。
  • Job 能通过 JobExecutionContext 对象访问到 Quartz 运行时的环境以及 Job 本身的明细数据。
// 获取 JobDetail 内容
JobDetail jobDetail = jobExecutionContext.getJobDetail();
// 获取 Trigger 内容
Trigger trigger = jobExecutionContext.getTrigger();
// 获取 Scheduler 内容
Scheduler scheduler = jobExecutionContext.getScheduler();
// 获取当前 job 实例
Job jobInstance = jobExecutionContext.getJobInstance();
System.out.println(this == jobInstance); // true
// 获取下次执行时间
Date nextFireTime = jobExecutionContext.getNextFireTime();
// 获取上次执行时间
Date previousFireTime = jobExecutionContext.getPreviousFireTime();
// 获取当前执行时间
Date fireTime = jobExecutionContext.getFireTime();
// 获取单次任务执行的唯一标示
String fireInstanceId = jobExecutionContext.getFireInstanceId();
// 获取 JobDataMap
JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();

JobDataMap

  • 在进行任务调度时,JobDataMap 存储在 JobExecutionContext 中,非常方便获取。
  • JobDataMap 可以用来装载任何可序列化的数据对象,当 Job 实例对象被执行时这些参数对象会传递给它。
  • JobDataMap 实现了 JDK 的 Map 接口,并且添加了非常方便的方法用来存取基础数据类型。

数据传递

可通过 JobDetailTrigger 传递数据给 Job 类,如下:

// com.zze.quartz.main.HelloSchedulerDemo :传递方
import com.zze.quartz.job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .usingJobData("msg","hello job from jobDetail") // 传递参数到任务类
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .usingJobData("msg","hello job from trigger") // 传递参数到任务类
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(5))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}

Job 类接收数据有如下两种方式:

// com.zze.quartz.job.HelloJob :方式一,通过 JobExecutionContext 接收传递过来的数据
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;

/**
 * 输出当前时间任务类
 */
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(jobExecutionContext.getFireTime());
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);

        // 通过 JobDetail 获取 JobDataMap
        JobDataMap jobDetailJobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        String msgFromJobDetail = jobDetailJobDataMap.getString("msg");
        System.out.println(msgFromJobDetail); // hello job from jobDetail
        // 通过 Trigger 获取 JobDataMap
        JobDataMap triggerJobDataMap = jobExecutionContext.getTrigger().getJobDataMap();
        String msgFromTrigger = triggerJobDataMap.getString("msg");
        System.out.println(msgFromTrigger); // hello job from trigger
    }
}
// com.zze.quartz.job.HelloJob :方式二,通过在 Job 类中提供 setter 来接收传递过来的数据
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;

/**
 * 输出当前时间任务类
 */
public class HelloJob implements Job {

    private String msg;

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(jobExecutionContext.getFireTime());
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);

        System.out.println(msg); // hello job from trigger
    }
}

注意:通过方式二接收数据时,JobDetailTrigger 传递的 key 值不可相同,否则 Trigger 传递的数据会覆盖 JobDetail 传递的数据。

有状态和无状态的 Job

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

无状态Job

// com.zze.quartz.main.HelloSchedulerDemo
import com.zze.quartz.job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .usingJobData("count",0) // 传递参数到任务类
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .usingJobData("msg","hello job from trigger") // 传递参数到任务类
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(5))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}
// com.zze.quartz.job.HelloJob
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;

public class HelloJob implements Job {

    private Integer count;

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

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(jobExecutionContext.getFireTime());
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);
        System.out.println(count);
        count++;
        jobExecutionContext.getJobDetail().getJobDataMap().put("count", count);
    }
}

有状态 Job

// com.zze.quartz.main.HelloSchedulerDemo
import com.zze.quartz.job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .usingJobData("count",0) // 传递参数到任务类
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .usingJobData("msg","hello job from trigger") // 传递参数到任务类
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(5))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}
// com.zze.quartz.job.HelloJob
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;

import java.text.SimpleDateFormat;
@PersistJobDataAfterExecution   // 给 Job 添加上该注解那么该 Job 就变成了有状态的Job,多次调用 Job 会保存该 Job 的状态
public class HelloJob implements Job {

    private Integer count;

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

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(jobExecutionContext.getFireTime());
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);
        System.out.println(count);
        count++;
        jobExecutionContext.getJobDetail().getJobDataMap().put("count", count);
    }
}

HelloJob 类没有添加 @PersistJobDataAfterExecution 注解时,每次调用都会新创建一个 JobDataMapcount 不会累加。

HelloJob 类添加 @PersistJobDataAfterExecution 注解时,多次 Job 调用期间会持久化 JobDataMap 信息,所以可以实现 count 的累加。

Trigger

Quartz 有一些不同的触发器类型,不过,用得最多的是 SimpleTriggerCronTrigger

  • jobKey:标识 Job 实例的标识,触发器被触发时,该 Job 实例会被执行。
  • startTime:标识触发器的时间表,第一次开始被出发的时间。
  • endTime:指定触发器终止被触发的时间。

示例

指定任务开始与结束的时间。

// com.zze.quartz.main.HelloSchedulerDemo
import com.zze.quartz.job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

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

public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException, ParseException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .usingJobData("count",0) // 传递参数到任务类
                .build();
        Date startTime = new Date();
        Date endTime = new Date();
        startTime.setTime(startTime.getTime()+3000); // 推迟 3 秒执行
        endTime.setTime(startTime.getTime()+10000); // 推迟 10 秒结束执行
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                // .startNow()
                .startAt(startTime) // 设置开始的时间
                .endAt(endTime) // 设置结束的时间
                .usingJobData("msg","hello job from trigger") // 传递参数到任务类
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(5))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}
// com.zze.quartz.job.HelloJob
import org.quartz.*;

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

@PersistJobDataAfterExecution   // 给 Job 添加上该注解那么该 Job 就变成了有状态的Job,多次调用 Job 会保存该 Job 的状态
public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = simpleDateFormat.format(jobExecutionContext.getFireTime());
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);

        Trigger trigger = jobExecutionContext.getTrigger();
        JobKey jobKey = trigger.getJobKey();
        Date startTime = trigger.getStartTime();
        Date endTime = trigger.getEndTime();
        System.out.printf("开始时间:%s\n", simpleDateFormat.format(startTime));
        System.out.printf("结束时间:%s\n", simpleDateFormat.format(endTime));
    }
}

SimpleTrigger 触发器

在 Quartz 中 SimpleTrigger 对于设置和使用是最为简单的一种 Trigger

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

示例

让一个任务在指定时间开始执行,并且只重复执行三次。

// com.zze.quartz.main.HelloSchedulerDemo
import com.zze.quartz.job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.text.ParseException;
import java.util.Date;

public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException, ParseException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .build();
        Date startTime = new Date();
        startTime.setTime(startTime.getTime() + 3000); // 推迟 3 秒执行
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startAt(startTime) // 设置开始的时间
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(5).withRepeatCount(3 - 1))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}
// com.zze.quartz.job.HelloJob
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = simpleDateFormat.format(jobExecutionContext.getFireTime());
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);
    }
}

SimpleTrigger 的属性有:开始时间、结束时间、重复次数和重复的时间间隔。

重复次数属性的值可以为 0 、正整数、或常量 SimpleTrigger.REPEAT_IDENFINITELY (-1)

重复的时间间隔属性值必须为长整型的正整数,以毫秒为时间单位。

如果有指定结束时间属性值,则结束时间属性优先于重复次数属性。

CronTrigger 触发器

如果你需要像日历那样按日程来出发任务,而不是像 SimpleTrigger 那样每隔特定的间隔时间触发,CronTrigger 通常比 SimpleTrigger 更有用,因为它是基于日历的作业调度器。

使用 CronTrigger,你可以指定例如“每周五中午 12:00”,或者“每隔工作日的 9:30”或“从每个周一、周三、周五的上午 9:00 到 上午 10:00 之间每间隔 5 分钟”这样的日程安排来触发。甚至,像 SimpleTrigger 一样,CronTrigger 也有一个 startTime 以指定日程从什么时候开始,也有一个 endTime(可选)来指定日程何时终止不再继续。

Cron 表达式

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

Seconds Minutes Hours DayofMonth Month DayofWeek Year(可选)
各字段含义
字段允许值允许的特殊字符
秒(Seconds)0~59的整数, - * /    四个字符
分(Minutes)0~59的整数, - * /    四个字符
小时(Hours0~23的整数, - * /    四个字符
日期(DayofMonth1~31的整数(但是你需要考虑你月的天数),- * ? / L W C     八个字符
月份(Month1~12的整数或者 JAN-DEC, - * /    四个字符
星期(DayofWeek1~7的整数或者 SUN-SAT (1=SUN), - * ? / L C #     八个字符
年(可选,留空)(Year1970~2099, - * /    四个字符

每一个域都可以使用数字,但还可以出现如下特殊字符,它们的含义如下:

*:表示匹配该域的任意值。假如在 Minutes 域使用 * , 即表示每分钟都会触发事件。
?:只能用在 DayofMonth 和 DayofWeek 两个域。它也匹配域的任意值,但实际不会。因为 DayofMonth 和 DayofWeek 会相互影响。例如想在每月的 20 日触发调度,不管 20 日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用 ? ,而不能使用 * ,如果使用 * 表示不管星期几都会触发,实际上并不是这样。
-:表示范围。例如在 Minutes 域使用5-20,表示从5分到20分钟每分钟触发一次 
/:表示起始时间开始触发,然后每隔固定时间触发一次。例如在 Minutes 域使用 5/20,则意味着 5 分钟触发一次,而 25,45 等分别触发一次. 
,:表示列出枚举值。例如:在 Minutes 域使用 5,20,则意味着在 5 和 20 分分别触发一次。 
L:表示最后,只能出现在 DayofWeek 和 DayofMonth 域。如果在 DayofWeek 域使用 5L,意味着在最后的一个星期四触发。 
W:表示有效工作日(周一到周五),只能出现在 DayofMonth 域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth 使用 5W,如果 5 日是星期六,则将在最近的工作日:星期五,即 4 日触发。如果 5 日是星期天,则在 6 日(周一)触发;如果 5 日在星期一到星期五中的一天,则就在 5 日触发。另外一点,W 的最近寻找不会跨过月份 。
LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。 
#:用于确定每个月第几个星期几,只能出现在 DayofMonth 域。例如 4#2,表示某月的第二个星期三。

常用表达式示例:

0 2 1 * ? *   表示在每月的 1 日的凌晨 2 点
15 10 ? * MON-FRI   表示周一到周五每天上午 10:15 
15 10 ? 6L 2002-2006   表示 2002-2006 年的每个月的最后一个星期五上午 10:15 
0 10,14,16 * * ?   每天上午 10 点,下午 2 点,4 点 
0/30 9-17 * * ?   朝九晚五工作时间内每半小时 
0 12 ? * WED    表示每个星期三中午 12 点 
0 12 * * ?   每天中午 12 点触发 
15 10 ? * *    每天上午 10:15 触发 
15 10 * * ?     每天上午 10:15 触发 
15 10 * * ? *    每天上午 10:15 触发 
15 10 * * ? 2005    2005 年的每天上午 10:15 触发 
* 14 * * ?     在每天下午 2 点到下午 2:59 期间的每 1 分钟触发 
0/5 14 * * ?    在每天下午 2 点到下午 2:55 期间的每 5 分钟触发 
0/5 14,18 * * ?     在每天下午 2 点到 2:55 期间和下午 6 点到 6:55 期间的每 5 分钟触发 
0-5 14 * * ?    在每天下午 2 点到下午 2:05 期间的每 1 分钟触发 
10,44 14 ? 3 WED    每年三月的星期三的下午 2:10 和 2:44 触发 
15 10 ? * MON-FRI    周一至周五的上午 10:15 触发 
15 10 15 * ?    每月 15 日上午 10:15 触发 
15 10 L * ?    每月最后一日的上午 10:15 触发 
15 10 ? * 6L    每月的最后一个星期五上午 10:15 触发 
15 10 ? * 6L 2002-2005   2002 年至 2005 年的每月的最后一个星期五上午 10:15 触发 
15 10 ? * 6#3   每月的第三个星期五上午 10:15 触发

在线 Cron 表达式生成器:http://cron.qqe2.com/

示例

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

import java.text.ParseException;

public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException, ParseException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                // 使用 Cron 调度器
                .withSchedule(CronScheduleBuilder.cronSchedule("0/3 * * 6 3 ?")) // 3 月 6 号 每隔 3 秒触发
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}
// com.zze.quartz.job.HelloJob
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = simpleDateFormat.format(jobExecutionContext.getFireTime());
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);
    }
}

Scheduler

相关API

scheduler.start(); // 调度器启动
scheduler.standby(); // 挂起
scheduler.shutdown(false); // 立即关闭调度器
scheduler.shutdown(true); // 等待所有任务执行完毕后再终止调度器

配置

通过之前的学习已经知道,调度器实例是通过 StdSchedulerFactory.getDefaultScheduler() 创建。查看该部分源码会发现,创建调度器时加载了如下属性文件:

# 用来区分特定的调度器实例,可以按照功能用途给调度器起名
org.quartz.scheduler.instanceName:DefaultQuartzScheduler
# 这个属性与前者一样,允许任意字符串,但这个值必须在所有调度器实例中是唯一的,尤其是再一个集群环境中,作为集群的唯一 key。设为 auto 即让 Quartz 自动生成这个值
org.quartz.scheduler.instanceId:auto

org.quartz.scheduler.rmi.export:false

org.quartz.scheduler.rmi.proxy:false

org.quartz.scheduler.wrapJobExecutionInUserTransaction:false
#Quartz 自带的线程池实现类,实现了 org.quartz.threadPool
org.quartz.threadPool.class:org.quartz.simpl.SimpleThreadPool

# 处理 Job 的线程个数,至少为 1 ,最多最好不要超过 100。
org.quartz.threadPool.threadCount:10
# 线程的优先级,优先级别高的线程比级别低的线程优先得到执行。最小为 1 ,最大为 10,默认 为 5
org.quartz.threadPool.threadPriority:5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread:true

org.quartz.jobStore.misfireThreshold:60000

org.quartz.jobStore.class:org.quartz.simpl.RAMJobStore

quartz-2.3.0.jar!/org/quartz/quartz.properties

我们可以在 classpath 下提供一个和上述同名的一个属性文件来覆盖 Quartz 的默认配置,也可以通过如下方式手动加载配置文件来初始化调度器:

StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
// stdSchedulerFactory.initialize(String fileName);
// stdSchedulerFactory.initialize(Properties properties);
// stdSchedulerFactory.initialize(InputStream propertiesStream);
Scheduler scheduler = stdSchedulerFactory.getScheduler();

Listener

Quartz 的监听器用于在任务调度中你我们所关注的事件发生时,能够及时获取这一事件的通知。类似于任务执行过程中的邮件、短信的提醒。Quartz 监听器主要有 JobListenerTriggerListenerSchedulerListener 三种,顾名思义,分别表示任务、触发器、调度器对应的监听器,三者的使用方法类似。

而这几种监听器又分为两类,全局监听器和非全局监听器,二者的区别在于:全局监听器能够接收到所有的 Job/Trigger 的时间通知;而非全局监听器只能接收到在其上注册的 Job 或 Trigger 事件,不在其上注册的 JobTrigger 则不会监听。

JobListener

任务调度过程中,与任务 Job 相关的事件包括:Job 要开始执行时、Job 执行完成时。要定义一个任务监听器,需要实现 JobListener 接口:

// org.quartz.JobListener
package org.quartz;

public interface JobListener {
    // 获取 JobListener 的名称
    String getName();
    // Scheduler 在 JobDetail 将要被执行时调用
    void jobToBeExecuted(JobExecutionContext var1);
    // Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决时调用
    void jobExecutionVetoed(JobExecutionContext var1);
    // Scheduler 在 JobDetail 被执行之后调用这个方法
    void jobWasExecuted(JobExecutionContext var1, JobExecutionException var2);
}

使用示例:

// com.zze.quartz.listener.HelloJobListener:任务监听器
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

public class HelloJobListener implements JobListener {
    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
        String jobName = jobExecutionContext.getJobDetail().getKey().getName();
        System.out.printf("jobToBeExecuted---Job的名称为:%s,Scheduler 在 Job 将要被执行时调用该方法\n", jobName);
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
        String jobName = jobExecutionContext.getJobDetail().getKey().getName();
        System.out.printf("jobExecutionVetoed---Job的名称为:%s,Scheduler 在 Job 即将被执行,但又被 TriggerListener 否决时调用该方法\n", jobName);
    }

    @Override
    public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
        String jobName = jobExecutionContext.getJobDetail().getKey().getName();
        System.out.printf("jobExecutionVetoed---Job的名称为:%s,Scheduler 在 Job 执行完成之后调用该方法\n\n", jobName);
    }
}
// com.zze.quartz.job.HelloJob:任务类
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = simpleDateFormat.format(jobExecutionContext.getFireTime());
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);
    }
}
// com.zze.quartz.main.HelloSchedulerDemo:注册监听器
import com.zze.quartz.job.HelloJob;
import com.zze.quartz.listener.HelloJobListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;

public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(3))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        // 注册全局 Job 监听器
//        scheduler.getListenerManager().addJobListener(new HelloJobListener(), EverythingMatcher.allJobs());
        // 注册局部 Job 监听器,对指定组、指定名称的 Job 进行监听
        scheduler.getListenerManager().addJobListener(new HelloJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("job1", "group1")));
        scheduler.start();
    }
}

TriggerListener

任务调度过程中,与触发器 Trigger 相关的事件包括:触发器触发时、触发器未正常触发时、触发器完成时等。要定义一个触发器监听器,需要实现 TriggerListener 接口:

// org.quartz.TriggerListener
package org.quartz;

import org.quartz.Trigger.CompletedExecutionInstruction;

public interface TriggerListener {
    // 获取 Trigger 名称
    String getName();
    // 当与监听器相关联的 Trigger 被触发,Job 上的 execute() 方法将被执行时,Scheduler 就调用该方法。
    void triggerFired(Trigger var1, JobExecutionContext var2);
    // 在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用该方法。TriggerListener 给了一个选择去否决 Job 的执行。假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行。
    boolean vetoJobExecution(Trigger var1, JobExecutionContext var2);
    // Scheduler 调用这个方法是在 Trigger 错过触发时。
    void triggerMisfired(Trigger var1);
    // Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法。
    void triggerComplete(Trigger var1, JobExecutionContext var2, CompletedExecutionInstruction var3);
}

使用示例:

// com.zze.quartz.listener.HelloTriggerListener:触发器监听器
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;

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

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext jobExecutionContext) {
        String triggerName = trigger.getKey().getName();
        System.out.printf("triggerFired---%s 将要被触发\n", triggerName);
    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext jobExecutionContext) {
        String triggerName = trigger.getKey().getName();
        boolean isTrigger = false;
        String msg = !isTrigger ? "允许被触发" : "拒绝被触发";
        System.out.printf("vetoJobExecution---%s %s\n", triggerName, msg);
        return isTrigger; // 如果返回 true,将不会执行 Job 的方法。
    }

    @Override
    public void triggerMisfired(Trigger trigger) {
        String triggerName = trigger.getKey().getName();
        System.out.printf("triggerMisfired---%s 错过触发\n", triggerName);
    }

    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {
        String triggerName = trigger.getKey().getName();
        System.out.printf("triggerComplete---%s 触发完成\n\n", triggerName);
    }
}
// com.zze.quartz.job.HelloJob:任务类

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

import java.text.SimpleDateFormat;

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = simpleDateFormat.format(jobExecutionContext.getFireTime());
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);
    }
}
// com.zze.quartz.main.HelloSchedulerDemo:注册监听器
import com.zze.quartz.job.HelloJob;
import com.zze.quartz.listener.HelloTriggerListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.EverythingMatcher;
import org.quartz.impl.matchers.KeyMatcher;

public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(3))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        // 注册一个全局的 Trigger 监听器
        // scheduler.getListenerManager().addTriggerListener(new HelloTriggerListener(), EverythingMatcher.allTriggers());
        // 注册一个局部的 Trigger 监听器
        scheduler.getListenerManager().addTriggerListener(new HelloTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "group1")));
        scheduler.start();
    }
}

SchedulerListener

任务调度过程中,与调度器有关的事件包括:增加一个 Job/Trigger、删除一个 Job/TriggerScheduler 发生错误、关闭 Scheduler 等。要定义一个调度器监听器,需要实现 SchedulerListener 接口:

// org.quartz.SchedulerListener
package org.quartz;

public interface SchedulerListener {
    // 部署 JobDetail 时调用
    void jobScheduled(Trigger trigger);
    // 卸载 JobDetail 时调用
    void jobUnscheduled(TriggerKey triggerKey);
    // 当一个 Trigger 再也不会触发时调用,若这个触发器对应的 Job 没有状态持久化,则将会从 Scheduler 中移除
    void triggerFinalized(Trigger trigger);
    // 在一个 Trigger 被暂停时
    void triggerPaused(TriggerKey triggerKey);
    // 在一个 TriggerGroup 被暂停时
    void triggersPaused(String triggerGroup);
    // 在一个 Trigger 恢复时
    void triggerResumed(TriggerKey triggerKey);
    // 在一个 TriggerGroup 恢复时
    void triggersResumed(String var1);
    // 当新加入一个 Job 时
    void jobAdded(JobDetail jobDetail);
    // 当移除一个 Job 时
    void jobDeleted(JobKey jobKey);
    // 当 Job 暂停时
    void jobPaused(JobKey jobKey);
    // 当 JobGroup 暂停时
    void jobsPaused(String jobGroup);
    // 当 Job 恢复时
    void jobResumed(JobKey jobKey);
    // 当 JobGroup 恢复时
    void jobsResumed(String jobGroup);
    // 当 Scheduler 运行期出现错误时
    void schedulerError(String msg, SchedulerException cause);
    // 当 Scheduler 挂起时
    void schedulerInStandbyMode();
    // 当 Scheduler 开始后
    void schedulerStarted();
    // 当 Scheduler 开始时
    void schedulerStarting();
    // 当 Scheduler 关闭后
    void schedulerShutdown();
    // 当 Scheduler 关闭时
    void schedulerShuttingdown();
    // 当 Scheduler 中数据被清除时
    void schedulingDataCleared();
}

使用示例:

// com.zze.quartz.listener.HelloSchedulerListener:调度器监听器
import org.quartz.*;

public class HelloSchedulerListener implements SchedulerListener {

    @Override
    public void jobScheduled(Trigger trigger) {
        String name = trigger.getKey().getName();
        System.out.println(name + "完成部署");
    }

    @Override
    public void jobUnscheduled(TriggerKey triggerKey) {
        String name = triggerKey.getName();
        System.out.println(name + "完成卸载");
    }

    @Override
    public void triggerFinalized(Trigger trigger) {
        System.out.println(trigger.getKey().getName() + "触发器已移除");
    }

    @Override
    public void triggerPaused(TriggerKey triggerKey) {
        System.out.println(triggerKey.getName() + "触发器被暂停");
    }

    @Override
    public void triggersPaused(String triggerGroup) {
        System.out.println(triggerGroup + "触发器组被暂停");
    }

    @Override
    public void triggerResumed(TriggerKey triggerKey) {
        System.out.println(triggerKey.getName() + "触发器恢复");
    }

    @Override
    public void triggersResumed(String triggerGroup) {
        System.out.println(triggerGroup + "触发器组恢复");
    }

    @Override
    public void jobAdded(JobDetail jobDetail) {
        System.out.println(jobDetail.getKey().getName() + "任务已添加");
    }

    @Override
    public void jobDeleted(JobKey jobKey) {
        System.out.println(jobKey.getName() + "任务已删除");
    }

    @Override
    public void jobPaused(JobKey jobKey) {
        System.out.println(jobKey.getName() + "任务已暂停");
    }

    @Override
    public void jobsPaused(String jobGroup) {
        System.out.println(jobGroup + "任务组已暂停");
    }

    @Override
    public void jobResumed(JobKey jobKey) {
        System.out.println(jobKey.getName() + "任务已恢复");
    }

    @Override
    public void jobsResumed(String jobGroup) {
        System.out.println(jobGroup + "任务组已恢复");
    }

    @Override
    public void schedulerError(String msg, SchedulerException cause) {
        System.out.println("调度器发生错误:"+msg);
    }

    @Override
    public void schedulerInStandbyMode() {
        System.out.println("调度器挂起");
    }

    @Override
    public void schedulerStarted() {
        System.out.println("调度器已开始");
    }

    @Override
    public void schedulerStarting() {
        System.out.println("调度器正在开始");
    }

    @Override
    public void schedulerShutdown() {
        System.out.println("调度器关闭");
    }

    @Override
    public void schedulerShuttingdown() {
        System.out.println("调度器关闭中");
    }

    @Override
    public void schedulingDataCleared() {
        System.out.println("调度器数据被清理");
    }
}
// com.zze.quartz.job.HelloJob:任务类
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.text.SimpleDateFormat;

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = simpleDateFormat.format(jobExecutionContext.getFireTime());
        System.out.println("正在执行 xxx 任务,时间为" + dateStr);
    }
}
// com.zze.quartz.main.HelloSchedulerDemo:注册监听器
import com.zze.quartz.job.HelloJob;
import com.zze.quartz.listener.HelloSchedulerListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1")
                .build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(3))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
        // 注册调度器监听
        scheduler.getListenerManager().addSchedulerListener(new HelloSchedulerListener());
        scheduler.start();
    }
}
0

评论区