1.Quartz的概念

Quartz就是一个基于Java实现的任务调度框架,用于执行你想要执行的任何任务。
Quartz是OpenSymphony开源组织在Job scheduling(定时调度)领域的开源项目,它可以与J2EE和J2SE应用程序相结合也可以单独使用。

Quartz是开源且具有丰富特性的任务调度库,能够集成任何的Java应用。它能创建简单的或者复杂的调度任务,以执行上十,上百,上千,上万的任务。任务job被定义为标准的Java组件。能工执行任何你想要实现的功能。Quartz调度框架包含许多企业级的特性,如JTA事务,集群的支持。

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

2.Quartz运行环境

  • Quartz可以运行嵌入在另一个独立的应用程序中
  • Quartz可以在应用程序服务器(或servlet容器)内被实例化,bin参与事务
  • Quartz可以作为一个独立的程序运行(其自己的java虚拟机内),可以通过RMI使用
  • Quartz可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业执行

3.Quartz使用的设计模式

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

4.Quartz学习的核心概念

  • 任务Job

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

  • 触发器Trigger

Trigger为你执行任务的触发器(定时)
Trigger主要包含两个SimpleTrigger和CronTrigger两种

  • 调度器Scheduler

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

5.Quartz的体系结构

在这里插入图片描述

6.Quartz的几个常用API

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

  • Job:预先定义的,希望在未来某个使劲能被调度程序执行的任务类,可自定义

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

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

  • Trigger:触发器,Trigger对象是用来触发执行Job的。当调度一个Job时,我们实例一个触发器然后调整他的属性来满足Job执行的条件。表明任务在什么时候会执行。【定义了一个已经被安排的任务将会在什么时候执行的时间条件】

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

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

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

7.入门案例

构建一个任务类实现Job接口,重写execute(执行方法)方法,在执行方法中编写定时调度需要实现的逻辑。
创建一个main方法,在main方法中构建任务实例和处方实例有调度器实例进行绑定并开启任务。

7.1.pom.xml
<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>

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>
7.2.任务类
/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:45
 * @Desc 任务类
 */
@Slf4j
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " | 任务被执行了");
    }
}
7.3.调度器
/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:53
 * @Desc 调度器
 */
public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        //从调度工厂中获取调度器实例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //通过JobBuilder构建一个任务实例
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                //设置任务的唯一实例名称和任务组名称组名
                .withIdentity("job1", "group1")
                //构建实例
                .build();
        //通过TriggerBuilder构建触发器实例
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                //设置触发器唯一实例名称和触发器的组名
                .withIdentity("trigger1", "group1")
                //执行计划,每五秒执行一次
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
                //立即执行
                .startNow()
                //构建实例
                .build();
        //调度器绑定任务实例和触发器
        scheduler.scheduleJob(jobDetail,trigger);
        //开启定时任务
        scheduler.start();
    }
}
7.4.执行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UE7c0gzB-1626663674638)(\images\image-20210713111049528.png)]

8.Job和JobDetail的关系介绍

  • Job:工作任务调度接口,任务类需要实现的接口。该接口中定义了execute方法,类似JDK提供的TimeTask类的run方法。在这里面编写任务执行的业务逻辑
  • Job实例在Quartz中的生命周期:每次调度器执行Job时,它在调用execute方法前会创建一个新的Job实例,当调度完成后,管理的Job对象实例将会被释放,释放的实例会被垃圾回收机制回收
@Slf4j
public class HelloJob implements Job {

    public HelloJob(){
        log.info("实例被创建了");
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " | 任务被执行了");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hpf6PuYl-1626663674639)(\images\image-20210713112341699.png)]

  • JobDetail:JobDetail为Job实例提供了许多设置属性,已经JobDetaMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助于JobDetail对象来添加Job实例【JobDetail中重要的属性:name[任务实例的唯一标识名称]、group[任务调度实例的组名]、jobClass[任务类信息]、jobDataMap】
@Slf4j
public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        //从调度工厂中获取调度器实例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //通过JobBuilder构建一个任务实例
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                //设置任务的唯一实例名称和任务组名称组名
                .withIdentity("job1", "group1")
                //构建实例
                .build();
        //jobDataMap
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        //任务实例的唯一标识名称
        String name = jobDetail.getKey().getName();
        //任务调度实例的组名
        String group = jobDetail.getKey().getGroup();
        //任务类信息
        String clazzName = jobDetail.getKey().getClass().getName();
        log.info("jobDataMap:{}", jobDataMap);
        log.info("任务实例的唯一标识名称:"+name);
        log.info("任务调度实例的组名:"+group);
        log.info("任务类信息:"+clazzName);
        //通过TriggerBuilder构建触发器实例
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                //设置触发器唯一实例名称和触发器的组名
                .withIdentity("trigger1", "group1")
                //执行计划,每五秒执行一次
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
                //立即执行
                .startNow()
                //构建实例
                .build();
        //调度器绑定任务实例和触发器
        scheduler.scheduleJob(jobDetail,trigger);
        //开启定时任务
        scheduler.start();
    }
}

在这里插入图片描述

