Quartz定时基础

Posted ziyue7575

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Quartz定时基础相关的知识,希望对你有一定的参考价值。

TOC

1.Quartz简介

1.1 Quartz概要

1.1.1 定义

-  OpenSymphony提供的强大的开源任务调度框架
- 官网:http://www.quartz-scheduler.org/
- 纯Java实现,精细控制排程
  • 特点
    • 强大的调度功能
    • 灵活的应用方式
    • 分布式和集群能力
  • 主要用到的设计模式
    • Builder模式
    • Factory模式
    • 组件模式
    • 链式写法

1.1.2 存储方式

- RAMJobStore
- JDBCJobStore 

类型
优点
缺点

RAMJobStore
不要外部数据库,配置容易,运行速度快 因为调度程序信息是存储在被分配给JVM的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到JVM内存里面,所以可以存储多少个Job和Trigger将会受到限制
JDBCJobStore
支持集群,因为所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务 运行速度的快慢取决与连接数据库的快慢

1.1.3 表关系和解释:

- 表关系

技术图片

  • 解释

表名称 说明
qrtz_blob_triggers Trigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore 并不知道如何存储实例的时候)
qrtz_calendars 以Blob类型存储Quartz的Calendar日历信息, quartz可配置一个日历来指定一个时间范围
qrtz_cron_triggers 存储Cron Trigger,包括Cron表达式和时区信息。
qrtz_fired_triggers 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
qrtz_job_details 存储每一个已配置的Job的详细信息
qrtz_locks 存储程序的非观锁的信息(假如使用了悲观锁)
qrtz_paused_trigger_graps 存储已暂停的Trigger组的信息
qrtz_scheduler_state 存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)
qrtz_simple_triggers 存储简单的 Trigger,包括重复次数,间隔,以及已触的次数
qrtz_triggers 存储已配置的 Trigger的信息
qrzt_simprop_triggers

1.1.4 核心类和关系

  • 核心类
    • QuartzSchedulerThread:负责执行向QuartzScheduler注册的触发Trigger的工作的线程。
    • ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提供运行效率。
    • QuartzSchedulerResources:包含创建QuartzScheduler实例所需的所有资源(JobStore,ThreadPool等)。
    • SchedulerFactory :提供用于获取调度程序实例的客户端可用句柄的机制。
    • JobStore: 通过类实现的接口,这些类要为org.quartz.core.QuartzScheduler的使用提供一个org.quartz.Joborg.quartz.Trigger存储机制。作业和触发器的存储应该以其名称和组的组合为唯一性。
    • QuartzScheduler :这是Quartz的核心,它是org.quartz.Scheduler接口的间接实现,包含调度org.quartz.Jobs,注册org.quartz.JobListener实例等的方法。
    • Scheduler :这是Quartz Scheduler的主要接口,代表一个独立运行容器。调度程序维护JobDetails和触发器的注册表。 一旦注册,调度程序负责执行作业,当他们的相关联的触发器触发(当他们的预定时间到达时)。
    • Trigger :具有所有触发器通用属性的基本接口,描述了job执行的时间出发规则。(使用TriggerBuilder实例化实际触发器。 )
    • JobDetail :传递给定作业实例的详细信息属性。 JobDetails将使用JobBuilder创建/定义。
    • Job:要由表示要执行的“作业”的类实现的接口。只有一个方法 void execute(jobExecutionContext context)
         (jobExecutionContext 提供调度上下文各种信息,运行时数据保存在jobDataMap中)
         Job有个子接口StatefulJob ,代表有状态任务。
         有状态任务不可并发,前次任务没有执行完,后面任务处于阻塞等到。

1.1.5 关系

技术图片
 
一个job可以被多个Trigger 绑定,但是一个Trigger只能绑定一个job!
 

1.2 Quartz体系结构

三个核心嘅念

  • 调度器:schedule
  • 任务:JobDetail
  • 触发器:trigger
    • SimpleTrigger
    • CronTrigger

重要组成

  • Job
  • JobDetail
  • JobBuilder
  • JobStore
  • Trigger
  • TriggerBuilder
  • ThreadPool
  • Scheduler
    • Calendar
      • 一个Trigger可以和多个Calendar关联,以排除或包含某些时间点。
    • 监听器
      • JobListener,TriggerListener,SchedulerListenero

1.3开启多循环

  • 注意:jobDetail和trigger是一对一的,要想让一个任务启动多次,要设计不同的jobDetail和trigger
    比如:
每次传入不同的string即可开启开启多个循环
jobDetail:   .withIdentity("myJob"+string,"group")

trigger:   .withIdentity("myTrigger"+string, "group")

结果:

调用两次,分别传入111和nihao:
  开启了两个循环
当123前线程+DefaultQuartzScheduler_Worker-7自定义值111
当123前线程+DefaultQuartzScheduler_Worker-8自定义值nihao
当123前线程+DefaultQuartzScheduler_Worker-9自定义值111
当123前线程+DefaultQuartzScheduler_Worker-10自定义值nihao

