Quartz 基本使用

Quartz 介绍

官网:http://www.quartz-scheduler.org/

Quartz 的意思是石英,像石英表一样精确。

Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone application to the largest e-commerce system. Quartz can be used to create simple or complex schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks are defined as standard Java components that may execute virtually anything you may program them to do. The Quartz Scheduler includes many enterprise-class features, such as support for JTA transactions and clustering.

Quatz 是一个特性丰富的,开源的任务调度库,它几乎可以嵌入所有的 Java 程序,从很小的独立应用程序到大型商业系统。Quartz 可以用来创建成百上千的简单的或者复杂的任务,这些任务可以用来执行任何程序可以做的事情。Quartz 拥有很多企业级的特性,包括支持 JTA 事务和集群。

Quartz 是一个老牌的任务调度系统,98 年构思,01 年发布到 sourceforge。现在更新比较慢,因为已经非常成熟了。

https://github.com/quartz-scheduler/quartz

Quartz 的目的就是让任务调度更加简单,开发人员只需要关注业务即可。他是用 Java 语言编写的(也有.NET 的版本)。Java 代码能做的任何事情,Quartz 都可以调度。

特点:

精确到毫秒级别的调度

可以独立运行,也可以集成到容器中

支持事务(JobStoreCMT )

支持集群

支持持久化

Quartz Java 编程

http://www.quartz-scheduler.org/documentation/quartz-2.3.0/

http://www.quartz-scheduler.org/documentation/quartz-2.3.0/quick-start.html

3.1 引入依赖

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

3.2 默认配置文件

org.quartz.core 包下,有一个默认的配置文件,quartz.properties。当我们没有定义一个同名的配置文件的时候,就会使用默认配置文件里面的配置。

org.quartz.scheduler.instanceName: DefaultQuartzScheduler 
 org.quartz.scheduler.rmi.export: false 
 org.quartz.scheduler.rmi.proxy: false 
 org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool 
 org.quartz.threadPool.threadCount: 10 
 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 
 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

3.3 创建 Job

实现唯一的方法 execute(),方法中的代码就是任务执行的内容。此处仅输出字符串。

public class MyJob implements Job { 
     public void execute(JobExecutionContext context) throws JobExecutionException { 
         System.out.println("假发在哪里买的");
     } 
 }

在测试类 main()方法中,把 Job 进一步包装成 JobDetail。

必须要指定 JobName 和 groupName,两个合起来是唯一标识符。

可以携带 KV 的数据(JobDataMap),用于扩展属性,在运行的时候可以从 context 获取到

JobDetail jobDetail = JobBuilder.newJob(MyJob1.class) 
     .withIdentity("job1", "group1") 
     .usingJobData("msb","我爱涛哥") 
     .usingJobData("moon",5.21F) 
     .build();

3.4 创建 Trigger

在测试类 main()方法中,基于 SimpleTrigger 定义了一个每 2 秒钟运行一次、不断重复的 Trigger:

Trigger trigger = TriggerBuilder.newTrigger() 
     .withIdentity("trigger1", "group1") 
     .startNow() 
     .withSchedule(SimpleScheduleBuilder.simpleSchedule() 
         .withIntervalInSeconds(2) 
         .repeatForever()) 
     .build();

3.5 创建 Scheduler

在测试类 main()方法中,通过 Factory 获取调度器的实例,把 JobDetail 和 Trigger绑定,注册到容器中。

Scheduler 先启动后启动无所谓,只要有 Trigger 到达触发条件,就会执行任务。

SchedulerFactory factory = new StdSchedulerFactory(); 
 Scheduler scheduler = factory.getScheduler(); 
 scheduler.scheduleJob(jobDetail, trigger); 
 scheduler.start();

注意这里,调度器一定是单例的。

3.6 体系结构总结

3.6.1 JobDetail

