欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Quartz 任务调度框架

程序员文章站 2024-03-18 17:06:28
...

Quartz 任务调度框架

一、背景和简介

1、产生背景

​ 从JDK1.3开始,Java通过java.util.Timerjava.util.TimerTask可以实现定时器。为什么要使用Quartz而不是使用Java中的这些标准功能呢?

​ 主要原因如下:

  • Timers没有持久化机制
  • Timers不灵活 (只可以设置开始时间和重复间隔,不是基于时间、日期、天等(秒、分、时)的)
  • Timers 不能利用线程池,一个timer一个线程
  • Timers没有真正的管理计划
2、简介

​ Quartz是一个完全由Java编写的开源任务调度的框架,通过触发器设置作业定时运行规则,控制作业的运行时间。其中quartz集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。主要用来执行定时任务,如:定时发送信息、定时生成报表等等。

​ 优势:

  • Quartz是非常灵活的,并包含多个使用范例,它们可以单独或一起使用,以实现您所期望的行为,并使您能够以最“自然”的方式来编写您的项目的代码。
  • Quartz是非常轻量级的,只需要非常少的配置 —— 它实际上可以被跳出框架来使用,如果你的需求是一些相对基本的简单的需求的话。
  • Quartz具有容错机制,并且可以在重启服务的时候持久化(”记忆”)你的定时任务,你的任务也不会丢失

二、构成和使用

1、Quartz的三大组件

​ Quartz框架主要核心组件包括调度器、触发器、作业。调度器作为作业的总指挥,触发器作为作业的操作者,作业为应用的功能模块。

  • 简介
  • Job为作业的接口,为任务调度的对象 。
  • JobDetail用来描述Job的实现类及其它相关的静态信息 ,用于包装业务代码为一个可执行的job。
  • Trigger做为作业的定时管理工具,一个Trigger只能对应一个作业实例,而一个作业实例可对应多个触发器 。
  • scheduler做为定时任务容器,是quartz最上层的东西,它提携了所有触发器和作业,使它们协调工作,每个Scheduler都存有JobDetail和Trigger的注册,一个Scheduler中可以注册多个JobDetail和多个Trigger 。

Quartz 任务调度框架

2、组件使用
  • Job

​ Job是一个接口,只有一个方法void execute(JobExecutionContext context),被调度的作业(类)需实现该接口中execute()方法,JobExecutionContext类提供了调度上下文的各种信息。每次执行该Job均重新创建一个Job实例 。

  • JobDetail

​ Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。JobDetail 用来保存我们作业的详细信息。一个JobDetail可以有多个Trigger,但是一个Trigger只能对应一个JobDetail 。

  • Trigger

​ Trigger是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和 CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则 可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等 。

  • Scheduler

​ Scheduler负责管理Quartz的运行环境,Quartz它是基于多线程架构的,它启动的时候会初始化一套线程,这套线程会用来执行一些预置的作业。Trigger和JobDetail可以注册到Scheduler中;Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。 Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。

三、原理和实现

1、开发步骤:
  1. 创建Job,被执行的内容。必须有一个实现了Job接口的类作为参数,实现该接口就是为了后面调用其实现的execute()方法。和线程的run方法类似(恰巧,线程实现Runnable接口,也叫任务task);
  2. 创建trigger。时间触发了事情的执行;
  3. 创建sheduler。一定要有谁来安排这么一个事情的执行;
  4. 组装上面的三个核心组件,运行代码。
2、代码示例

导入依赖

<!-- 定时任务 -->
<dependency>
     <groupId>org.quartz-scheduler</groupId>
     <artifactId>quartz</artifactId>
     <version>2.3.0</version>
</dependency>

创建job

package com.cg.job;

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

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

/**
 * 定时执行任务的任务类-测试
 * @author wanghao
 *
 */
public class TestJob01 implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // 业务逻辑
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        System.out.println("任务打印"+sdf.format(new Date()));
    }
}