2.Quartz详解

2.1 基础案例:每隔两秒打印helloWord:

  • pom
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency><!-- 该依赖必加,里面有sping对schedule的支持 -->
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
  • 运行的方法
public class HelloJob implements Job {//继承Job

    public void execute(JobExecutionContext context) throws JobExecutionException {//编写具体的业务逻辑
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Exec Time Is : " + sf.format(date));
        System.out.println("Hello World");
    }
}
  • schedule,调用任务
public class HelloScheduler {
    public static void main(String[] args) throws SchedulerException, InterruptedException {
        // 打印当前的时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Time Is : " + sf.format(date));
        // 1.创建一个JobDetail实例,将该实例与HelloJob Class绑定
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)//绑定方法
                .withIdentity("myJob", "group1").build();//创建唯一标示:myJob,这里的组可以不写
        // 1.创建一个trigger实例,定义job立即执行,每2s执行一次
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("myTrigger", "group1") //设置唯一标示和组
                //注意:虽然trigger和jobDetail组名一致,但是不是同一个类,所以不是同一个组
                //现在开始执行,执行频度
                .startNow().withSchedule(SimpleScheduleBuilder.simpleSchdule().withIntervalInSeconds(2).repeatForever().build();
        // 创建Scheduler实例
        SchedulerFactory sfact = new StdSchedulerFactory();
        Scheduler scheduler = sfact.getScheduler();
        scheduler.start();//开始执行
        System.out.println("scheduled time is :"
                + sf.format(date));//将jobDetail, trigger绑定在一起
scheduler.scheduleJob(jobDetail, trigger);
    }
}

技术图片

2.2 Job和JobDetail

  • job定义
    • 实现业务逻辑的任务接口;
    • job接口非常容易实现,只有一个execute方法,类似TimerTask的run方法,在里面编写业务逻辑;
  • job实例在Quartz中的生命周期:
    • 每次调度器执行job时,它在调用e×ecute方法前会创建一个新的job实例。
    • 当调用完成后,关联的job对象实例会被释放,释放的实例会被垃圾回收机制回收。

  • JobDetail
    • JobDetail为Job实例提供了许多设置属性,以及JobDataMap成员变量属性,它用来存储特定job实例的状态信息,调度器需要借助JobDetail对象来添加job实例。
    • 是用来绑定job并携带job需要用到的信息。
  • JobDetail重要属性:
    • name:(任务名称必须)
    • group(组,必须(有默认值DEFAULT))
    • jobClass(任务实现类,必须)
    • jobDataMap(传参)
  • 获取JobDetail信息
    • jobDetail.getKey().getName());//任务名称
    • jobDetail.getKey().getGroup());//组,默认是DEFAULT
    • jobDetail.getJobClass().getName()//任务class名,全类名

2.3 JobExecutionContext和JobDataMap

  • JobExecutionContext是什么
    • 当Scheduler调用一个job,就会将JobExecutionContext传递给b的execute()方法,
    • Job能通过JobExecutionContext对象访问到Quartz运行时候的环境以及job本身的明细数据。
  • JobDataMap是什么
    • 在进行任务调度时jobDataMap存储在JobExecutionContext中,非常方便获取。
    • JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时这些参数对象会传递给它。
    • JobDataMap实现了JDK的Map接口,并且添加了一些非常方便的方法用来存取基本数据类型

2.3.1获取JobDataMap:2种方式

1)从Map中直接获取--自定义参数

  • 使用JobDetail和trigger给任务添加自定义的参数
@RestController
public class HelloScheduler {
    @RequestMapping("/t1")
    public  void t1() throws SchedulerException, InterruptedException {
        // 1.创建一个JobDetail实例,将该实例与HelloJob Class绑定
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)//绑定方法
                .withIdentity("myJob")
                .usingJobData("message","hello myjob1")//传入自定义参数
                .usingJobData("FloatJobValue",3.14f).build();//创建唯一标示:myJob
        // 1.创建一个trigger实例,定义job立即执行,每2s执行一次
        Trigger trigger = TriggerBuilder
                .newTrigger()
                .withIdentity("myTrigger", "group1") //设置唯一标示和组
                .usingJobData("message","hello myTrigger1")
                .usingJobData("DoubleTriggerValue",2.0D)
                .startNow()
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
                .build();
        // 创建Scheduler实例
        SchedulerFactory sfact = new StdSchedulerFactory();
        Scheduler scheduler = sfact.getScheduler();
   //启动任务
        scheduler.start();
        // 打印当前的时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Time Is : " + sf.format(date));
        scheduler.scheduleJob(jobDetail, trigger);
    }

}
a.使用getJobDetail().getJobDataMap()调用任务,并在任务中使用自定义参数
public class HelloJob implements Job {//继承Job  
    public void execute(JobExecutionContext context) throws JobExecutionException {//编写具体的业务逻辑
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Exec Time Is : " + sf.format(date));
        //System.out.println("Hello World");
        //打印JobDetail信息
        JobKey  key=context.getJobDetail().getKey();
        System.out.println("My job name and group are:"+key.getName()+":"+key.getGroup());
        //打印trigger信息
        TriggerKey triggerKey=context.getTrigger().getKey();
        System.out.println("My trigger name and group are:"+triggerKey.getName()+":"+triggerKey.getGroup());
        //打印自定义传入的数据
        JobDataMap dataMap=context.getJobDetail().getJobDataMap();
        JobDataMap tDataMap=context.getTrigger().getJobDataMap();
        String jobMsg=dataMap.getString("message");
        Float jobFloatValue=dataMap.getFloat("FloatJobValue");
        String triggerMsg=tDataMap.getString("message");

        Double triggerDoubleValue=tDataMap.getDouble("DoubleTriggerValue");
        System.out.println("JobMsg is"+jobMsg);
        System.out.println("jobFloatValue is"+jobFloatValue);
        System.out.println("triggerMsg is"+triggerMsg);
        System.out.println("triggerDoubleValue is"+triggerDoubleValue);
    }
}
  • 结果