9.JobExecutionContext的介绍

  • 当Scheduler调用一个Job,就会将JobExecutionContext传递给Job的execute()方法

  • Job能通过JobExecuteContext对象访问到Quartz运行时的环境以及Job本身的明细数据

    • 获取JobDetail相关信息、获取Trigger相关信息、获取Job类本身的信息
    /**
     * @Author ScholarTang
     * @Date 2021/7/13 10:45
     * @Desc 任务类
     */
    @Slf4j
    public class HelloJob implements Job {
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            //通过JobExecutionContext获取JobDetail
            JobDetail jobDetail = jobExecutionContext.getJobDetail();
            JobKey jobKey = jobDetail.getKey();
            String jobDetailName = jobKey.getName();
            String jobDetailGroup = jobKey.getGroup();
            String jobClazzNameJobDetail = jobKey.getClass().getName();
            log.info("任务实例的唯一标识名称:" + jobDetailName);
            log.info("任务实例的组名:" + jobDetailGroup);
            log.info("任务实例绑定的任务类信息:" + jobClazzNameJobDetail);
            log.info("-----------------------------------------");
            //通过JobExecutionContext获取Trigger
            Trigger trigger = jobExecutionContext.getTrigger();
            TriggerKey triggerKey = trigger.getKey();
            String triggerKeyName = triggerKey.getName();
            String triggerKeyGroup = triggerKey.getGroup();
            String triggerClazzName = triggerKey.getClass().getName();
            log.info("触发器的唯一标识名称:" + triggerKeyName);
            log.info("触发器的组名:" + triggerKeyGroup);
            log.info("触发器绑定的任务类信息:" + triggerClazzName);
            log.info("-----------------------------------------");
            //通过JobExecutionContext
            String jobClazzName = jobExecutionContext.getClass().getName();
            log.info("job类相关的信息:"+jobClazzName);
            log.info("-----------------------------------------");
            log.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " | 任务被执行了");
        }
    }
    

在这里插入图片描述

10.JobDataMap介绍

10.1.使用Map获取
  • 在进行任务调度时,JobDataMap存储在JobExecutionContent中,非常方便获取
  • JobDataMap可以用来存储任何可序列化的数据对象,当Job实例对象被执行时这些参数都会传递给它
  • JobDataMap实现了JDK的Map接口,并且添加了非常方便的方法来存储基本数据类型
/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:53
 * @Desc 调度器
 */
@Slf4j
public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        //从调度工厂中获取调度器实例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //通过JobBuilder构建一个任务实例
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                //设置任务的唯一实例名称和任务组名称组名
                .withIdentity("job1", "group1")
                //设置jobDataMap数据  <<===============
                .usingJobData("message","勇敢牛牛、不怕困难")
                //构建实例
                .build();
        //通过TriggerBuilder构建触发器实例
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                //设置触发器唯一实例名称和触发器的组名
                .withIdentity("trigger1", "group1")
                //设置jobDataMap数据   <<===============
                .usingJobData("username","张三")
                //执行计划,每五秒执行一次
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
                //立即执行
                .startNow()
                //构建实例
                .build();
        //调度器绑定任务实例和触发器
        scheduler.scheduleJob(jobDetail,trigger);
        //开启定时任务
        scheduler.start();
    }
}
/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:45
 * @Desc 任务类
 */
@Slf4j
public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //通过JobExecutionContext获取JobDetail
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        JobKey jobKey = jobDetail.getKey();
        String jobDetailName = jobKey.getName();
        String jobDetailGroup = jobKey.getGroup();
        String jobClazzNameJobDetail = jobKey.getClass().getName();
        log.info("任务实例的唯一标识名称:" + jobDetailName);
        log.info("任务实例的组名:" + jobDetailGroup);
        log.info("任务实例绑定的任务类信息:" + jobClazzNameJobDetail);
        log.info("-----------------------------------------");
        //通过JobExecutionContext获取Trigger
        Trigger trigger = jobExecutionContext.getTrigger();
        TriggerKey triggerKey = trigger.getKey();
        String triggerKeyName = triggerKey.getName();
        String triggerKeyGroup = triggerKey.getGroup();
        String triggerClazzName = triggerKey.getClass().getName();
        log.info("触发器的唯一标识名称:" + triggerKeyName);
        log.info("触发器的组名:" + triggerKeyGroup);
        log.info("触发器绑定的任务类信息:" + triggerClazzName);
        log.info("-----------------------------------------");
        //通过JobExecutionContext
        String jobClazzName = jobExecutionContext.getClass().getName();
        log.info("job类相关的信息:"+jobClazzName);
        log.info("-----------------------------------------");
        //获取JobDetail中JobDataMap中的message内容  <<===============
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        String message = jobDataMap.getString("message");
        log.info("从JobDetail-JobDataMap中或到的message内容为:" + message);
        //获取Trigger中JobDataMap中的username内容   <<===============
        JobDataMap triggerJobDataMap = trigger.getJobDataMap();
        String username = triggerJobDataMap.getString("username");
        log.info("从Trigger-JobDataMap中获取到的username内容为:" + username);
        log.info("-----------------------------------------");
        log.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " | 任务被执行了");
    }
}

在这里插入图片描述

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

调度器的内容是不变的。
注意:如果任务实例和触发器的JobDataMap中使用的是同一个key那么触发器的JobDataMap值会覆盖掉任务实例的值

/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:45
 * @Desc 任务类
 */
@Slf4j
public class HelloJob implements Job {

    private String message;
    private String username;

    public void setMessage(String message) {
        this.message = message;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //通过JobExecutionContext获取JobDetail
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        JobKey jobKey = jobDetail.getKey();
        String jobDetailName = jobKey.getName();
        String jobDetailGroup = jobKey.getGroup();
        String jobClazzNameJobDetail = jobKey.getClass().getName();
        log.info("任务实例的唯一标识名称:" + jobDetailName);
        log.info("任务实例的组名:" + jobDetailGroup);
        log.info("任务实例绑定的任务类信息:" + jobClazzNameJobDetail);
        log.info("-----------------------------------------");
        //通过JobExecutionContext获取Trigger
        Trigger trigger = jobExecutionContext.getTrigger();
        TriggerKey triggerKey = trigger.getKey();
        String triggerKeyName = triggerKey.getName();
        String triggerKeyGroup = triggerKey.getGroup();
        String triggerClazzName = triggerKey.getClass().getName();
        log.info("触发器的唯一标识名称:" + triggerKeyName);
        log.info("触发器的组名:" + triggerKeyGroup);
        log.info("触发器绑定的任务类信息:" + triggerClazzName);
        log.info("-----------------------------------------");
        //通过JobExecutionContext
        String jobClazzName = jobExecutionContext.getClass().getName();
        log.info("job类相关的信息:"+jobClazzName);
        log.info("-----------------------------------------");
        //获取JobDetail中JobDataMap中的message内容
        //JobDataMap jobDataMap = jobDetail.getJobDataMap();
        //String message = jobDataMap.getString("message");
        log.info("从JobDetail-JobDataMap中或到的message内容为:" + message);
        //获取Trigger中JobDataMap中的username内容
        //JobDataMap triggerJobDataMap = trigger.getJobDataMap();
        //String username = triggerJobDataMap.getString("username");
        log.info("从Trigger-JobDataMap中获取到的username内容为:" + username);
        log.info("-----------------------------------------");
        log.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " | 任务被执行了");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DP3HoGTN-1626663674672)(\images\image-20210713143046337.png)]

