详解 Quartz --- Java 实现定时任务

Quartz

在这里插入图片描述

1. 快速开始

1.1 依赖

<dependency>
    <groupId>org.quartz-scheduler</groupId>   
    <artifactId>quartz</artifactId>  
    <version>2.3.0</version>   
    <exclusions>      
        <exclusion>         
        <artifactId>slf4j-api</artifactId>         
        <groupId>org.slf4j</groupId>      
        </exclusion>   
    </exclusions>
</dependency>

<dependency>   
    <groupId>org.quartz-scheduler</groupId>   
    <artifactId>quartz-jobs</artifactId>   
    <version>2.3.0</version>
</dependency>

<dependency>   
    <groupId>ch.qos.logback</groupId>  
    <artifactId>logback-classic</artifactId>  
    <version>1.2.3</version>
</dependency>

1.2 Quartz 配置文件

Quartz 默认会在 classpath 下去找一个 quartz.properties 的配置文件, 该配置文件并不是必须的, 但是通常都是需要的.

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
  • org.quartz.scheduler.instanceName: 指定 Schedule 实例的名称;
  • org.quartz.threadPool.threadCount: 线程数, 意味着最多可以同时运行 3 个任务;
  • org.quartz.jobStore.class: 指定 Quartz 数据的存放形式. Quartz的所有数据(例如作业和触发器的详细信息)都保存在内存中(而不是数据库中).

1.3 获取并启动 Schedule 实例

// 获取 Schedule 实例, 该实例的信息(比如: 实例名称)将会从 quartz.properties 中获取.
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

// and start it off
scheduler.start();

1.4 创建任务(JobDetail)

创建一个类, 实现 org.quartz.Job 接口并在 execute 方法中添加任务的逻辑.
通过 JobBuilder.newJob 的静态方法创建 JobDetail

package com.jackli.schedule.job;

import org.quartz.*;

import java.time.LocalDateTime;

@PersistJobDataAfterExecution // 标记当前 Job 为一个有状态的 Job
public class PrintDateTimeJob implements Job {

   public PrintDateTimeJob() {
      System.out.println("======PrintDateTimeJob=======");
   }

   public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
      String dateTime = LocalDateTime.now().toString();

      System.out.println("$$The current dateTime is " + dateTime);

      try {
         Thread.sleep(2000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

      System.out.println("$$PrintDateTimeJob execute success.");
   }

    /**
     * 创建 JobDetail 实例.
     * Job 的 name 是必须的, Group 是可选的, 如果不指定, 默认为 DEFAULT
     */
   public static JobDetail buildJob(String key) {
      return JobBuilder.newJob(PrintDateTimeJob.class)
         .withIdentity(key, JOB_GROUP)
         .storeDurably() // 如果添加 Job 时没有指定 trigger 需要指定 durably
         .build();
   }

   public static final String JOB_GROUP = "print";
}

1.5 运行 Job

1.5.1 创建触发器

每一个 Job 都需要一个触发器才会执行, 在触发器中将指定何时执行? 多久执行一次? 重复多少次等信息.
创建触发器可以通过 TriggerBuilder.newTrigger 静态方法创建.

Trigger trigger = newTrigger()
   .withIdentity("jack-dateTime", PrintDateTimeJob.JOB_GROUP)
   .forJob(jobDetail.getKey()) // 仅用 trigger 执行 job, trigger 必须指定 jobKey.
   .startNow()
   .withSchedule(simpleSchedule().withIntervalInSeconds(1).repeatForever())
   .build();

1.5.2 执行 Job

执行 Job 有多种方式:

  • 持有 JobDetail 和 Trigger 直接触发执行
scheduleServer.scheduleJob(jobDetail, trigger);
  • 先添加 Job, 再用触发器触发
scheduleServer.addJob(jobDetail);

scheduleServer.scheduleJob(trigger);

addJob 方法必须指定 Job 为 durable, 否则将会抛出异常.
scheduleJob 方法如果没指定JobDetail, 那么 Trigger 就必须指定 jobKey, 通过.forJob(key)

1.5.3 关闭 Schedule

关闭 Schedule 有两种方式.