Current Exec Time Is : 2018-07-03 19:09:46 //启动任务
My job name and group are:myJob:DEFAULT  //DEFAULT 是默认的组名
My trigger name and group are:myTrigger:group1
JobMsg ishello myjob1
jobFloatValue is3.14
triggerMsg ishello myTrigger1
triggerDoubleValue is2.0
Current Exec Time Is : 2018-07-03 19:09:48
My job name and group are:myJob:DEFAULT
My trigger name and group are:myTrigger:group1
JobMsg ishello myjob1
jobFloatValue is3.14

b.使用getMergedJobDataMap获取传入的自定义的任务参数
public class HelloJob implements Job {//继承Job  
    public void execute(JobExecutionContext context) throws JobExecutionException {//编写具体的业务逻辑
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Exec Time Is : " + sf.format(date));
        //System.out.println("Hello World");
        //打印JobDetail信息
        JobKey  key=context.getJobDetail().getKey();
        System.out.println("My job name and group are:"+key.getName()+":"+key.getGroup());
        //打印trigger信息
        TriggerKey triggerKey=context.getTrigger().getKey();
        System.out.println("My trigger name and group are:"+triggerKey.getName()+":"+triggerKey.getGroup());
        //打印自定义传入的数据
        //getMergedJobDataMap是JobDataMap,Trigger合并后 的
        JobDataMap dataMap=context.getMergedJobDataMap();//getTrigger().getJobDataMap();
        //JobDataMap,Trigger中message是想相同的,会显示Trigger中的
        String msg=dataMap.getString("message");
        Float jobFloatValue=dataMap.getFloat("FloatJobValue");
        Double triggerDoubleValue=dataMap.getDouble("DoubleTriggerValue");
        System.out.println("JobMsg is"+msg);
        System.out.println("jobFloatValue is"+jobFloatValue);
        System.out.println("triggerDoubleValue is"+triggerDoubleValue);
    }
}
  • 结果
Current Exec Time Is : 2018-07-03 19:23:26
My job name and group are:myJob:DEFAULT
My trigger name and group are:myTrigger:group1
JobMsg ishello myTrigger1  //显示的是Trigger的
jobFloatValue is3.14  //打印的是对应的
triggerDoubleValue is2.0

2)job实现类中添加setter方法对应jobDataMap的键值

  • Quartz框架默认的jobFactory实现类在初始化job实例对象时会自动地调用这些setter方法
  • 实质:创建与key对应的参数,生成getset方法即可获取。
  • 使用第二种方法从任务中获取参数
//方法2,直接传入参数
public class HelloJob implements Job {//继承Job  
    private String  message;//要与传入的数据的key对应
    private Float  FloatJobValue;
    private Double  DoubleTriggerValue;
    //生成getset方法
    public void execute(JobExecutionContext context) throws JobExecutionException {//编写具体的业务逻辑
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("Current Exec Time Is : " + sf.format(date));
        //System.out.println("Hello World");
        //打印JobDetail信息
        JobKey  key=context.getJobDetail().getKey();
        System.out.println("My job name and group are:"+key.getName()+":"+key.getGroup());
        //打印trigger信息
        TriggerKey triggerKey=context.getTrigger().getKey();
        System.out.println("My trigger name and group are:"+triggerKey.getName()+":"+triggerKey.getGroup());
        //打印自定义传入的数据
        //JobDataMap,Trigger中message是想相同的,会显示Trigger中的
        System.out.println("JobMsg is"+message);
        System.out.println("jobFloatValue is"+FloatJobValue);
        System.out.println("triggerDoubleValue is"+DoubleTriggerValue);
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public Float getFloatJobValue() {
        return FloatJobValue;
    }
    public void setFloatJobValue(Float floatJobValue) {
        FloatJobValue = floatJobValue;
    }
    public Double getDoubleTriggerValue() {
        return DoubleTriggerValue;
    }
    public void setDoubleTriggerValue(Double doubleTriggerValue) {
        DoubleTriggerValue = doubleTriggerValue;
    }
}
  • 结果
Current Exec Time Is : 2018-07-03 19:33:25
My job name and group are:myJob:DEFAULT
My trigger name and group are:myTrigger:group1
JobMsg ishello myTrigger1
jobFloatValue is3.14
triggerDoubleValue is2.0

2.4 Trigger