10.3.获取其他
//获取任务的本次执行时间
Date fireTime = jobExecutionContext.getFireTime();
//获取任务的下次执行时间
Date nextFireTime = jobExecutionContext.getNextFireTime();

log.info("任务的本次执行时间:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(fireTime));
log.info("任务的下次执行时间:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(nextFireTime));

11.有状态的Job和无状态的Job

什么是有状态的Job?什么是无状态的Job?

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

11.1.Job默认是无状态的,通过如下示例查看:
package com.scholartang.quartz.main;

import com.scholartang.quartz.job.HelloJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.triggers.CoreTrigger;

/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:53
 * @Desc 调度器
 */
@Slf4j
public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        //从调度工厂中获取调度器实例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //通过JobBuilder构建一个任务实例
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                //设置任务的唯一实例名称和任务组名称组名
                .withIdentity("job1", "group1")
                //设置jobDataMap数据   <<===============
                .usingJobData("count",0)
                //构建实例
                .build();
        //通过TriggerBuilder构建触发器实例
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                //设置触发器唯一实例名称和触发器的组名
                .withIdentity("trigger1", "group1")
                //执行计划,每五秒执行一次
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
                //立即执行
                .startNow()
                //构建实例
                .build();
        //调度器绑定任务实例和触发器
        scheduler.scheduleJob(jobDetail,trigger);
        //开启定时任务
        scheduler.start();
    }
}

/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:45
 * @Desc 任务类
 */
@Slf4j
public class HelloJob implements Job {
    private Integer count;

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

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //通过JobExecutionContext获取JobDetail
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        count++;
        jobDetail.getJobDataMap().put("count",count);
        log.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " | 任务被执行了");
        log.info("当前count的值为:" + count);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2rsYNEkl-1626663674675)(\images\image-20210713145605765.png)]

11.2.通过@PersistJobDataAfterExecution注解将Job设置为由状态的
/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:45
 * @Desc 任务类
 */
@Slf4j
@PersistJobDataAfterExecution
public class HelloJob implements Job {
    private Integer count;

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

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //通过JobExecutionContext获取JobDetail
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        count++;
        jobDetail.getJobDataMap().put("count",count);
        log.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " | 任务被执行了");
        log.info("当前count的值为:" + count);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YPG1NXK5-1626663674676)(\images\image-20210713145844114.png)]

如果Job类没有添加@PersistJobDataAfterExecution注解,每次调用时都会创建一个新的JobDataMap。不会累加count的值
如果Job类添加了@PersistJobDataAfterExecution注解,每次调用期间都会持有一些状态信息,。会累加count的值

12.Trigger触发器的介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vBOnV0mg-1626663674677)(\images\image-20210713152208189.png)]

上图即是Quartz的触发器类型,常用的为SimpleTriggerImpl,CronTriggerImpl

12.1.SimpleTriggerImpl的使用

设置触发的开始时间和结束时间及执行计划

/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:45
 * @Desc 任务类
 */
@Slf4j
@PersistJobDataAfterExecution
public class HelloJobTrigger implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Trigger trigger = jobExecutionContext.getTrigger();
        JobKey jobKey = trigger.getJobKey();
        log.info("触发器名称:" + jobKey.getName() + " | 触发器组名:" + jobKey.getGroup() );
        log.info("触发器开始执行时间:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(trigger.getStartTime()) + " | 触发器结束执行时间:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(trigger.getEndTime()));
        log.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " | 任务被执行了");
    }
}
/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:53
 * @Desc 调度器
 */
@Slf4j
public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        //开始时间 & 结束时间
        Date startTime = new Date();
        Date endTime = new Date();
        endTime.setTime(endTime.getTime() + 20000);
        //从调度工厂中获取调度器实例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //通过JobBuilder构建一个任务实例
        JobDetail jobDetail = JobBuilder.newJob(HelloJobTrigger.class)
                //设置任务的唯一实例名称和任务组名称组名
                .withIdentity("job1", "group1")
                //构建实例
                .build();
        //通过TriggerBuilder构建触发器实例
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                //设置触发器唯一实例名称和触发器的组名
                .withIdentity("trigger1", "group1")
                //执行计划,每五秒执行一次
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)
                               .withRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY) //重复执行次数
                             )
                //开始执行时间
                .startAt(startTime)
                //结束执行时间
                .endAt(endTime)
                //构建实例
                .build();
        //调度器绑定任务实例和触发器
        scheduler.scheduleJob(jobDetail,trigger);
        //开启定时任务
        scheduler.start();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fMBW5hRR-1626663674678)(\images\image-20210713153505134.png)]

注意:

  • SimpleTrigger的属性有:开始时间,结束时间,重复次数和重复时间间隔
  • 重复次数的值可以为0、正数、或常量。例如:SimpleTrigger.REPEAT_INDEFINITELY
  • 重复的时间间隔属性值必须为大于0或者长整型的整数,以毫秒为为时间单位,当重复的时间间隔为0时,意味着与Trigger同时触发执行
  • 如果有指定结束时间属性值,则结束时间属性优先于重复次数属性,这样的好处在于;当我们需要创建一个每间隔10秒触发一次直到指定的结束时间的Trigger,而无需去计算从开始到结束的所重复执行次数。我们只需要简单的执行结束时间和使用REPEAT_INDEFINITELY作为重复次数的属性值即可