创建任务调度器(以simpleTrigger方式触发)

package com.cg.job;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

/**
 * 任务调度类
 * 三个元素:任务类,触发器,调度器
 */
public class TestJob01Scheduler {

    //创建调度器
    public static Scheduler getScheduler() throws SchedulerException {
        //通过工厂类获取实例
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        return schedulerFactory.getScheduler();
    }

    // 执行任务
    public static void run() throws SchedulerException{
        /*
         *  创建任务
         *  包装业务代码为一个可执行的job
         *  反射机制
         * */
        JobDetail jobDetail = JobBuilder.newJob(TestJob01.class).withIdentity("testJob01", "group11").build();
        /*
         * 创建触发器 每5秒钟执行一次(每五秒钟就触发一次)
         */
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group21")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                .build();
        Scheduler scheduler = getScheduler();
        //将任务及其触发器放入调度器
        scheduler.scheduleJob(jobDetail, trigger);
        //调度器开始调度任务
        // 启动
        if (!scheduler.isShutdown()) {
            scheduler.start();
        }
    }

    public static void main(String[] args) throws SchedulerException {
        TestJob01Scheduler testJob01Scheduler = new TestJob01Scheduler();
        testJob01Scheduler.run();
    }
}

创建任务调度器(以CronTrigger方式触发)

package com.cg.job;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzManager {

    private static SchedulerFactory gSchedulerFactory = new StdSchedulerFactory();
    private static String JOB_GROUP_NAME = "EXTJWEB_JOBGROUP_NAME";
    private static String TRIGGER_GROUP_NAME = "EXTJWEB_TRIGGERGROUP_NAME";

    /**
     * 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名
     * @param jobName 任务名
     * @param cls 任务
     * @param time 时间设置
     */

    public static void addJob(String jobName, Class cls, String time) {
        try {
            // org.quartz-scheduler
            Scheduler sched = gSchedulerFactory.getScheduler();
            // 任务名,任务组,任务执行类
            //创建任务
            JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, TRIGGER_GROUP_NAME).build();
            //可以传递参数
            jobDetail.getJobDataMap().put("param", "railsboy");
            // 触发器
            //每秒钟触发一次任务
            //秒 分 时 日 月 年 星期 *=每 /=间隔  */2==每两秒 ,不连贯的值  -连贯的范围
            // ?用在星期或者日期上,二者存在冲突,解决二者冲突  L用在星期或者日期上,表示最后一天
            // w离该日最近的一个工作日,不可跨月   lw当月最后一个工作日
            // #用在星期上,4#2 表示第二个周三
            CronTrigger trigger = TriggerBuilder.newTrigger().
                    withIdentity(jobName, TRIGGER_GROUP_NAME).
                    // 每三秒执行一次
                    withSchedule(CronScheduleBuilder.cronSchedule("*/3 * * * * ? *")).
                    build();
            // 触发器时间设定
            sched.scheduleJob(jobDetail, trigger);
            // 启动
            if (!sched.isShutdown()) {
                sched.start();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        QuartzManager.addJob("Test01",TestJob01.class,"");
    }
}

Cron 表达式

Quartz 任务调度框架

星号():可用在所有字段中,表示对应时间域的每一个时刻,例如, 在分钟字段时,表示“每分钟”;

问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;

减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12;

逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;

斜杠(/):x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y;

L:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号;如果L用在星期中,则表示星期六,等同于7。但是,如果L出现在星期字段里,而且在前面有一个数值X,则表示“这个月的最后X天”,例如,6L表示该月的最后星期五;

W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意关联的匹配日期不能够跨月,如你指定1W,如果1号是星期六,结果匹配的是3号星期一,而非上个月最后的那天。W字符串只能指定单一日期,而不能指定日期范围;

LW组合:在日期字段可以组合使用LW,它的意思是当月的最后一个工作日;

井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五,#3表示当前的第三个),而4#5表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;

C:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。

Cron表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。