  • Trigger概述:
      Quartz中的触发器用来告诉调度程序作业什么时候触发。即Trigger对象是用来触发执行job的。
    Quartz框架中的Trigger:
    技术图片

Trigger通用属性

  • JobKey:表示job实例的标识,触发器被触发时,该指定Job实例会执行。
  • StartTime:表示触发器的时间表首次被触发的时间。它的值的类型是Java.util.Date.
  • EndTime:指定触发器的不再被触发的时间。它的值的类型是Java.util.Date.

案例:设置开始结束时间

  • 任务
public class HelloJob implements Job {//继承Job  
    private String  message;//要与传入的数据的key对应
    private Float  FloatJobValue;
    private Double  DoubleTriggerValue;
    //生成getset方法
    public void execute(JobExecutionContext context) throws JobExecutionException {//编写具体的业务逻辑
        // 打印当前的执行时间,格式为2017-01-01 00:00:00
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("任务HelloJob开始时间 : " + sf.format(date));
        //获取定义的开始时间,和结束时间
        Trigger cunrrentTrigger= context.getTrigger();
        System.out.println("开始时间:"+sf.format(cunrrentTrigger.getStartTime()));
        System.out.println("结束时间:"+sf.format(cunrrentTrigger.getEndTime()));
        JobKey  key=cunrrentTrigger.getJobKey();
        System.out.println("JobKey信息:name:"+key.getName()+" group:"+key.getGroup());
    }
  • 调用任务
@RestController
public class HelloScheduler {
    @RequestMapping("/t1")
    public  void t1() throws SchedulerException, InterruptedException {
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("controller方法 t1开始时间: " + sf.format(date));
        // 1.创建一个JobDetail实例,将该实例与HelloJob Class绑定
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)//绑定方法
                .withIdentity("myJob").build();//创建唯一标示:myJob
        // 1.创建一个trigger实例,
        //获取距离当前时间date,3秒后的时间
        date.setTime(date.getTime()+3000);//3秒后的时间
        Date enDate=new Date();
        enDate.setTime(enDate.getTime()+6000);//6秒后的时间
        Trigger trigger = TriggerBuilder
                .newTrigger()
                .withIdentity("myTrigger", "group1") //设置唯一标示和组
                .startAt(date)//设置开始时间:3秒后的时间
                .endAt(enDate)//设置结束时间,6s后停止
                //每2s执行一次的话,会在第3s执行第一次,第5s执行第二次
                .withSchedule(
                        //设置任务每2s执行一次
                        SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
                .build();
        // 创建Scheduler实例
        SchedulerFactory sfact = new StdSchedulerFactory();
        Scheduler scheduler = sfact.getScheduler();
        scheduler.scheduleJob(jobDetail, trigger);
        // 打印当前的时间,格式为2017-01-01 00:00:00
        scheduler.start();

    }
}
  • 结果
-----任务只执行了两次,58-013s,在58和00执行的。
controller方法 t1开始时间: 2018-07-04 09:21:55  //开始时间
2018-07-04 09:21:55.852  INFO 9320 --- [nio-8087-exec-3] org.quartz.core.QuartzScheduler          : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
任务HelloJob开始时间 : 2018-07-04 09:21:58 //3s后第一次执行
开始时间:2018-07-04 09:21:58  
结束时间:2018-07-04 09:22:01  //6s后结束
JobKey信息:name:myJob group:DEFAULT
任务HelloJob开始时间 : 2018-07-04 09:22:00
开始时间:2018-07-04 09:21:58
结束时间:2018-07-04 09:22:01
JobKey信息:name:myJob group:DEFAULT

2.6 SimpleTrigger

  • SimpleTrigger的作用
    在一个指定时间段执行一次作业任务或是在指定的时间间隔多次执行作业任务
  • 在一个指定时间段执行一次作业任务

    4秒后只执行一次