13.CronTrigger触发器的介绍

13.1.CronTrigger简介

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

使用CronTrigger,可以指定诸如每个周五的12点,或者每天的9点等等…这样的日程来安排触发。甚至还可以像SimpleTrigger一样,CornTrigger也有一个startTime以指定日程从什么时候开始,也有一个(可选的)endTime以指定任何日期不再继续

13.2.corn表达式

corn表达式相信大家并不陌生,corn表达式被用来配置CronTrigger实例。corn表达式是一个由7个表达式组成的字符串。每一个子表达式描述了一个单独的日程细节。这些子表达式用空格分隔,分别表示:seconds秒、minutes分钟、hours小时、day-of-month月中的天、moth月,year年

13.3.表达式的取值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-syG4qvIx-1626663674679)(/Users/jiangnan/Library/Application Support/typora-user-images/image-20210719095751206.png)]

在线生成corn表达式:https://cron.qqe2.com/

13.3.演示案例
/**
 * @Author ScholarTang
 * @Date 2021/7/13 16:28
 * @Desc 任务类
 */
@Slf4j
public class HelloJonCornTrigger implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " | 任务被执行了");
    }
}
/**
 * @Author ScholarTang
 * @Date 2021/7/13 10:53
 * @Desc 调度器
 */
@Slf4j
public class HelloSchedulerDemo {
    public static void main(String[] args) throws SchedulerException {
        //从调度工厂中获取调度器实例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //通过JobBuilder构建一个任务实例
        JobDetail jobDetail = JobBuilder.newJob(HelloJonCornTrigger.class)
                //设置任务的唯一实例名称和任务组名称组名
                .withIdentity("job1", "group1")
                //构建实例
                .build();
        //通过TriggerBuilder构建触发器实例
        Trigger trigger = TriggerBuilder.newTrigger()
                //设置触发器唯一实例名称和触发器的组名
                .withIdentity("trigger1", "group1")
            	//立刻执行
                .startNow()
                //每年的每月的每个星期得每天的每小时的每秒都会执行
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
                //构建实例
                .build();
        //调度器绑定任务实例和触发器
        scheduler.scheduleJob(jobDetail,trigger);
        //开启定时任务
        scheduler.start();
    }
}

14.配置、资源SchedulerFactory

Quartz以模块方式架构,因此要使它运行。几个组件必须要很好的咬合在一起。【现在已经有了一些现成的助手可以完成这些工作】

所有scheduler实例是由SchedulerFactory创建。Quartz的三个核心概念:调度器,任务,触发器。

调度器,任务,触发器三者之间的关系为:
在这里插入图片描述

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

Scheduler的创建方式

  • StdSchedulerFactory

Quartz默认的SchedulerFactory

  • 使用一组参数(java.util.Properties)来创建和初始化Quartz调度器
  • 配置参数一般存储在quartz.properties文件中
  • 调用getScheduler方法就能穿件和初始化调度器对象
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();

用法一:输出调度器开始的时间

//调度器绑定任务实例和触发器,方法返回调开始执行的时间
Date date = scheduler.scheduleJob(jobDetail, trigger);

用法二:启动任务调度

//开启任务调度
scheduler.start();

用法三:任务调度挂起【暂停操作】

//任务调度挂起
scheduler.standby();

用法四:任务调度关闭

//关闭任务调度
scheduler.shutdown(); //默认为false
//scheduler.shutdown(true);  表示等待所有正在执行的Job执行完毕后,再关闭>Scheduler
//scheduler.shutdown(false); 表示直接关闭Scheduler
  • DirectSchedulerFactory

DirectSchedulerFactory是对SchedulerFactory的直接实现,通过它可以直接构建scheduler,threadpool等

DirectSchedulerFactory directSchedulerFactory = DirectSchedulerFactory.getInstance();
Scheduler scheduler = directSchedulerFactory.getScheduler();

15.Quartz.properties文件介绍

15.1.文件默认所在位置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yy9DXl5H-1626663674683)(\images\image-20210713172314589.png)]

默认使用的是该配置文件,如果想自定义配置文件只需要在resources重新定义一个quartz.properties文件编写内容即可。

15.2.组成部分
  • 调度器属性

org.quartz.scheduler.instanceName:属性用来区分特定的调度器实例,可以按照功能用途来给调度器起名
org.quartz.scheduler.instanceId:属性和前者一样,运行任何字符串,但是这个值必须在所有调度器实例中是唯一的,尤其是在一个集群环境中,作为集群的唯一key。假如你想Quartz帮你生成这个值额度话可以设置为AUTO

  • 线程池属性

threadCount:处理Job的线程个数。至少是1,但是最多的话不要超过100,在多数集群上设置该值超过100的话就会显得相当不实用了,特别是Job执行时间较长的情况下

threadPriority:线程的优先级,有限级别高的线程比级别地的线程有限执行。最小为1,最大为10。默认为5

org.quartz.threadPoll.class:一个实现了org.quartz.spi.ThreadPoll接口的类,Quartz自带的线程池实现类是org.quartz.smpl.SimpleThreadPoll

  • 作业存储设置

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

  • 插件配置

满足特定需求需要用到的Quartz插件的配置

15.3.配置属性描述好文推荐

https://blog.csdn.net/yixiaoping/article/details/10476817?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.base&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.base

15.4.在程序中去进行配置

如下例所示:

//创建调度实例工厂实例
StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
//创建配置工厂属性的对象
Properties properties = new Properties();
properties.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool");
properties.put("org.quartz.threadPool.threadCount", "10");
//加载配置
schedulerFactory.initialize(properties);
//从调度工厂中获取调度器实例
Scheduler scheduler = schedulerFactory.getScheduler();
..........

16.Quartz监听器

16.1.概念