我们创建一个实现 Job 接口的类,使用 JobBuilder 包装成 JobDetail,它可以携带 KV 的数据。

3.6.2 Trigger

定义任务的触发规律,Trigger,使用 TriggerBuilder 来构建。 JobDetail 跟 Trigger 是 1:N 的关系。 思考:为什么要解耦? Trigger 接口在 Quartz 有 4 个继承的子接口:

子接口

描述

特点

SimpleTrigger

简单触发器

固定时刻或时间间隔,毫秒

CalendarIntervalTrigger

基于日历的触发器

比简单触发器更多时间单位,支持非固定时间的触发,例如一年可能 365/366,一个月可能 28/29/30/31

DailyTimeIntervalTrigger

基于日期的触发器

每天的某个时间段

CronTrigger

基于 Cron 表达式的触发器

MutableTrigger 和 CoreTrigger 最终也是用到以上四个类的实现类

代码:standalone com.msb.trigger.TriggerDefine

SimpleTrigger

SimpleTrigger 可以定义固定时刻或者固定时间间隔的调度规则(精确到毫秒)。 例如:每天 9 点钟运行;每隔 30 分钟运行一次。

CalendarIntervalTrigger

CalendarIntervalTrigger 可以定义更多时间单位的调度需求,精确到秒。 好处是不需要去计算时间间隔,比如 1 个小时等于多少毫秒。 例如每年、每个月、每周、每天、每小时、每分钟、每秒。 每年的月数和每个月的天数不是固定的,这种情况也适用。

DailyTimeIntervalTrigger

每天的某个时间段内,以一定的时间间隔执行任务。 例如:每天早上 9 点到晚上 9 点,每隔半个小时执行一次,并且只在周一到周六执行。

CronTrigger

CronTirgger 可以定义基于 Cron 表达式的调度规则,是最常用的触发器类型。

Cron 表达式

位置

时间域

特殊值

1

0-59

, - * /

2

分钟

0-59

, - * /

3

小时

0-23

, - * /

4

日期

1-31

, - * ? / L W C

5

月份

1-12

, - * /

6

星期

1-7

, - * ? / L W C

7

年份(可选)

1-31

, - * /

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

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

减号(-):表达一个范围,如在小时字段中使用“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 表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。

上面我们定义的都是在什么时间执行,但是我们有一些在什么时间不执行的需求,比如:理财周末和法定假日购买不计息;证券公司周末和法定假日休市。

是不是要把日期写在数据库中,然后读取基于当前时间判断呢?

基于Calendar的排除规则

如果要在触发器的基础上,排除一些时间区间不执行任务,就要用到Quartz的Calendar类(注意不是JDK的Calendar)。可以按年、月、周、日、特定日期、Cron表达式排除。

调用Trigger的modifiedByCalendar()添加到触发器中,并且调用调度器的addCalendar()方法注册排除规则。

代码示例:standalone工程:com.msb.calendar.CalendarDemo

Calendar名称

用法

BaseCalendar

为高级的 Calendar 实现了基本的功能,实现了 org.quartz.Calendar 接口

AnnualCalendar

排除年中一天或多天

CronCalendar

日历的这种实现排除了由给定的CronExpression表达的时间集合。 例如,您可以使用此日历使用表达式“* * 0-7,18-23?* *”每天排除所有营业时间(上午8点至下午5点)。 如果CronTrigger具有给定的cron表达式并且与具有相同表达式的CronCalendar相关联,则日历将排除触发器包含的所有时间,并且它们将彼此抵消。

DailyCalendar

您可以使用此日历来排除营业时间(上午8点 - 5点)每天。 每个DailyCalendar仅允许指定单个时间范围,并且该时间范围可能不会跨越每日边界(即,您不能指定从上午8点至凌晨5点的时间范围)。 如果属性invertTimeRange为false(默认),则时间范围定义触发器不允许触发的时间范围。 如果invertTimeRange为true,则时间范围被反转 - 也就是排除在定义的时间范围之外的所有时间。