  • 注意
    • 重复次数可以为0,正整数或是SimpleTrigger.REPEAT_INDEFINITELY常量值.
    • 重复执行间隔必须为0或长整数.
    • 一旦被指定了endTime参数,那么它会覆盖重复次数参数的效果(若是还未到运行时间,任务就运行完了,会停止).
  • withRepeatCount是重复的次数,真正运行的次数是withRepeatCount+1(withRepeatCount==0的时候,会运行一次)
@RestController
public class HelloScheduler {
    @RequestMapping("/t1")
    public  void t1() throws SchedulerException, InterruptedException {
        Date date = new Date();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("controller方法 t1开始时间: " + sf.format(date));
        // 1.创建一个JobDetail实例,将该实例与HelloJob Class绑定
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)//绑定方法
                .withIdentity("myJob").build();//创建唯一标示:myJob
        // 1.创建一个trigger实例,
        //获取距离当前时间4秒后的时间,只执行一次
        date.setTime(date.getTime()+4000l);
        SimpleTrigger trigger = (SimpleTrigger)TriggerBuilder
                .newTrigger()
                .withIdentity("myTrigger", "group1") //设置唯一标示id和组
                .startAt(date)
                .build();
        // 创建Scheduler实例
        SchedulerFactory sfact = new StdSchedulerFactory();
        Scheduler scheduler = sfact.getScheduler();
        scheduler.scheduleJob(jobDetail, trigger);
        // 打印当前的时间,格式为2017-01-01 00:00:00
        scheduler.start();
    }
}

技术图片

  • 指定的时间间隔多次执行**作业任务

    4s后,开始执行,之后每隔2s中执行一次

        date.setTime(date.getTime()+4000l);
        SimpleTrigger trigger = (SimpleTrigger)TriggerBuilder
                .newTrigger()
                .withIdentity("myTrigger", "group1") //设置唯一标示id和组
                .startAt(date)
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2)//每2s执行一次
                        .withRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY))//执行无限次
                .build();
  • 4s后开始执行,每2s执行一次,执行无限次,6s后停止
        //6s后停止执行
        Date enDate=new Date();
        enDate.setTime(enDate.getTime()+6000l);
        date.setTime(date.getTime()+4000l);
        SimpleTrigger trigger = (SimpleTrigger)TriggerBuilder
                .newTrigger()
                .withIdentity("myTrigger", "group1") //设置唯一标示id和组
                .startAt(date)
                .endAt(enDate)//6s后停止执行
                .withSchedule(
                        SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2)//每2s执行一次
                        .withRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY))//执行无限次
                .build();

2.7 CronTrigger

  • CronTrigger作用
    • 基于日历作业调度器,而不是像SimpleTrigger那样精确指定间隔时间,比SimpleTrigge更常用。
  • Cron表达式
    • 用于配置CronTrigger实例。
    • 是由7个子表达式组成的字符串,描述了时间表的详细信息。
    • 格式:【秒】【分】【小时】【日】】【月】【周】【年】

2.7.1案例:每s执行一次

CronTrigger trigger = (CronTrigger)TriggerBuilder
    .newTrigger()
    .withIdentity("myTrigger", "group1") //设置唯一标示id和组
    .withSchedule(
        CronScheduleBuilder.cronSchedule("* * * * * ? *"))        
    .build();

2.7.2 Cron表达式

  • 特殊字符意义对应表
    技术图片
  • Cron表达式举例
    技术图片

    • 每天的14点整至14点59分55秒,以及18点整至18点59分55秒。每5秒钟触发1次:0/5 * 14,18 * * ?

      由于每5s执行一次,所以到55的时候执行最后一次,下一次是下一分的开始或结束此次运行

  • 通配符说明
    技术图片
  • Cron表达式小提示
    • ‘L‘和‘W‘可以组合使用。
    • 周字段英文字母不区分大小写即MON与mon相同
    • 利用工具,在线生成
        

 

2.8 Scheduler:工厂模式

  • 所有的Scheduler实例应该由SchedulerFactory来创建

技术图片

  • Scheduler的创建方式
SchedulerFactory sfact=new StdSchedulerFactory();
Scheduler scheduler=sfact.getScheduler();
DirectSchedulerFactory factory=DirectSchedulerFactory.getInstance();
Scheduler scheduler=factory.getScheduler();

2.8.1 StdSchedulerFactory

  1. 使用一组参数(Java.util.Properties)来创建和初始化Quartz调度器
  2. 配置参数一般存储在quartz.properties
  3. 调用getScheduler方法就能创建和初始化调度器对象

2.8.2 Scheduler的主要函数


1. Date scheduleJob(JobDetail jobDetail,Trigger trigger)  //是在项目中,启动任务,设置任务和时间的
2. void start()  //启动Scheduler
3. void standby()  //让Scheduler暂时挂起,暂停
4. void shutdown() //将任务关闭,不能用start重启,会报错
    shutdown(true) //表示等待所有正在执行的job执行完毕之后,再关闭scheduler,此时是等一次任务运行完成,才会运行shutdown代码及之后的代码

    shutdown(false)  //即shutdowno()表示直接关闭scheduler,直接运行shutdown代码,任务在另一个线程上继续,直到运行完这一次任务
5. scheduler.isShutdown();//判断scheduler是否关闭
  • 案例
// 创建Scheduler实例
SchedulerFactory sfact = new StdSchedulerFactory();
Scheduler scheduler = sfact.getScheduler();
System.out.println("返回值:"+sf.format(scheduler.scheduleJob(jobDetail, trigger)));
// 打印当前的时间,格式为2017-01-01 00:00:00
 scheduler.start();