Quartz的监听器用于当任务调度中你所关注事件发生时,能够及时获取这一事件的通知。类似于任务执行过程中的邮件,短信的提醒。
Quartz监听器主要有JobListener、TriggerListener、SchedulerListener三种,顾名思义,分别表示任务,触发器,调度器对应的监听器。三者的使用方法类似,在开始介绍三种监听器之前,需要明确两个概念:全局监听器与非全局监听器。
两者的区别在于:
全局监听器只能够接收到所有的Job、Trigger的事件通知。
而非全局监听器只能接收到在其上注册的Job或Trigger的事件,不在其上注册的Job或Trigger则不会进行监听。

16.2.JobListener监听器的使用
1.JobListener源码

JobListener是一个接口,它内部有四个方法。详细解析如下所示;
JobListener用于监听任务,通常需要被监听的任务类实现该接口

public interface JobListener {

    /**
     * 用于获取该JobListener的名称
     */
    String getName();

    /**
     * scheduler在JobDetail将要被执行时调用这个方法【JobDetail执行前】
     * @see #jobExecutionVetoed(JobExecutionContext)
     */
    void jobToBeExecuted(JobExecutionContext context);

    /**
     * scheduler在JobDetail即将被执行,但又被TriggerListener否决时会调用该方法
     * @see #jobToBeExecuted(JobExecutionContext)
     */
    void jobExecutionVetoed(JobExecutionContext context);

    
    /**
     * scheduler在JobDetail被执行后调用这个方法
     */
    void jobWasExecuted(JobExecutionContext context,JobExecutionException jobException);
}
2.使用演示案例

创建任务类:HelloJobToListener
创建任务调度类:HelloSchedulerDemoListener
创建job监听类:MyJobListener
任务类实现Job接口,并重写execute方法;
任务调度类创建任务执行实例和触发器与调度器进行绑定。并设置对job的监听【全局监听,局部监听】
job监听类实现JobListener接口,重写对应的方法。

HelloJobToListener.java

/**
 * @Author ScholarTang
 * @Date 2021/7/13 19:47
 * @Desc 任务类
 */
@Slf4j
public class HelloJobToListener implements Job {
    @Override
    public void execute(JobExecutionContext context) {
        log.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + " | 任务被执行了");
    }
}

MyJobListener.java

/**
 * @Author ScholarTang
 * @Date 2021/7/13 19:53
 * @Desc JobListener
 */
@Slf4j
public class MyJobListener implements JobListener {
    @Override
    public String getName() {
        String name = this.getClass().getSimpleName();
        log.info("当前JobListener的名称为:" + name);
        return name;
    }

    @Override
    public void jobToBeExecuted(JobExecutionContext context) {
        String name = context.getJobDetail().getJobClass().getName();
        log.info("当前Job的名称为:" + name + ",JobDetail将要被执行了...");
    }

    @Override
    public void jobExecutionVetoed(JobExecutionContext context) {
        String name = context.getJobDetail().getJobClass().getName();
        log.info("当前Job的名称为:" + name + ",JobDetail将要被执,但被TriggerListener否决...");
    }

    @Override
    public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
        String name = context.getJobDetail().getJobClass().getName();
        log.info("当前Job的名称为:" + name + ",JobDetail执行完成了...");
    }
}

HelloSchedulerDemoListener.java

/**
 * @Author ScholarTang
 * @Date 2021/7/13 19:55
 * @Desc 调度器
 */
public class HelloSchedulerDemoListener {
    public static void main(String[] args) throws SchedulerException {
        //通过调度器工厂构建调度器实例
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //通过JobBuilder构建JobDetail
        JobDetail jobDetail = JobBuilder.newJob(HelloJobToListener.class)
                .withIdentity("job1", "group1")
                .build();
        //通过TriggerBuilder构建Trigger
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("3 * * * * ?"))
                .build();
        //调度器绑定JobDetail和Trigger
        scheduler.scheduleJob(jobDetail, trigger);
        //绑定job监听
        //1.全局绑定,所有的job在被调度的时候都会被监听
        //scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
        //2.局部监听,用来监听指定的job
        scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher
                //jobKey中的name参数和group参数对应的就是任务实例【JobDetail】的name和group
                .keyEquals(JobKey.jobKey("job1","group1")));
        //开启调度
        scheduler.start();
    }
}
3.执行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8l6wCoo9-1626663674686)(\images\image-20210713203910129.png)]

16.3.TriggerListener监听器的使用

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

1.TriggerListener源码
public interface TriggerListener {

    /**
     * 用于触发器的名称
     */
    String getName();

    /**
     * 当与监听器相关联的Trigger被触发,Job上的execute()方法将被执行时,Scheduler就调用该方法【job执行前】
     */
    void triggerFired(Trigger trigger, JobExecutionContext context);

    /**
     * 在Trigger触发后,Job将要被执行由Scheduler调用这个方法。Trigger Listener给了一个选择去否决Job的执行。假如这个方法返回true,这个Job将不会为此次Trigger而得到触发【job执行前】
     */
    boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);

    
    /**
     * scheduler调用这个方法是在Trigger错过触发时,你应该关注此方法中持续时间长的逻辑;在出现许多错过触发的Trigger时,长逻辑会导致骨牌效应,你应当保持这个方法尽量的少使用
     */
    void triggerMisfired(Trigger trigger);

    /**
     * Trigger被触发并完成了Job的执行,scheduler调用这个方法
     */
    void triggerComplete(Trigger trigger, JobExecutionContext context,
            CompletedExecutionInstruction triggerInstructionCode);

}
2.使用演示案例

创建任务类:HelloJobToTrigger
创建任务调度类:HelloSchedulerDemoTrigger
创建Trigger监听类:MyTriggerListener

HelloJobToTrigger.java

/**
 * @Author ScholarTang
 * @Date 2021/7/13 下午10:03
 * @Desc 任务类
 */