HolidayCalendar

特别的用于从 Trigger 中排除节假日

MonthlyCalendar

排除月份中的指定数天,例如,可用于排除每月的最后一天

WeeklyCalendar

排除星期中的任意周几,例如,可用于排除周末,默认周六和周日

Scheduler

调度器,是Quartz的指挥官,由StdSchedulerFactory产生。它是单例的。

并且是Quartz中最重要的API,默认是实现类是StdScheduler,里面包含了一个QuartzScheduler。QuartzScheduler里面又包含了一个QuartzSchedulerThread。

Scheduler中的方法主要分为三大类:

1)操作调度器本身,例如调度器的启动start()、调度器的关闭shutdown()。

2)操作Trigger,例如pauseTriggers()、resumeTrigger()。

3)操作Job,例如scheduleJob()、unscheduleJob()、rescheduleJob()

这些方法非常重要,可以实现任务的动态调度。

Scheduler中的方法主要分为三大类:

1)操作调度器本身,例如调度器的启动start()、调度器的关闭shutdown()。

2)操作Trigger,例如pauseTriggers()、resumeTrigger()。

3)操作Job,例如scheduleJob()、unscheduleJob()、rescheduleJob()

这些方法非常重要,可以实现任务的动态调度。

Listener

我们有这么一种需求,在每个任务运行结束之后发送通知给运维管理员。那是不是要在每个任务的最后添加一行代码呢?这种方式对原来的代码造成了入侵,不利于维护。如果代码不是写在任务代码的最后一行,怎么知道任务执行完了呢?或者说,怎么监测到任务的生命周期呢?

观察者模式:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖它的对象都会得到通知并自动更新。

Quartz中提供了三种Listener,监听Scheduler的,监听Trigger的,监听Job的。

只需要创建类实现相应的接口,并在Scheduler上注册Listener,便可实现对核心对象的监听。

standalone工程:com.msb.listener

MyJobListenerTest

MySchedulerListenerTest

MyTriggerListenerTest

JobListener

四个方法:

方法

作用或**执行实际**

getName()

返回JobListener 的名称

jobToBeExecuted()

Scheduler 在 JobDetail 将要被执行时调用这个方法

jobExecutionVetoed()

Scheduler 在 JobDetail 即将被执行,但又被 TriggerListener 否决了时调用这个方法

jobWasExecuted()

Scheduler 在 JobDetail 被执行之后调用这个方法

工具类:ListenerManager,用于添加、获取、移除监听器

工具类:Matcher,主要是基于groupName和keyName进行匹配。

TriggerListener

方法

作用或执行实际

getName()

返回监听器的名称

triggerFired()

Trigger 被触发,Job 上的 execute() 方法将要被执行时,Scheduler 就调用这个方法

vetoJobExecution()

在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方法。TriggerListener 给了一个选择去否决 Job 的执行。假如这个方法返回 true,这个 Job 将不会为此次 Trigger 触发而得到执行

triggerMisfired()

Trigger 错过触发时调用

triggerComplete()

Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方法

SchedulerListener

方法比较多,省略。

JobStore

问题:最多可以运行多少个任务(磁盘、内存、线程数)

Jobstore用来存储任务和触发器相关的信息,例如所有任务的名称、数量、状态等等。Quartz中有两种存储任务的方式,一种在在内存,一种是在数据库。

RAMJobStore

Quartz默认的JobStore是RAMJobstore,也就是把任务和触发器信息运行的信息存储在内存中,用到了HashMap、TreeSet、HashSet等等数据结构。

如果程序崩溃或重启,所有存储在内存中的数据都会丢失。所以我们需要把这些数据持久化到磁盘。

JDBCJobStore

JDBCJobStore可以通过JDBC接口,将任务运行数据保存在数据库中。

JDBC的实现方式有两种,JobStoreSupport类的两个子类:

JobStoreTX:在独立的程序中使用,自己管理事务,不参与外部事务。

JobStoreCMT:(Container Managed Transactions (CMT),如果需要容器管理事务时,使用它。

使用JDBCJobSotre时,需要配置数据库信息:

org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX
 org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate
 \# 使用quartz.properties,不使用默认配置
 org.quartz.jobStore.useProperties:true
 \#数据库中quartz表的表名前缀
 org.quartz.jobStore.tablePrefix:QRTZ_
 org.quartz.jobStore.dataSource:myDS
\#配置数据源
 org.quartz.dataSource.myDS.driver:com.mysql.jdbc.Driver
 org.quartz.dataSource.myDS.URL:jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8
 org.quartz.dataSource.myDS.user:root
 org.quartz.dataSource.myDS.password:123456
 org.quartz.dataSource.myDS.validationQuery=select 0 from dual

问题来了?需要建什么表?表里面有什么字段?字段类型和长度是什么?

在官网的Downloads链接中,提供了11张表的建表语句:

quartz-2.2.3-distribution\quartz-2.2.3\docs\dbTables

2.3的版本在这个路径下:src\org\quartz\impl\jdbcjobstore

表名与作用:

表名

作用

QRTZ_BLOB_TRIGGERS

Trigger作为Blob类型存储

QRTZ_CALENDARS

存储Quartz的Calendar信息

QRTZ_CRON_TRIGGERS

存储CronTrigger,包括Cron表达式和时区信息

QRTZ_FIRED_TRIGGERS

存储与已触发的Trigger相关的状态信息,以及相关Job的执行信息

QRTZ_JOB_DETAILS

存储每一个已配置的Job的详细信息

QRTZ_LOCKS

存储程序的悲观锁的信息

QRTZ_PAUSED_TRIGGER_GRPS

存储已暂停的Trigger组的信息

QRTZ_SCHEDULER_STATE

存储少量的有关Scheduler的状态信息,和别的Scheduler实例

QRTZ_SIMPLE_TRIGGERS

存储SimpleTrigger的信息,包括重复次数、间隔、以及已触的次数

QRTZ_SIMPROP_TRIGGERS

存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器

QRTZ_TRIGGERS

存储已配置的Trigger的信息

Quartz是一个强大的作业调度框架,可以用来实现定时任务。下面是Quartz基本使用方法: 1. 引入Quartz依赖 在项目的pom.xml文件中添加Quartz的依赖: ``` <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> </dependency> ``` 2. 配置Scheduler 在应用程序中创建一个Scheduler实例,用于调度作业。可以使用StdSchedulerFactory类创建一个Scheduler实例: ``` Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); ``` 3. 创建Job 创建一个实现了Job接口的类,并实现execute方法,该方法中实现需要执行的业务逻辑。 ``` public class MyJob implements Job { public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 业务逻辑 } } ``` 4. 创建JobDetail 创建一个JobDetail实例,用于描述Job的实现类及其它相关属性。 ``` JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build(); ``` 其中,withIdentity方法用于指定Job的名称和所属的组。 5. 创建Trigger 创建一个Trigger实例,用于描述Job的触发条件。 ``` Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever()).build(); ``` 其中,withIdentity方法用于指定Trigger的名称和所属的组,startNow方法用于指定Trigger的启动时间,withSchedule方法用于指定Trigger的调度策略,这里使用SimpleScheduleBuilder实现一个简单的定时调度策略,每隔10秒执行一次。 6. 调度Job 将JobDetail和Trigger传递给Scheduler的scheduleJob方法,用于调度Job。 ``` scheduler.scheduleJob(jobDetail, trigger); ``` 7. 启动Scheduler 使用Scheduler的start方法启动Scheduler。 ``` scheduler.start(); ``` 通过以上步骤,就可以使用Quartz实现简单的定时任务调度了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

埃泽漫笔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值