定时任务管理框架Quartz

304 阅读9分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

快速入口

Quartz API

Quartz API的关键接口是:

  • Scheduler - 与调度程序交互的主要API。
  • Job - 你想要调度器执行的任务组件需要实现的接口
  • JobDetail - 用于定义作业的实例。
  • Trigger(即触发器) - 定义执行给定作业的计划的组件。
  • JobBuilder - 用于定义/构建 JobDetail 实例,用于定义作业的实例。
  • TriggerBuilder - 用于定义/构建触发器实例。
  • Scheduler 的生命期,从 SchedulerFactory 创建它时开始,到 Scheduler 调用shutdown() 方法时结束;Scheduler 被创建后,可以增加、删除和列举 Job 和 Trigger,以及执行其它与调度相关的操作(如暂停 Trigger)。但是,Scheduler 只有在调用 start() 方法后,才会真正地触发 trigger(即执行 job)

Quartz 提供的“builder”类,可以认为是一种领域特定语言(DSL,Domain Specific Language)。教程一中有相关示例,这里是其中的代码片段:(校对注:这种级联的 API 非常方便用户使用,大家以后写对外接口时也可以使用这种方式)

Job和Trigger

一个 job 就是一个实现了 Job 接口的类,该接口只有一个方法:

你定义了一个实现Job接口的类,这个类仅仅表明该job需要完成什么类型的任务,除此之外,Quartz还需要知道该Job实例所包含的属性;这将由JobDetail类来完成。

  public interface Job {
    public void execute(JobExecutionContext context)
      throws JobExecutionException;
  }

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

JobDetail 对象是在将 job 加入 scheduler 时,由客户端程序(你的程序)创建的。它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap。本节是对 job 实例的简单介绍,更多的细节将在下一节讲到。

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

JobDetail

JobDetail实例是通过JobBuilder类创建的,导入该类下的所有静态方法,会让你编码时有DSL的感觉,让我们先看看Job的特征(nature)以及Job实例的生命期。

  // define the job and tie it to our HelloJob class
  JobDetail job = newJob(HelloJob.class)
      .withIdentity("myJob", "group1") // name "myJob", group "group1"
      .build();

  // Trigger the job to run now, and then every 40 seconds
  Trigger trigger = newTrigger()
      .withIdentity("myTrigger", "group1")
      .startNow()
      .withSchedule(simpleSchedule()
          .withIntervalInSeconds(40)
          .repeatForever())            
      .build();

  // Tell quartz to schedule the job using our trigger
  sched.scheduleJob(job, trigger);
  public class HelloJob implements Job {

    public HelloJob() {
    }

    public void execute(JobExecutionContext context)
      throws JobExecutionException
    {
      System.err.println("Hello!  HelloJob is executing.");
    }
  }

可以看到,我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job;每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。

那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:JobDataMap,JobDetail对象的一部分。

JobDataMap

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

总结

Quartz中

  • 一个job对象代表一个实现job接口的类的对象实例,包含excute方法,内部为具体的定时任务的业务代码,JobExecutionContext 为此方法参数,能够获取到作业实例
  • JobDetail是一个作业实例,对象是在将 job 加入 scheduler 时,由客户端程序(你的程序)创建的。它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap。
    • 一个JobDetail维护一个jobDataMap,存放获取参数
  • Trigger触发器,用于触发job的执行,创建一个新的线程,能够绑定cron
    • Trigger 也有一个相关联的 JobDataMap,用于给 Job 传递一些触发相关的参数。Quartz 自带了各种不同类型的 Trigger,最常用的主要是 SimpleTrigger 和 CronTrigger。

优点

支持多线程,每个定时任务的每次执行都会开辟一个新的线程,即使上个线程执行发生错误,不会影响下个线程的执行。

比如系统调用addJob方法开放了两个定时任务,分别为定时任务1和定时任务2,定时任务1每隔10s执行一次,定时任务2每隔2s执行一次,每个任务执行对应的代码需要5s。10:00开始分别执行,此时任务1、2都开始执行。

先看任务1,10:00开辟了一个线程1执行任务1,5秒后执行完毕,将该线程1关闭,10:10开辟一个新的线程2,开始执行任务1。以此类推。

