SpringBoot 集成 Quartz 01

实验环境

  • IDEA
  • Spring Boot:2.4.4
  • Gradle:6.8.3
  • Quartz:2.3.2

Quartz 简介

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

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

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

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

Quartz 核心概念

  1. Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:

    void execute(JobExecutionContext context)
    
    • job的一个 trigger 被触发后,execute() 方法会被 scheduler 的一个工作线程调用;传递给 execute() 方法的 JobExecutionContext 对象中保存着该 job 运行时的一些信息 ,包括执行 job 的 scheduler 的引用、触发 job 的 trigger 的引用、JobDetail 对象引用,以及一些其它信息。

    • 如果在 job 类中,为 JobDataMap 中存储的数据的 key 增加 set 方法,那么 Quartz 的默认 JobFactory 实现在 job 被实例化的时候会自动调用这些 set 方法,这样就不需要在 execute() 方法中显式地从map中取数据了。

    • 在 job 类上可以加入一些注解,这些注解会影响 job 的状态和并发性。
      | 注解 | 说明 | 备注 |
      | ----------------------------- | ------------------------------------------------------------ | ------------------------------------------ |
      | @DisallowConcurrentExecution | 告诉 Quartz 不要并发地执行同一个 job 定义(这里指特定的 job 类)的多个实例 | 该限制是针对 JobDetail 的,而不是 job 类的 |
      | @PersistJobDataAfterExecution | 告诉 Quartz 在成功执行了 job 类的 execute 方法后(没有发生任何异常),更新 JobDetail 中 JobDataMap 的数据,使得该 job(即 JobDetail)在下一次执行的时候,JobDataMap 中是更新后的数据,而不是更新前的旧数据 | 该限制是针对 JobDetail 的,而不是 job 类的 |

    注:如果使用了 @PersistJobDataAfterExecution 注解,强烈建议你同时使用 @DisallowConcurrentExecution 注解,因为当同一个 job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap 中存储的数据很可能是不确定的。

    • execute 方法中仅允许抛出一种类型的异常(包括 RuntimeExceptions),即 JobExecutionException。因此,你应该将 execute 方法中的所有内容都放到一个”try-catch”块中。

  2. JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,除此之外 JobDetail 还包含了这个任务调度的方案和策略。

    • JobDetail 对象是在将 job 加入 scheduler 时,由客户端程序创建的。它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap。

    • 通过 JobDetail 对象,可以给 job 实例配置的属性有:
      | 属性 | 说明 | 备注 |
      | ---------------- | ------------------------------------------------------------ | ------------------------------------------------------- |
      | Durability | 如果一个 job 是非持久的,当没有活跃的 trigger 与之关联的时候,会被自动地从 scheduler 中删除 | |
      | RequestsRecovery | 如果一个 job 是可恢复的,并且在其执行的时候,schedule r发生硬关闭,则当 scheduler 重新启动的时候,该 job 会被重新执行 | 该 job 的 JobExecutionContext.isRecovering() 返回 true |

  3. Trigger 代表一个触发器,当准备调度一个 job 时,创建一个 Trigger 的实例,然后设置调度相关的属性。Trigger 有一个相关联的 JobDataMap,用于给 Job 传递一些触发相关的参数。Quartz 自带了各种不同类型的 Trigger,最常用的主要是 SimpleTrigger 和 CronTrigger。

    • SimpleTrigger 主要用于精确指定间隔进行重新启动的作业启动计划,例如只在某个特定的时间点执行一次,或者在特定的时间点执行,重复执行 N 次,每次执行间隔T个时间单位。

    • CronTrigger 在基于基于日历的概念进行重新启动的作业启动计划,例如“每个星期五的正午”,或者“每月的第十天的上午 10:15”等。

    • Job 被创建后,可以保存在 Scheduler 中,与 Trigger 是独立的,同一个 Job 可以有多个 Trigger。

    • Trigger 可配置属性:

    属性 拥有属性的 Trigger 类型 说明 备注
    TriggerKey SimpleTrigger/CronTrigger 表示 trigger 的身份 没有设置,TriggerBuilder会为trigger生成一个随机的名称
    jobKey SimpleTrigger/CronTrigger 当 trigger 触发时被执行的 job 的身份
    startTime SimpleTrigger/CronTrigger 设置 trigger 第一次触发的时间 没有设置,默认使用当前时间
    endTime SimpleTrigger/CronTrigger 表示trigger失效的时间点 可选
    priority SimpleTrigger/CronTrigger 优先级 比如,你有N个 trigger 需要同时触发,但只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为 trigger 设置优先级,trigger 使用默认优先级,值为5
    misfire Instructions SimpleTrigger/CronTrigger 错过触发机制 如果 scheduler 关闭了,或者 Quartz 线程池中没有可用的线程来执行 job,此时持久性的 trigger 就会错过(miss)其触发时间,即错过触发(misfire)。当scheduler启动的时候,查询所有错过触发(misfire)的持久性 trigger。然后根据它们各自的 misfire 机制更新 trigger 的信息
    calendar SimpleTrigger/CronTrigger 日历 Calendar 用于从 trigger 的调度计划中排除时间段
    repeat SimpleTrigger 重复次数
    Interval SimpleTrigger 重复的间隔
    Cron Expressions CronTrigger Cron表达式
  4. Scheduler 代表一个调度器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 调度了。

实验步骤

Quartz 的简单使用

  1. 在 build.gradle 文件中 dependencies 中添加 quartz 依赖

    implementation 'org.springframework.boot:spring-boot-starter-quartz'
    
  2. 新建一个Job类

TestJob.class

   package club.lacerate.blog.modules.quartz;
   
   import org.quartz.Job;
   import org.quartz.JobExecutionContext;
   import org.quartz.JobExecutionException;
   
   import java.time.LocalDateTime;
   
   public class TestJob implements Job {
   
       @Override
       public void execute(JobExecutionContext context) throws JobExecutionException {
           System.out.println("------定时任务执行开始------");
           System.out.println("当前时间"+ LocalDateTime.now());
           System.out.println("------定时任务执行结束------");
       }
   }
  1. 编写主函数,创建 scheduler、Trigger、job,并执行任务

Test.java

   package club.lacerate.blog.modules.quartz;
   
   import org.quartz.JobBuilder;
   import org.quartz.JobDetail;
   import org.quartz.Scheduler;
   import org.quartz.SchedulerException;
   import org.quartz.SimpleScheduleBuilder;
   import org.quartz.Trigger;
   import org.quartz.TriggerBuilder;
   import org.quartz.impl.StdSchedulerFactory;
   
   public class Test {
   
       public static void main(String[] args) throws SchedulerException {
           // 创建一个 scheduler
           Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
           // 创建一个 Trigger
           Trigger trigger = TriggerBuilder.newTrigger()
                   .withIdentity("trigger1", "group1")
                   .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                   .repeatForever()).build();
   
           // 创建一个 job
           JobDetail job = JobBuilder.newJob(TestJob.class)
                   .withIdentity("jobName1", "jobGroup1").build();
   
           // 注册 trigger 并启动scheduler
           scheduler.scheduleJob(job, trigger);
           scheduler.start();
       }
   }
  1. 运行 Test.main()

从控制台输出可见,定时任务每个3s 执行一次