@Slf4j
public class HelloJobToTrigger implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " | 任务被执行...");
    }
}

MyTriggerListener.java

/**
 * @Author ScholarTang
 * @Date 2021/7/13 下午10:05
 * @Desc TriggerListener
 */

@Slf4j
public class MyTriggerListener implements TriggerListener {
    @Override
    public String getName() {
        String simpleName = this.getClass().getSimpleName();
        return simpleName;
    }

    @Override
    public void triggerFired(Trigger trigger, JobExecutionContext jobExecutionContext) {
        String name = trigger.getKey().getName();
        log.info(name + " | Job上的execute()方法将被执行时,Scheduler就调用该方法");
    }

    @Override
    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext jobExecutionContext) {
        boolean temp = false;
        String name = trigger.getKey().getName();
        log.info(name + " | Job将要被执行由Scheduler调用这个方法。Trigger Listener给了一个选择去否决Job的执行为" + (temp ? "为此次Trigger触发" : "不为此次Trigger触发"));
        return temp;
    }

    @Override
    public void triggerMisfired(Trigger trigger) {
        String name = trigger.getKey().getName();
        log.info(name + " | scheduler调用这个方法是在Trigger错过触发时调用该方法");
    }

    @Override
    public void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {
        String name = trigger.getKey().getName();
        log.info(name + " | Trigger被触发并完成了Job的执行,scheduler调用这个方法");
    }
}

HelloSchedulerDemoTrigger.java

/**
 * @Author ScholarTang
 * @Date 2021/7/13 下午10:05
 * @Desc 调度器
 */

public class HelloSchedulerDemoTrigger {
    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        JobDetail jobDetail = JobBuilder.newJob(HelloJobToTrigger.class)
                .withIdentity("job1", "group1")
                .build();

        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("3 * * * * ?"))
                .build();

        scheduler.scheduleJob(jobDetail, trigger);
        //全局监听
        scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), EverythingMatcher.allTriggers());
        //局部监听
        //scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "group1")));
        scheduler.start();
    }
}
3.执行结果

在这里插入图片描述

16.4.Scheduler Listener监听器的使用
1.Scheduler Listener源码
public interface SchedulerListener {
    /**
     * 用于部署Job Detail时调用
     */
    void jobScheduled(Trigger trigger);

    /**
     * 用于卸载JobDetail时调用
     */
    void jobUnscheduled(TriggerKey triggerKey);

    /**
     * 当一个Trigger来到了再也不会触发的状态时调用这个方法。除非这个Job已设置成了持久性,否则它就会中scheduler中移除
     */
    void triggerFinalized(Trigger trigger);

    /**
     * scheduler调用这个方法时发生在一个Trigger被暂停时调用。
     */
    void triggerPaused(TriggerKey triggerKey);

    /**
     * scheduler调用这个方法时发生在一个Trigger组被暂停时调用。
     */
    void triggersPaused(String triggerGroup);
    
    /**
     * scheduler调用这个方法发生在为一个Trigger从暂停中恢复时
     */
    void triggerResumed(TriggerKey triggerKey);

    /**
     * scheduler调用这个方法发生在为一个Trigger组从暂停中恢复时
     */
    void triggersResumed(String triggerGroup);

    /**
     * 添加任务时被调用
     */
    void jobAdded(JobDetail jobDetail);
    
    /**
     * 删除任务时被调用
     */
    void jobDeleted(JobKey jobKey);
    
    /**
     * 暂停任务时被调
     */
    void jobPaused(JobKey jobKey);

    /**
     * 暂停任务组时被调用
     */
    void jobsPaused(String jobGroup);
    
    /**
     * 恢复任务时被调用
     */
    void jobResumed(JobKey jobKey);

    /**
     * 恢复任务组时被调用
     */
    void jobsResumed(String jobGroup);

    /**
     * 在scheduler的政策运行期间发生严重错误时被调用
     */
    void schedulerError(String msg, SchedulerException cause);

    /**
     * 在scheduler处于挂载状态时被调用
     */
    void schedulerInStandbyMode();

    /**
     * 在scheduler开启时被调用
     */
    void schedulerStarted();
    
    /**
     * 当调度程序启动时调用该方法
     */
    void schedulerStarting();
    
    /**
     * 当scheduler关闭时调用该方法
     */
    void schedulerShutdown();
    
    /**
     * 当调度程序关闭时调用
     */
    void schedulerShuttingdown();

    /**
     * 当scheduler中的数据被清除时,调用该方法
     */
    void schedulingDataCleared();
}
2.使用演示案例

HelloJobToScheduler.java

/**
 * @Author ScholarTang
 * @Date 2021/7/13 下午10:03
 * @Desc 任务类
 */
@Slf4j
public class HelloJobToScheduler implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " | 任务被执行...");
    }
}

MySchedulerListener.java

/**
 * @Author ScholarTang
 * @Date 2021/7/13 下午10:05
 * @Desc TriggerListener
 */

@Slf4j
public class MySchedulerListener implements SchedulerListener {

    @Override
    public void jobScheduled(Trigger trigger) {
        log.info(trigger.getKey().getName() + " | 用于部署Job Detail时调用");
    }

    @Override
    public void jobUnscheduled(TriggerKey triggerKey) {
        log.info(triggerKey.getName() + " | 用于卸载JobDetail时调用");
    }

    @Override
    public void triggerFinalized(Trigger trigger) {
        log.info(trigger.getKey().getName() + " | 当一个Trigger来到了再也不会触发的状态时调用这个方法。除非这个Job已设置成了持久性,否则它就会中scheduler中移除");
    }

    @Override
    public void triggerPaused(TriggerKey triggerKey) {
        log.info(triggerKey.getName() + " | scheduler调用这个方法时发生在一个Trigger被暂停时调用。");
    }

    @Override
    public void triggersPaused(String triggerGroup) {
        log.info(triggerGroup + " | scheduler调用这个方法时发生在一个Trigger组被暂停时调用。");
    }

    @Override
    public void triggerResumed(TriggerKey triggerKey) {
        log.info(triggerKey.getName() + " | scheduler调用这个方法发生在为一个Trigger从暂停中恢复时");
    }

    @Override
    public void triggersResumed(String triggerGroup) {
        log.info(triggerGroup + " | scheduler调用这个方法发生在为一个Trigger组从暂停中恢复时");
    }

    @Override
    public void jobAdded(JobDetail jobDetail) {
        log.info("添加任务时被调用");
    }

    @Override
    public void jobDeleted(JobKey jobKey) {
        log.info("删除任务时被调用");
    }

    @Override
    public void jobPaused(JobKey jobKey) {
        log.info("暂停任务时被调");
    }

    @Override
    public void jobsPaused(String jobGroup) {
        log.info("暂停任务组时被调用");
    }

    @Override
    public void jobResumed(JobKey jobKey) {
        log.info("恢复任务时被调用");
    }

    @Override
    public void jobsResumed(String jobGroup) {
        log.info("恢复任务组时被调用");
    }

    @Override
    public void schedulerError(String msg, SchedulerException cause) {
        log.info("在scheduler的政策运行期间发生严重错误时被调用");
    }

    @Override
    public void schedulerInStandbyMode() {
        log.info("在scheduler处于挂载状态时被调用");
    }

    @Override
    public void schedulerStarted() {
        log.info("在scheduler开启时被调用");
    }

    @Override
    public void schedulerStarting() {
        log.info("当调度程序启动时调用该方法");
    }

    @Override
    public void schedulerShutdown() {
        log.info("当scheduler关闭时调用该方法");
    }

    @Override
    public void schedulerShuttingdown() {
        log.info("当调度程序关闭时调用");
    }

    @Override
    public void schedulingDataCleared() {
        log.info("当scheduler中的数据被清除时,调用该方法");
    }
}

HelloSchedulerDemoScheduler.java

/**
 * @Author ScholarTang
 * @Date 2021/7/13 下午10:05
 * @Desc 调度器
 */

public class HelloSchedulerDemoScheduler {
    public static void main(String[] args) throws Exception {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        JobDetail jobDetail = JobBuilder.newJob(HelloJobToScheduler.class)
                .withIdentity("job1", "group1")
                .build();

        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ?"))
                .build();

        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());
        scheduler.start();
        Thread.sleep(10000L);
        scheduler.shutdown();
    }
}
3.执行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t4OjhIIV-1626663674690)(/Users/jiangnan/Library/Application Support/typora-user-images/image-20210713225946758.png)]

17.SpringBoot整合Quartz实现动态的创建或删除定时任务并将定时调度任务持久化到MySQL以及Quartz集群配置

17.1.创建quartz数据库并导入quartz的SQL脚本文件

quartz源码下载地址:http://www.quartz-scheduler.org/downloads/
下载完成后解压,在/src/org/quartz/impl/jdbcjobstore可以找到对应数据库的SQL脚本
我这里使用的是MySQL数据库,SQL脚本如下:

CREATE DATABASE `quartz` /*!40100 DEFAULT CHARACTER SET utf8 */;

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;