再看任务2,10:00开辟了一个线程7执行任务2,2秒后10:02创建一个新的线程8执行任务2,再过2秒后10:04创建一个新的线程9执行任务2,10:05此时线程7终于执行完毕了,将该线程7关闭,以此类推。 在这里插入图片描述

demo

@Controller
public class MyController {
    @ResponseBody
    @RequestMapping("/ttt")
    public void s() throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
                .build();

        JobDetail job =JobBuilder.newJob(Myjob.class).build();

        scheduler.scheduleJob(job,trigger);

        scheduler.start();
    }
}

Quartz工具类

开启、关闭定时任务 方法:

  • 开启一个定时任务:addJob(.......)
  • 开启一个定时任务(带参数):addJob(......,params)
  • 关闭一个定时任务:removeJob(......)
  • 修改一个定时任务名:modifyJob(......)
/**
 * @program: 
 * @description:
 * @author: ZW
 * @email:
 * @create: 2022-06-17 09:51
 **/

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Map;
import static org.quartz.JobBuilder.newJob;

public class QuartzManager {

    private static SchedulerFactory schedulerFactory = new StdSchedulerFactory();

    /**
     * @Description: 添加一个定时任务
     *
     * @param jobName 任务名
     * @param jobGroupName  任务组名
     * @param triggerName 触发器名
     * @param triggerGroupName 触发器组名
     * @param jobClass  任务
     * @param cron   时间设置,参考quartz说明文档
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void addJob(String jobName, String jobGroupName,
                              String triggerName, String triggerGroupName, Class jobClass, String cron) {
        try {
            Scheduler sched = schedulerFactory.getScheduler();
            // 任务名,任务组,任务执行类
            JobDetail jobDetail= newJob(jobClass).withIdentity(jobName, jobGroupName).build();

            // 触发器
            TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
            // 触发器名,触发器组
            triggerBuilder.withIdentity(triggerName, triggerGroupName);
            triggerBuilder.startNow();
            // 触发器时间设定
            triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
            // 创建Trigger对象
            CronTrigger trigger = (CronTrigger) triggerBuilder.build();

            // 调度容器设置JobDetail和Trigger
            sched.scheduleJob(jobDetail, trigger);

            // 启动
            if (!sched.isShutdown()) {
                sched.start();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description: 添加一个定时任务(带参数)
     *
     * @param jobName 任务名
     * @param jobGroupName  任务组名
     * @param triggerName 触发器名
     * @param triggerGroupName 触发器组名
     * @param jobClass  任务
     * @param cron   时间设置,参考quartz说明文档
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void addJobWithParams(String jobName, String jobGroupName,
                                       String triggerName, String triggerGroupName, Class jobClass, Map<String,Object> params, String cron) {
        try {
            Scheduler sched = schedulerFactory.getScheduler();
            // 任务名,任务组,任务执行类
            JobDetail jobDetail= JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();

            for (String key:params.keySet()){
                jobDetail.getJobDataMap().put(key,params.get(key));
            }

            // 触发器
            TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
            // 触发器名,触发器组
            triggerBuilder.withIdentity(triggerName, triggerGroupName);
            triggerBuilder.startNow();
            // 触发器时间设定
            triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
            // 创建Trigger对象
            CronTrigger trigger = (CronTrigger) triggerBuilder.build();

            // 调度容器设置JobDetail和Trigger
            sched.scheduleJob(jobDetail, trigger);

            // 启动
            if (!sched.isShutdown()) {
                sched.start();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description: 修改一个任务的触发时间
     *
     * @param jobName
     * @param jobGroupName
     * @param triggerName 触发器名
     * @param triggerGroupName 触发器组名
     * @param cron   时间设置,参考quartz说明文档
     */
    public static void modifyJobTime(String jobName,
                                     String jobGroupName, String triggerName, String triggerGroupName, String cron) {
        try {
            Scheduler sched = schedulerFactory.getScheduler();
            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }

            String oldTime = trigger.getCronExpression();
            if (!oldTime.equalsIgnoreCase(cron)) {
                /** 方式一 :调用 rescheduleJob 开始 */
                // 触发器
                TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
                // 触发器名,触发器组
                triggerBuilder.withIdentity(triggerName, triggerGroupName);
                triggerBuilder.startNow();
                // 触发器时间设定
                triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
                // 创建Trigger对象
                trigger = (CronTrigger) triggerBuilder.build();
                // 方式一 :修改一个任务的触发时间
                sched.rescheduleJob(triggerKey, trigger);
                /** 方式一 :调用 rescheduleJob 结束 */

                /** 方式二:先删除,然后在创建一个新的Job  */
                //JobDetail jobDetail = sched.getJobDetail(JobKey.jobKey(jobName, jobGroupName));
                //Class<? extends Job> jobClass = jobDetail.getJobClass();
                //removeJob(jobName, jobGroupName, triggerName, triggerGroupName);
                //addJob(jobName, jobGroupName, triggerName, triggerGroupName, jobClass, cron);
                /** 方式二 :先删除,然后在创建一个新的Job */
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description: 移除一个任务
     *
     * @param jobName
     * @param jobGroupName
     * @param triggerName
     * @param triggerGroupName
     */
    public static void removeJob(String jobName, String jobGroupName,
                                 String triggerName, String triggerGroupName) {
        try {
            Scheduler sched = schedulerFactory.getScheduler();

            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);

            sched.pauseTrigger(triggerKey);// 停止触发器
            sched.unscheduleJob(triggerKey);// 移除触发器
            sched.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 删除任务
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description:启动所有定时任务
     */
    public static void startJobs() {
        try {
            Scheduler sched = schedulerFactory.getScheduler();
            sched.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Description:关闭所有定时任务
     */
    public static void shutdownJobs() {
        try {
            Scheduler sched = schedulerFactory.getScheduler();
            if (!sched.isShutdown()) {
                sched.shutdown();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

测试

MyJob.java 任务类

import java.util.Date;

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

public class MyJob implements Job{

    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println(new Date() + ": doing something...");
    }
}

Test.Java 测试类

public class Test {

    public static String JOB_NAME = "动态任务调度";  
    public static String TRIGGER_NAME = "动态任务触发器";  
    public static String JOB_GROUP_NAME = "XLXXCC_JOB_GROUP";  
    public static String TRIGGER_GROUP_NAME = "XLXXCC_JOB_GROUP"; 

    public static void main(String[] args) {
        try {  
            System.out.println("【系统启动】开始(每1秒输出一次)...");    
            QuartzManager.addJob(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME, MyJob.class, "0/1 * * * * ?");    

            Thread.sleep(5000);    
            System.out.println("【修改时间】开始(每5秒输出一次)...");    
            QuartzManager.modifyJobTime(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME, "0/5 * * * * ?");    

            Thread.sleep(6000);    
            System.out.println("【移除定时】开始...");    
            QuartzManager.removeJob(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME);    
            System.out.println("【移除定时】成功");    
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }
}

增加参数的demo

每一个任务中增加参数的demo如下

/**
     * @Description: 添加一个定时任务(自定义参数)
     *
     * @param jobName 任务名
     * @param jobGroupName  任务组名
     * @param triggerName 触发器名
     * @param triggerGroupName 触发器组名
     * @param jobClass  任务
     * @param cron   时间设置,参考quartz说明文档
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void addJobWithParams(String jobName, String jobGroupName,
                                       String triggerName, String triggerGroupName, Class jobClass, Map<String,Object> params, String cron) {
        try {
            Scheduler sched = schedulerFactory.getScheduler();
            // 任务名,任务组,任务执行类
            JobDetail jobDetail= JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();

            for (String key:params.keySet()){
                jobDetail.getJobDataMap().put(key,params.get(key));
            }

            // 触发器
            TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
            // 触发器名,触发器组
            triggerBuilder.withIdentity(triggerName, triggerGroupName);
            triggerBuilder.startNow();
            // 触发器时间设定
            triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
            // 创建Trigger对象
            CronTrigger trigger = (CronTrigger) triggerBuilder.build();

            // 调度容器设置JobDetail和Trigger
            sched.scheduleJob(jobDetail, trigger);

            // 启动
            if (!sched.isShutdown()) {
                sched.start();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }