在上篇文章中,我们深入的介绍了单机版本定时任务的实现原理,今天我们继续来点干活,介绍在平时使用最多的一个定时任务框架 Quartz!
一、摘要
阅读完本文大概需要5分钟,本文主要分享内容如下:
- Quartz 架构介绍
- SpringBoot Quartz 应用整合
二、关于 Quartz
Quartz 是 OpenSymphony 开源组织在 Job scheduling 领域开源的一个作业调度框架项目,完全由 Java 编写,主要是为了实现在 Java 应用程序中进行作业调度并提供了简单却强大的机制!
Quartz 不仅可以单独使用,还可以与 J2EE 与 J2SE 应用程序相结合使用!
同时,Quartz 允许程序开发人员根据时间的间隔来调度作业!
与 JDK 中调度器不同的是,Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联,一次可以调度几十个、上百个甚至上几万个复杂的程序!
三、Quartz 架构图
在详细介绍 Quartz 应用之前,我们先来看看它具体的架构图!
从图中可以看出,Quartz 框架主要包括如下几个部分:
- SchedulerFactory:任务调度工厂,主要负责管理任务调度器
- Scheduler:任务调度控制器,主要是负责任务调度
- Job:任务接口,即被调度的任务
- JobDetail:Job 的任务描述类,job 执行时会依据此对象的信息反射实例化出 Job 的具体执行对象。
- Trigger:任务触发器,主要存放 Job 执行的时间策略。例如多久执行一次,什么时候执行,以什么频率执行等等
- Calendar:Trigger 扩展对象,可以排除或者包含某个指定的时间点(如排除法定节假日)
- JobStore:存储作业和任务调度期间的状态
可能感觉很抽象,下面我们先来看一个简单的例子,示例代码如下:
1 |
|
运行结果如下:
1 |
|
整个代码虽然简单,但是五脏俱全,在应用方面使用最多的就是Job
和Trigger
。
下面,我们就一起从源码层面来看看具体怎么使用!
3.1、Job
打开Job
源码,里面其实就是一个包含执行方法void execute(JobExecutionContext context)
的接口,开发者只需实现接口来定义具体任务即可!
1 |
|
JobExecutionContext
类封装了获取上下文的各种信息,Job
运行时的信息也保存在 JobDataMap
实例中!
例如,我想要获取在上文初始化时使用到的usingJobData("jobData", "test")
参数,可以通过如下方式进行获取!
1 |
|
输出结果:
1 |
|
3.2、Trigger
Trigger
主要用于描述Job
执行的时间触发规则,最常用的有SimpleTrigger
和CronTrigger
两个实现类型。
- SimpleTrigger:主要处理一些简单的调度规则,例如触发一次或者以固定时间间隔周期执行
- CronTrigger:调度处理更加灵活,可以通过
Cron
表达式定义出各种复杂时间规则的调度方案,例如每早晨9:00执行,周一、周三、周五下午5:00执行等;
3.2.1、SimpleTrigger
用SimpleTrigger
实现每2秒钟执行一次任务为例,代码如下:
1 |
|
运行结果:
1 |
|
其中最关键的就是withSchedule()
这个方法,通过SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()
来构建了一个简单的SimpleTrigger
类型的任务调度规则,从而实现任务调度!
3.2.2、CronTrigger
如开始介绍的例子一样,里面使用正是CronTrigger
类型的调度规则!
1 |
|
运行结果:
1 |
|
CronTrigger
相比SimpleTrigger
,在配置调度规则方面,使用cron
表达式更加灵活!
3.2.3、Cron 表达式详解
Quartz 的 Cron 表达式,具体配置规则可以参考如下:
1 |
|
具体样例如下:
在 cron 表达式中不区分大小写,更多配置和使用操作可以参考这里。
还可以在线解析cron
表达式进行测试。
3.3、监听器(选用)
quartz 除了提供能正常调度任务的功能之外,还提供了监听器功能!
所谓监听器,其实你可以把它理解为类似Spring Aop
的功能,可以对全局或者局部实现监听!
监听器应用,在实际项目中并不常用,但是在某些业务场景下,可以发挥一定的作用,例如:你想在任务处理完成之后,去发送邮件或者发短信进行通知,但是你又不想改以前的代码,这个时候就可以在监听器里面完成改项任务!
quartz 监听器主要分三大类:
- SchedulerListener:任务调度监听器
- TriggerListener:任务触发监听器
- JobListener:任务执行监听器
3.3.1、SchedulerListener
SchedulerListener
监听器,主要对任务调度Scheduler
生命周期中关键节点进行监听,它只能全局进行监听,简单示例如下:
1 |
|
需要在任务调度器启动前,将SimpleSchedulerListener
注册到Scheduler
容器中!
1 |
|
运行main
方法,输出结果如下:
1 |
|
3.3.2、TriggerListener
TriggerListener
,与触发器Trigger
相关的事件都会被监听,它既可以全局监听,也可以实现局部监听。
所谓局部监听,就是对某个Trigger
的名称或者组进行监听,简单示例如下:
1 |
|
与之类似,需要将SimpleTriggerListener
注册到Scheduler
容器中!
1 |
|
3.3.3、JobListener
JobListener
,与任务执行Job
相关的事件都会被监听,和Trigger
一样,既可以全局监听,也可以实现局部监听。
简单示例如下:
1 |
|
同样的,将SimpleJobListener
注册到Scheduler
容器中,即可实现监听!
1 |
|
3.3.4、小结
如果想多个同时监听,将其依次加入即可!
1 |
|
四、单体应用介绍
下面我们以每5秒执行一次任务为例,采用SpringBoot + Quartz
进行技术实现。流程如下!
4.1、引入 quartz 包
1 |
|
4.2、编写具体任务
1 |
|
4.3、编写任务调度配置服务
与上面的参数基本一致
1 |
|
4.4、任务测试
启动应用程序,即可观察到对应的任务运行状况!
如果需要创建多个定时任务,配置流程也类似!
只是这种静态配置方式多了会带来一个问题,比如,现在这个任务是每5秒跑一次,我现在想改成1个小时或者凌晨2点跑一次,这个时候就必须要改代码呢,但是我又不想改代码,基于此需求,利用数据库存储,我们可以将其改成动态配置!
4.5、动态配置定时任务
从上面的代码中我们可以分析中,定时任务的有三个核心变量,其他的方法都可以封装成公共的。
- 任务名称:例如
myJob
- 任务执行类:例如
TestTask.class
- 任务调度时间:例如
0/5 * * * * ?
基于此规律,我们可以创建一个定时任务实体类,用于保存定时任务相关信息到数据库当中,然后编写一个定时任务工具库,用于创建、更新、删除、暂停任务操作,通过 restful 接口操作将任务存入数据库并管理定时任务!
4.5.1、引入 Jpa 包
1 |
|
在application.properties
中加入数据源配置
1 |
|
4.5.2、创建任务配置表
1 |
|
4.5.3、编写任务实体类
1 |
|
4.5.4、编写任务操作工具类
1 |
|
4.5.5、编写 Jpa 数据操作服务
1 |
|
4.5.6、编写controller服务
1 |
|
4.5.7、服务重启补偿
在应用程序正常运行的时候,虽然没问题,但是当我们重启服务的时候,这个时候内存的里面的定时任务其实全部都被销毁,因此在应用程序启动的时候,还需要将正常的任务重新加入到服务中!
1 |
|
在服务重启的时候,会重新将有效任务加入quartz 中!
4.5.8、接口服务测试
- 调用
quartz/createJob
接口,创建任务
1 |
|
- 调用
quartz/pauseJob
接口,暂停任务
1 |
|
- 调用
quartz/runOnce
接口,立即运行一次定时任务
1 |
|
- 调用
quartz/resume
接口,恢复定时任务
1 |
|
- 调用
quartz/update
接口,更新定时任务
1 |
|
- 调用
quartz/delete
接口,删除定时任务
1 |
|
4.5.9、添加监听器(选用)
当然,如果你想对某个任务实现监听,只需要添加一个配置类,将其注入即可!
1 |
|
4.5.10、小结
需要注意的是:在 quartz 任务暂停之后再次启动时,会立即执行一次,在更新之后也会立即执行一次任务调度!
五、总结
本文主要围绕 quartz 的架构以及Springboot + quartz
项目整合和应用做了初步的介绍。
可能有的朋友会发问,quartz 已经有对应的任务表,不需要手动创建,只需要配置quartz.properties
文件即可!
没错,quartz
官方提供了对应任务表,主要用于分布式架构下的任务处理!
如果只是单体应用,可以参考本文中创建一张单表来存储任务,实现简单!
如果集群环境部署,在下篇文章中,我们会详细的介绍quartz
分布式架构的开发和应用!
鉴于笔者才疏学浅,如果发现有错误的地方,欢迎网友批评指导!