//让Scheduler执行2s后自动挂起
Thread.sleep(2000L);//睡眠2s,此时任务的线程还在运行
scheduler.standby();//2s后,任务挂起

//让Scheduler挂起,3s后,启动
Thread.sleep(3000l);
scheduler.start();

结果:

返回值:2018-07-04 19:16:18  //最近一次任务将要运行的时间
任务HelloJob开始时间 : 2018-07-04 19:16:18

Hello World
任务HelloJob开始时间 : 2018-07-04 19:16:19
Hello World
任务HelloJob开始时间 : 2018-07-04 19:16:20 //2s后
Hello World
//任务挂起
//3s后,任务开始再次执行
任务HelloJob开始时间 : 2018-07-04 19:16:23

Hello World
任务HelloJob开始时间 : 2018-07-04 19:16:23
Hello World
任务HelloJob开始时间 : 2018-07-04 19:16:23
Hello World
任务HelloJob开始时间 : 2018-07-04 19:16:24
Hello World
任务HelloJob开始时间 : 2018-07-04 19:16:25
Hello World
任务HelloJob开始时间 : 2018-07-04 19:16:26
Hello World
任务HelloJob开始时间 : 2018-07-04 19:16:27
Hello World

//之后便是一直运行了

2.9 quartz.properties文件

  • 文档的位置和加载顺序:

    需要自己建立,默认在jar包中有
    放到resource中即可

  • 组成部分
    • 调度器属性:
      • org.quartz.scheduler.instanceName属性用来区分特定的调度器实例,可以按照功能用途来给调度器起名。
      • org.quartz.scheduler.instanceId属性和前者一样,也允许任何字符串,但这个值必须是在所有调度器实例中是唯一的,尤其是在一个集群当中,作为集群的唯一key.假如你想Quartz帮你生成这个值的话,可以设置为AUTO。
    • 线程池属性:
    • threadCount:设置工作线程数:(无默认值,必须设置,1-100最好)
    • threadPriority:设置工作优先级:(1-10,默认5,主要用在集群)
    • org.quartz.threadPool.class
    • 作业存储设置:
    • 描述了在调度器实例的生命周期中,Job和Trigger信息是如何被存储的。
    • 插件配置:
    • 满足特定需求用到的Quartz插件的配置。

2.9.1非持久化配置

# ===========================================================================
# Configure Main Scheduler Properties 调度器属性
# ===========================================================================
#调度标识名 集群中每一个实例都必须使用相同的名称 (区分特定的调度器实例) 
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
#ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的) 
org.quartz.scheduler.instanceid:AUTO #自增
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
# ===========================================================================  
# Configure ThreadPool 线程池属性  
# ===========================================================================
#线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
#指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)
org.quartz.threadPool.threadCount: 10
#设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority: 5
#设置SimpleThreadPool的一些属性
#设置是否为守护线程
#org.quartz.threadpool.makethreadsdaemons = false
#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
#org.quartz.threadpool.threadsinheritgroupofinitializingthread=false
#线程前缀默认值是:[Scheduler Name]_Worker
#org.quartz.threadpool.threadnameprefix=swhJobThead;
# 配置全局监听(TriggerListener,JobListener) 则应用程序可以接收和执行 预定的事件通知
# ===========================================================================
# Configuring a Global TriggerListener 配置全局的Trigger监听器
# MyTriggerListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)
# ===========================================================================
#org.quartz.triggerListener.NAME.class = com.swh.MyTriggerListenerClass
#org.quartz.triggerListener.NAME.propName = propValue
#org.quartz.triggerListener.NAME.prop2Name = prop2Value
# ===========================================================================
# Configuring a Global JobListener 配置全局的Job监听器
# MyJobListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)
# ===========================================================================
#org.quartz.jobListener.NAME.class = com.swh.MyJobListenerClass
#org.quartz.jobListener.NAME.propName = propValue
#org.quartz.jobListener.NAME.prop2Name = prop2Value
# ===========================================================================  
# Configure JobStore 存储调度信息(工作,触发器和日历等)
# ===========================================================================
# 信息保存时间 默认值60秒
org.quartz.jobStore.misfireThreshold: 60000
#保存job和Trigger的状态信息到内存中的类,没有持久化
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
# ===========================================================================  
# Configure SchedulerPlugins 插件属性 配置
# ===========================================================================
# 自定义插件  
#org.quartz.plugin.NAME.class = com.swh.MyPluginClass
#org.quartz.plugin.NAME.propName = propValue
#org.quartz.plugin.NAME.prop2Name = prop2Value
#配置trigger执行历史日志(可以看到类的文档和参数列表)
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin  
org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}  
org.quartz.plugin.triggHistory.triggerCompleteMessage = Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy} with resulting trigger instruction code: {9}  
#配置job调度插件 quartz_jobs(jobs and triggers内容)的XML文档  
#加载 Job 和 Trigger 信息的类 (1.8之前用:org.quartz.plugins.xml.JobInitializationPlugin)
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
#指定存放调度器(Job 和 Trigger)信息的xml文件,默认是classpath下quartz_jobs.xml
org.quartz.plugin.jobInitializer.fileNames = my_quartz_job2.xml  
#org.quartz.plugin.jobInitializer.overWriteExistingJobs = false  
org.quartz.plugin.jobInitializer.failOnFileNotFound = true  
#自动扫描任务单并发现改动的时间间隔,单位为秒
org.quartz.plugin.jobInitializer.scanInterval = 10
#覆盖任务调度器中同名的jobDetail,避免只修改了CronExpression所造成的不能重新生效情况
org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
# ===========================================================================  
# Sample configuration of ShutdownHookPlugin ShutdownHookPlugin插件的配置样例
# ===========================================================================
#org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin
#org.quartz.plugin.shutdownhook.cleanShutdown = true
#
# Configure RMI Settings 远程服务调用配置
#
#如果你想quartz-scheduler出口本身通过RMI作为服务器,然后设置“出口”标志true(默认值为false)。
#org.quartz.scheduler.rmi.export = false
#主机上rmi注册表(默认值localhost)
#org.quartz.scheduler.rmi.registryhost = localhost
#注册监听端口号(默认值1099)
#org.quartz.scheduler.rmi.registryport = 1099
#创建rmi注册,false/never:如果你已经有一个在运行或不想进行创建注册
# true/as_needed:第一次尝试使用现有的注册,然后再回来进行创建
# always:先进行创建一个注册,然后再使用回来使用注册
#org.quartz.scheduler.rmi.createregistry = never
#Quartz Scheduler服务端端口,默认是随机分配RMI注册表
#org.quartz.scheduler.rmi.serverport = 1098
#true:链接远程服务调度(客户端),这个也要指定registryhost和registryport,默认为false
# 如果export和proxy同时指定为true,则export的设置将被忽略
#org.quartz.scheduler.rmi.proxy = false