CREATE TABLE QRTZ_JOB_DETAILS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    JOB_CLASS_NAME   VARCHAR(250) NOT NULL,
    IS_DURABLE VARCHAR(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    JOB_NAME  VARCHAR(200) NOT NULL,
    JOB_GROUP VARCHAR(200) NOT NULL,
    DESCRIPTION VARCHAR(250) NULL,
    NEXT_FIRE_TIME BIGINT(13) NULL,
    PREV_FIRE_TIME BIGINT(13) NULL,
    PRIORITY INTEGER NULL,
    TRIGGER_STATE VARCHAR(16) NOT NULL,
    TRIGGER_TYPE VARCHAR(8) NOT NULL,
    START_TIME BIGINT(13) NOT NULL,
    END_TIME BIGINT(13) NULL,
    CALENDAR_NAME VARCHAR(200) NULL,
    MISFIRE_INSTR SMALLINT(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
        REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_SIMPLE_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    REPEAT_COUNT BIGINT(7) NOT NULL,
    REPEAT_INTERVAL BIGINT(12) NOT NULL,
    TIMES_TRIGGERED BIGINT(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CRON_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    CRON_EXPRESSION VARCHAR(200) NOT NULL,
    TIME_ZONE_ID VARCHAR(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 INT NULL,
    INT_PROP_2 INT NULL,
    LONG_PROP_1 BIGINT NULL,
    LONG_PROP_2 BIGINT NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_BLOB_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    BLOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CALENDARS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    CALENDAR_NAME  VARCHAR(200) NOT NULL,
    CALENDAR BLOB NOT NULL,
    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP  VARCHAR(200) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_FIRED_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR(95) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    FIRED_TIME BIGINT(13) NOT NULL,
    SCHED_TIME BIGINT(13) NOT NULL,
    PRIORITY INTEGER NOT NULL,
    STATE VARCHAR(16) NOT NULL,
    JOB_NAME VARCHAR(200) NULL,
    JOB_GROUP VARCHAR(200) NULL,
    IS_NONCONCURRENT VARCHAR(1) NULL,
    REQUESTS_RECOVERY VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);

CREATE TABLE QRTZ_SCHEDULER_STATE
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR(200) NOT NULL,
    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);

CREATE TABLE QRTZ_LOCKS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME  VARCHAR(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);

commit;
17.2.构建SpringBoot工程整合Quartz
1.引入Quartz的POM依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2.YML配置

配置参考:https://github.com/wangmaoxiong/quartzjdbc/blob/master/src/main/resources/application-cluster.yml

spring:
  # Mysql数据库
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/quartz?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT
    username: root
    password: root
    # 连接池大小
    max-active: 20
    max-pool-prepared-statement-per-connection-size: 20
  # quartz相关配置
  quartz:
    # 将任务等保存化到数据库
    job-store-type: jdbc
    # 程序结束时会等待quartz相关的内容结束
    wait-for-jobs-to-complete-on-shutdown: true
    # QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
    overwrite-existing-jobs: true
    properties:
      org:
        quartz:
          # scheduler相关
          scheduler:
            # scheduler的实例名
            instanceName: scheduler
            instanceId: AUTO
          # 持久化相关
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            # 表示数据库中相关表是QRTZ_开头的
            tablePrefix: QRTZ_
            useProperties: false
            # 配置集群
            # 是否加入集群
            isClustered: true
            # 容许的最大作业延长时间
            clusterCheckinInterval: 20000
          # 线程池相关
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            # 线程数
            threadCount: 10
            # 线程优先级
            threadPriority: 5
            threadsInheritContextClassLoaderOfInitializingThread: true
3.编写Job类
/**
 * @Author ScholarTang
 * @Date 2021/7/14 下午4:09
 * @Desc 定时任务处理(允许并发执行)
 */
@Slf4j
@Component
public class QuartzJobExecution implements Job {

  @Autowired
  private SendNoticeUtils sendNoticeUtils;

  @SneakyThrows
  @Override
  public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    //执行逻辑体,发送消息
    sendNoticeUtils.sendNotice(jobExecutionContext);
  }
}
4.编写一个工具类,用来创建、删除定时调度任务的工具类
/**
 * @Author ScholarTang
 * @Date 2021/7/15 下午3:50
 * @Desc 定时任务工具类
 */

@Component
public class ScheduleUtils {

  @Autowired
  private Scheduler scheduler;

  /**
   * 构建任务触发对象
   * @param jobName
   * @param jobGroup
   * @return
   */
  public  TriggerKey getTriggerKey(String jobName, String jobGroup) {
    return TriggerKey.triggerKey(jobName, jobGroup);
  }

  /**
   * 构建任务键对象
   * @param jobName
   * @param jobGroup
   * @return
   */
  public  JobKey getJobKey(String jobName, String jobGroup) {
    return JobKey.jobKey(jobName, jobGroup);
  }

  /**
   * 创建日定时调度任务
   * @param schedulingTaskVo
   * @throws SchedulerException
   * @throws TaskException
   */
  public  void createScheduleJob(SchedulingTaskVo schedulingTaskVo) throws SchedulerException, TaskException {
    // 构建job信息
    String jobName = schedulingTaskVo.getJobId() + "_" + schedulingTaskVo.getJobName();
    String jobGroupName = schedulingTaskVo.getJobGroupName();

    //构建job实例
    JobDetail jobDetail = JobBuilder.newJob(QuartzJobExecution.class).withIdentity(getJobKey(jobName, jobGroupName)).build();

    // 表达式调度构建器
    CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(schedulingTaskVo.getCronExpression());
    cronScheduleBuilder = handleCronScheduleMisfirePolicy(schedulingTaskVo, cronScheduleBuilder);

    // 按新的cronExpression表达式构建一个新的trigger
    CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobName, jobGroupName))
      .withSchedule(cronScheduleBuilder).build();

    // 放入参数,运行时的方法可以获取
    jobDetail.getJobDataMap().put("TASK_PROPERTIES", schedulingTaskVo);

    // 判断是否存在
    if (scheduler.checkExists(getJobKey(jobName, jobGroupName))) {
      // 防止创建时存在数据问题 先移除,然后在执行创建操作
      scheduler.deleteJob(getJobKey(jobName, jobGroupName));
    }

    //创建定时任务调度
    scheduler.scheduleJob(jobDetail, trigger);

    // 暂停任务
    if (schedulingTaskVo.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) {
      scheduler.pauseJob(getJobKey(jobName, jobGroupName));
    }
  }


  /**
   * 删除定时调度任务
   * @param schedulingTaskVo
   * @throws SchedulerException
   */
  public void deleteScheduleJob(SchedulingTaskVo schedulingTaskVo) throws SchedulerException {
    scheduler.deleteJob(getJobKey(schedulingTaskVo.getJobId() + "_" + schedulingTaskVo.getJobName(), schedulingTaskVo.getJobGroupName()));
  }


  /**
   * 设置定时任务策略
   * @param SchedulingTaskVo
   * @param cb
   * @return
   */
  public  CronScheduleBuilder handleCronScheduleMisfirePolicy(SchedulingTaskVo SchedulingTaskVo, CronScheduleBuilder cb) throws TaskException {
    switch (SchedulingTaskVo.getMisfirePolicy()) {
      case "0":
        return cb;
      case "1":
        return cb.withMisfireHandlingInstructionIgnoreMisfires();
      case "2":
        return cb.withMisfireHandlingInstructionFireAndProceed();
      case "3":
        return cb.withMisfireHandlingInstructionDoNothing();
      default:
        throw new TaskException("任务失败策略 '" + SchedulingTaskVo.getMisfirePolicy()
          + "' 不能在cron计划任务中使用", TaskException.Code.CONFIG_ERROR);
    }
  }
}
5.异常类
/**
 * @Author ScholarTang
 * @Date 2021/7/15 上午11:40
 * @Desc 计划策略异常类
 */

public class TaskException extends Exception {
  private static final long serialVersionUID = 1L;

  private Code code;

  public TaskException(String msg, Code code) {
    this(msg, code, null);
  }

  public TaskException(String msg, Code code, Exception nestedEx) {
    super(msg, nestedEx);
    this.code = code;
  }

  public Code getCode() {
    return code;
  }

  public enum Code {
    TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE, SEND_NOTICE_ERROR
  }
}
6.最后

到这里定时调度服务的代码就写完了,如果是想动态的去创建/删除定时调度任务的话只需要将这个utils工具类注入到需要使用它的类中,去调用工具类中的方法创建或删除定时任务即可。

在创建任务时会将任务持久化到数据库,删除则是将数据库中存储的定时任务进行删除。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