  • scheduler.shutdown(waitForJobsToComplete) 如果 waitForJobsToComplete 为 true, 则表示等待当前正在运行的 Jobs 运行完成之后再关闭 Schedule 实例; 如果为 false 则表示立即关闭 Schedule 实例.
    scheduler.shutdown() 等同于 scheduler.shutdown(false)

2. 核心 API

2.1 Trigger

Quartz 提供的触发器有四种, 常用的有两种: SimpleTrigger 和 CronTrigger.

  • CalendarIntervalScheduleBuilder
  • CronScheduleBuilder
  • DailyTimeIntervalScheduleBuilder
  • SimpleScheduleBuilder

2.1.1 SimpleTrigger

SimpleTrigger 是由 SimpleScheduleBuilder 构造的简单触发器, SimpleScheduleBuilder 使用 Build 模式构造对象.
SimpleScheduleBuilder 可以实现一些简单的定时触发, 比如: 每几秒执行一次, 重复执行多少次等.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EEslgJ1D-1574041461603)(en-resource://database/1263:1)]

Trigger trigger = newTrigger()
   .withIdentity("jack-dateTime", PrintDateTimeJob.JOB_GROUP)
   .forJob(jobDetail.getKey())
   .startNow()
   .withSchedule(simpleSchedule().withIntervalInSeconds(1).repeatForever())
   .build();

2.1.2 CronTrigger

Cron 触发器的核心就是 cron 表达式的书写.

  • cron 表达式有 7 个值, 最后一个 [年] 为可选参数
  • 格式为 [秒] [分] [小时] [日] [月] [周] [年] .
  • 其中 [周] 与 [日] 相互冲突, 因此如果设置了其中一项, 则另一项用 ? 表示不设置此值.

cron 表达式:
在这里插入图片描述

其中
在这里插入图片描述

  • , 表示或的意思
  • - 表示至的关系
  • * 表示任意的关系
  • / 表示每的关系
  • # 表示第的关系
  • L 表示最后的关系
  • ? 表示不设置该值, 用于解决 [日] 和 [周] 的冲突.
  • 周上的1-7 表示 周日(1) 到周六

For example
在这里插入图片描述

一般可以通过在线工具去生成 cron 表达式而不用自己去写.

2.2 JobDetail

JobDetail 代表一个将要执行的任务.

  • JobDetail 通过 JobBuilder.newJob 进行构造, 除此之外一般需要指定 name 和 group, name 属性默认为 UUID, group 默认为 "DEFAULT".
  • 如果 JobDetail 通过scheduler.addJob 方法添加到 Schedule, 则 JobDetail 必须指定 storeDurably().
  • 同一个 identification(group.name) 必须在 Schedule 中唯一, 如果再次添加已经存在的 JobDetail, 则会抛错 ObjectAlreadyExistsException.
  • JobDetail 如果需要传递参数到 job 执行时, 则在构造时通过 .usingJobData() 传递. 如果需要传递引用数据类型, 则需要自己 new JobDataMap 然后 put 进去.
  • 参数的获取通过jobExecutionContext.getJobDetail() 获取到 JobDataMap, 然后从 Map 中 get.
public static JobDetail buildJob(String key) {
   JobDataMap jobDataMap = new JobDataMap();
   jobDataMap.put("objectKey", new Object());

   return JobBuilder.newJob(PrintDateTimeJob.class)
      .withIdentity(key, JOB_GROUP)
      // 传递基本数据类型
      .usingJobData("dKey", "dValue")
      // 传递引用数据类型
      .usingJobData(jobDataMap)
      .storeDurably() // 如果添加 Job 时没有指定 trigger 需要指定 durably
      .build();
}
   public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
      JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
      String baseValue = jobDataMap.getString("dKey");
      Object object = jobDataMap.get("objectKey");

2.3 Listener

Listener 有全局 Listener 和局部 Listener之分.

2.3.1 JobListener

2.3.1.1 创建 JobListener

写一个类实现 JobListener 接口

package com.jackli.schedule.listner;

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

public class MyJobListener implements JobListener {
   @Override
   public String getName() {
      // Listener Name. 不能为空.
      return this.getClass().getSimpleName();
   }

   @Override
   public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
      System.out.println("Job 将要被执行....");
   }

   @Override
   public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
      System.out.println("Job 被阻止执行时调用...");
   }

   @Override
   public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
      System.out.println("Job 被执行完后调用...");
   }
}