2.9.2持久化配置

//调度标识名 集群中每一个实例都必须使用相同的名称 (区分特定的调度器实例) 
org.quartz.scheduler.instanceName:DefaultQuartzScheduler 
//ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的) 
org.quartz.scheduler.instanceId :AUTO 
//数据保存方式为持久化 
org.quartz.jobStore.class :org.quartz.impl.jdbcjobstore.JobStoreTX 
//表的前缀 
org.quartz.jobStore.tablePrefix : QRTZ_ 
//设置为TRUE不会出现序列化非字符串类到 BLOB 时产生的类版本问题 
//org.quartz.jobStore.useProperties : true 
//加入集群 true 为集群 false不是集群 
org.quartz.jobStore.isClustered : false 
//调度实例失效的检查时间间隔 
org.quartz.jobStore.clusterCheckinInterval:20000 
//容许的最大作业延长时间 
org.quartz.jobStore.misfireThreshold :60000 
//ThreadPool 实现的类名 
org.quartz.threadPool.class:org.quartz.simpl.SimpleThreadPool 
//线程数量 
org.quartz.threadPool.threadCount : 10 
//线程优先级 
org.quartz.threadPool.threadPriority : 5(threadPriority 属性的最大值是常量 java.lang.Thread.MAX_PRIORITY,等于10。最小值为常量 java.lang.Thread.MIN_PRIORITY,为1) 
//自创建父线程 
//org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true 
//数据库别名 
org.quartz.jobStore.dataSource : qzDS 
//设置数据源 
org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver 
org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz 
org.quartz.dataSource.qzDS.user:root 
org.quartz.dataSource.qzDS.password:123456 
org.quartz.dataSource.qzDS.maxConnection:10

2.9.3 JDBC插入表顺序

主要的JDBC操作类,执行sql顺序。
Simple_trigger :插入顺序
qrtz_job_details —> qrtz_triggers —> qrtz_simple_triggers
qrtz_fired_triggers
Cron_Trigger:插入顺序
qrtz_job_details —> qrtz_triggers —> qrtz_cron_triggers
qrtz_fired_triggers

3.Spring、Quartz整合

  • pom
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency><!-- 该依赖必加,里面有sping对schedule的支持 -->
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
<!--spring的事务,不确定。。。。-->
        <dependency><!-- 该依赖必加,里面有sping对schedule的支持 -->
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>

3.1使用Quartz配置作业

  • 两种方式
    • MethodInvokingJobDetailFactoryBean
    • JobDetailFactoryBean

MethodInvokingJobDetailFactoryBean

调用myBean的printMessage方法
技术图片

MyBean类:

@Component("myBean")
public class MyBean {
 public void printMessage() {
  // 打印当前的执行时间,格式为2017-01-01 00:00:00
  Date date = new Date();
  SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  System.out.println("MyBean Executes!" + sf.format(date));
 }
}

JobDetailFactoryBean

需要给作业传递数据,想更加灵活的话就使用这种方式
技术图片

public class FirstScheduledJob extends QuartzJobBean{
     private AnotherBean anotherBean;

     public void setAnotherBean(AnotherBean anotherBean){
      this.anotherBean = anotherBean;
     }