2.3.1.2 注册 JobListener

  • 注册全局 JobListener
scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());

scheduler.getListenerManager().addJobListener(new MyJobListener());
  • 注册局部 Listener
scheduler.getListenerManager().addJobListener(new MyJobListener(), NameMatcher.jobNameContains("jack"));

scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("jack-dateTime", PrintDateTimeJob.JOB_GROUP)));

2.3.2 TriggerListener

2.3.2.1 创建 TriggerListener

package com.jackli.schedule.listner;

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

import java.util.Random;

public class MyTriggerListener implements TriggerListener {
   @Override
   public String getName() {
      // Listener Name. 不能为空.
      return this.getClass().getSimpleName();
   }

   @Override
   public void triggerFired(Trigger trigger, JobExecutionContext jobExecutionContext) {
      // 与监听器相关联的 Trigger 被触发, Job 的 execute 方法将要执行时调用
      System.out.println("triggerFired....");
   }

   @Override
   public boolean vetoJobExecution(Trigger trigger, JobExecutionContext jobExecutionContext) {
      // 阻止 Job 执行. 当 Job 将要执行时如果该方法返回 true, 则 Job 将不会执行本次触发.
      boolean result = new Random().nextInt(10) > 5;
      System.out.println("vetoJobExecution....." + result);

      return result;
   }

   @Override
   public void triggerMisfired(Trigger trigger) {
      // 在 Trigger 错过触发时执行.
      System.out.println("triggerMisfired....");
   }

   @Override
   public void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {
      /**
       * Trigger 被触发, 并且完成了 Job 的执行时触发.
       * 该方法在 JobListener 的 jobWasExecuted 方法执行后执行.
       * 如果 Job 被 vetoJobExecution 阻止, 则该方法不会执行.
       */
      System.out.println("triggerComplete...");
   }
}

2.3.2.2 注册 TriggerListener

// 创建全局 TriggerListener
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), EverythingMatcher.allTriggers());

// 创建局部 TriggerListener
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener(), KeyMatcher.keyEquals(new TriggerKey("jack-dateTime", PrintDateTimeJob.JOB_GROUP)));

2.3.3 SchedulerListener

2.3.3.1 创建 SchedulerListener

package com.jackli.schedule.listner;

import org.quartz.*;

public class MyScheduleListener implements SchedulerListener {
   @Override
   public void jobScheduled(Trigger trigger) {
      System.out.println("jobScheduled...部署 JobDetail 时调用...");
   }

   @Override
   public void jobUnscheduled(TriggerKey triggerKey) {
      System.out.println("jobUnscheduled...卸载 JobDetail 时调用..." + triggerKey.getName());
   }

   @Override
   public void triggerFinalized(Trigger trigger) {
      System.out.println("triggerFinalized...当这个 trigger 再也不会被触发时调用, " +
         "除非对应的 JobDetail 被设置成了持久化的, 否则他就会从 Schedule 中移除..."
         + trigger.getKey().getName());
   }

   @Override
   public void triggerPaused(TriggerKey triggerKey) {
      System.out.println("triggerPaused....当一个 Trigger 被暂停时调用..." +
        triggerKey.getName());
   }

   @Override
   public void triggersPaused(String triggerGroup) {
      System.out.println("triggersPaused....当一个 Trigger 组被暂停时调用..." +
         "如果是 Trigger 组, 那么 key 的 name 将为 null.." + triggerGroup);
   }

   @Override
   public void triggerResumed(TriggerKey triggerKey) {
      System.out.println("triggerResumed....Trigger 被恢复执行时触发..." + triggerKey.getName());
   }

   @Override
   public void triggersResumed(String triggerGroup) {
      System.out.println("triggersResumed... Trigger 组被恢复执行时触发..." + triggerGroup);
   }

   @Override
   public void jobAdded(JobDetail jobDetail) {
      System.out.println("jobAdded... Job 被添加到 Schedule 时执行..." + jobDetail.getKey());
   }

   @Override
   public void jobDeleted(JobKey jobKey) {
      System.out.println("jobDeleted... Job 从 Schedule 移除时执行..." + jobKey);
   }