 @Override
 protected void executeInternal(JobExecutionContext arg0)
   throws JobExecutionException {
  Date date = new Date();
  SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  System.out.println("FirstScheduledJob Executes!" + sf.format(date));
  this.anotherBean.printAnotherMessage();  
 }
}
@Component("anotherBean")
public class AnotherBean {
 public void printAnotherMessage() {
  System.out.println("AnotherMessage");
 }
}

配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="http://www.springframework.org/schema/beans  
            http://www.springframework.org/schema/beans/spring-beans.xsd  
            http://www.springframework.org/schema/mvc  
            http://www.springframework.org/schema/mvc/spring-mvc.xsd  
            http://www.springframework.org/schema/context  
            http://www.springframework.org/schema/context/spring-context.xsd"
 default-lazy-init="true">


 <!-- 通过mvc:resources设置静态资源,这样servlet就会处理这些静态资源,而不通过控制器 -->
 <!-- 设置不过滤内容,比如:css,jquery,img 等资源文件 -->
 <mvc:resources location="/*.html" mapping="/**.html" />
 <mvc:resources location="/css/*" mapping="/css/**" />
 <mvc:resources location="/js/*" mapping="/js/**" />
 <mvc:resources location="/images/*" mapping="/images/**" />
 <!-- 设定消息转换的编码为utf-8防止controller返回中文乱码 -->
 <bean
  class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
  <property name="messageConverters">
   <list>
    <bean
     class="org.springframework.http.converter.StringHttpMessageConverter">
     <property name="supportedMediaTypes">
      <list>
       <value>text/html;charset=UTF-8</value>
      </list>
     </property>
    </bean>
   </list>
  </property>
 </bean>
 <!-- 添加注解驱动 -->
 <mvc:annotation-driven />
 <!-- 默认扫描的包路径 -->
 <context:component-scan base-package="com.imooc.springquartz" />


 <!-- mvc:view-controller可以在不需要Controller处理request的情况,转向到设置的View -->
 <!-- 像下面这样设置,如果请求为/,则不通过controller,而直接解析为/index.jsp -->
 <mvc:view-controller path="/" view-name="index" />
 <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
  <property name="viewClass"
   value="org.springframework.web.servlet.view.JstlView"></property>
  <!-- 配置jsp路径前缀 -->
  <property name="prefix" value="/"></property>
  <!-- 配置URl后缀 -->
  <property name="suffix" value=".jsp"></property>
 </bean>
<!--配置类和方法-->
 <bean id="simpleJobDetail"
  class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
  <property name="targetObject" ref="myBean" />
  <property name="targetMethod" value="printMessage" />
 </bean>


 <bean id="firstComplexJobDetail"
  class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
  <property name="jobClass"
   value="com.imooc.springquartz.quartz.FirstScheduledJob" />
  <property name="jobDataMap">
   <map>
    <entry key="anotherBean" value-ref="anotherBean" />
   </map>
  </property>
  <property name="Durability" value="true"/>    
 </bean>
 <!-- 距离当前时间1秒之后执行,之后每隔两秒钟执行一次 -->
 <bean id="mySimpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
     <property name="jobDetail" ref="simpleJobDetail"/>
     <property name="startDelay" value="1000"/>
     <property name="repeatInterval" value="2000"/>
 </bean>
 <!-- 每隔5秒钟执行一次 -->
 <bean id="myCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
     <property name="jobDetail" ref="firstComplexJobDetail"/>
     <property name="cronExpression" value="0/5 * * ? * *"/>
 </bean>
 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
     <property name="jobDetails">
         <list>
             <ref bean="simpleJobDetail"/>
             <ref bean="firstComplexJobDetail"/>
         </list>
     </property>
     <property name="triggers">
         <list>
             <ref bean="mySimpleTrigger"/>
             <ref bean="myCronTrigger"/>
         </list>
     </property>
 </bean>
</beans>  

判断cron格式是否正确

CronExpression.isValidExpression(cron)

cron获取下次运行时间

 /**
  * 查看cron下次运行时间
  * @param cron
  */
 public Date getNextTime(String cron){
  //检查cron格式
  if (CronExpression.isValidExpression(cron)){//cron格式正确
   try {
    System.out.println(new CronExpression(cron).getNextValidTimeAfter(new Date()));
    return new CronExpression(cron).getNextValidTimeAfter(new Date());
   } catch (ParseException e) {
    e.printStackTrace();
    return null;
   }
  }else return null;
 }




附件列表

     

    以上是关于Quartz定时基础的主要内容,如果未能解决你的问题,请参考以下文章

    深入Quartz,更优雅地管理你的定时任务

    深入Quartz,更优雅地管理你的定时任务

    Quartz定时代码实现

    一行代码完成定时任务调度,基于Quartz的UI可视化操作组件 GZY.Quartz.MUI

    对quartz定时任务的初步认识

    SpringBoot之旅 -- 定时任务两种(Spring Schedule 与 Quartz 整合 )实现