   @Override
   public void jobPaused(JobKey jobKey) {
      System.out.println("jobPaused....job 被暂停执行时触发..." + jobKey);
   }

   @Override
   public void jobsPaused(String jobGroup) {
      System.out.println("jobsPaused....job 组被暂停执行时触发..." + jobGroup);
   }

   @Override
   public void jobResumed(JobKey jobKey) {
      System.out.println("jobResumed....job 被恢复执行时触发..." + jobKey);
   }

   @Override
   public void jobsResumed(String jobGroup) {
      System.out.println("jobsResumed....job 组被恢复执行时触发..." + jobGroup);
   }

   @Override
   public void schedulerError(String msg, SchedulerException cause) {
      System.out.println("schedulerError...Schedule 发生错误时执行...msg: " + msg + ", cause: " + cause);
   }

   @Override
   public void schedulerInStandbyMode() {
      System.out.println("schedulerInStandbyMode...当 Schedule 处于 StandBy(等待或者备用) 模式时调用...");
   }

   @Override
   public void schedulerStarted() {
      System.out.println("schedulerStarted...Schedule 已经启动时调用...");
   }

   @Override
   public void schedulerStarting() {
      System.out.println("schedulerStarting...Schedule 正在启动时调用...");
   }

   @Override
   public void schedulerShutdown() {
      System.out.println("schedulerShutdown....Schedule 停止时调用...");
   }

   @Override
   public void schedulerShuttingdown() {
      System.out.println("schedulerShuttingdown...Schedule 正在关闭时调用...");
   }

   @Override
   public void schedulingDataCleared() {
      System.out.println("schedulingDataCleared...当 Schedule 中的数据被清空时调用该方法...");
   }
}

2.3.3.2 注册 SchedulerListener

SchedulerListener 没有全局与局部之分

scheduler.getListenerManager().addSchedulerListener(new MyScheduleListener());

3. 配置

3.1 配置文件的加载

  • org/quartz/quartz.properties
  • classpath/quartz.properties
  • 手动指定配置文件的位置和名称
    • 手动指定使用的 properties 文件就不能通过 StdSchedulerFactory.getDefaultScheduler() 获取. 需要自己去 new StdSchedulerFactory.
public StdSchedulerFactory(Properties props) throws SchedulerException {
    initialize(props);
}

public StdSchedulerFactory(String fileName) throws SchedulerException {
    initialize(fileName);
}

3.2 Schedule 主配置

  • org.quartz.scheduler.instanceName

可以是任何字符串,并且该值对调度器本身没有意义——而是作为客户机代码的一种机制,以便在同一程序中使用多个实例时区分调度器。如果使用集群特性,则必须对集群中“逻辑上”相同的调度程序的每个实例使用相同的名称。

  • org.quartz.scheduler.instanceId

可以是任何字符串,但对于所有工作的调度程序必须是惟一的,就好像它们是集群中相同的“逻辑”调度程序一样。如果希望为自己生成Id,可以使用值“AUTO”作为instanceId。如果希望值来自系统属性“org.quartz.scheduler.instanceId”,则使用“SYS_PROP”。

  • org.quartz.scheduler.instanceIdGenerator.class

org.quartz.simpl.SimpleInstanceIdGenerator”,它根据主机名和时间戳生成实例ID。其他IntanceIdGenerator实现包括SystemPropertyInstanceIdGenerator(从系统属性“ org.quartz.scheduler.instanceId”获取实例ID)和HostnameInstanceIdGenerator(使用本地主机名(InetAddress.getLocalHost()。getHostName()))。您也可以实现InstanceIdGenerator接口自己。

  • org.quartz.scheduler.threadName

可以是Java线程的有效名称的任何String。如果未指定此属性,则线程将收到调度程序的名称(“ org.quartz.scheduler.instanceName”)追加字符串 “_QuartzSchedulerThread” 作为线程名.

在这里插入图片描述

4. Example

GitHub


没过瘾? 关注我获取更多技术干货

扫一扫关注公众号, 加入 QQ 群

我们

一起进步 !

在这里插入图片描述

一个致力于技术交流讨论的技术型 QQ 群

要错过这几百个亿吗?
在这里插入图片描述

发布了25 篇原创文章 · 获赞 36 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/DreamLi1314/article/details/103118109