Quartz任务调度
1. Quartz概念
- Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目
- Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
- Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
- Quartz 允许程序开发人员根据时间的间隔来调度作业。
- Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
2. 核心
-
Job 表示一个工作,要执行的具体内容。此接口中只有一个方法
-
void execute(JobExecutionContext context) throws JobExecutionException;
-
-
JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
-
Trigger 触发器,代表一个调度参数的配置,什么时候去调。
-
Scheduler 调度器,代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
3. 运行环境
- Quartz 可以运行嵌入在另一个独立式应用程序。
- Quartz 可以在应用程序服务器(或 servlet 容器)内被实例化,并且参与 XA 事务。
- Quartz 可以作为一个独立的程序运行(其自己的 Java 虚拟机内),可以通过 RMI 使用。
- Quartz 可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行。
4. 常用API
- Scheduler 用于与调度程序交互的主程序接口
- 调度程序-任务执行计划表,只有安排进执行计划的任务Job(通过Schedule.scheduleJob方法安排进任务计划),任务触发trigger,任务才会执行
- Job 任务类,如果自定义任务的话,需要实现Job接口,实现
void execute(JobExecutionContext context) throws JobExecutionException;
方法 - JobDetail 定义任务Job的实例,通过JobBuilder类创建
- JobDataMap Map接口实现类,存储Job实例执行过程中的数据
- Trigger触发器,用来执行Job任务
- JobBuilder 用于声明一个任务实例,可定义该任务的任务名,组名等
- TriggerBuilder 触发器创建类,用于创建触发器trigger的实例
- JobListener、TriggerListener、SchedulerListener监听器,用于对组件的监听
5. Quartz的使用
5.1 依赖引入
-
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
-
创建Job接口实现类,实现Job接口,实现execute方法
-
/** * @Author: Hjx * @Date: 2021/8/27 17:39 */ public class HelloJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { String string = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); System.out.println("数据库备份时间:"+string); } }
-
测试Main函数类
-
/** * @Author: Hjx * @Date: 2021/8/27 17:05 */ public class TestQuartz { public static void main(String[] args) throws SchedulerException { // 1. 创建调度器Scheduler,从工厂中获取默认调度器 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 2. 创建JobDetail实例 // HelloJob.class 实现Job接口的实例类 // "JobDetailName" JobDetail的名称,用来标识任务 // "JobDetailGroup" 任务组名称,可省略,默认为DEFAULT // build() 创建JobDetail实例 JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) .withIdentity("JobDetailName", "JobDetailGroup") .build(); // 3. 触发器 Trigger // "TriggerName" 触发器名称 用来标识触发器 // "TriggerGroup" 触发器组名称,可省略,默认为DEFAULT // startNow() 开始时间 立刻开始 // withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) 每5秒执行一次 // 创建触发器 Trigger实例 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("TriggerName", "TriggerGroup") .startNow() .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) .build(); // 将任务与触发器相关联 scheduler.scheduleJob(jobDetail, trigger); // 启动调度器 scheduler.start(); } }
-
运行结果
-
数据库备份时间:2021-08-27 18:00:45 数据库备份时间:2021-08-27 18:00:50 数据库备份时间:2021-08-27 18:00:55 数据库备份时间:2021-08-27 18:01:00 数据库备份时间:2021-08-27 18:01:05 ······
6. Job和JobDetail
6.1 Job
- Job:工作任务调度的接口,任务类需要实现该接口,该接口中的execute方法,类似JDK提供的TimeTask中的run方法,在里面编写任务执行的业务逻辑代码
6.2 Job生命周期
-
Job实例在Quartz中的生命周期:每次调度器执行Job时,它在调用execute方法钱会创建一个新的Job实例,当调用结束后,关联的Job对象就会被释放,被垃圾回收机制回收
-
修改HelloJob类,添加无参构造方法
-
/** * @Author: Hjx * @Date: 2021/8/27 17:39 */ public class HelloJob implements Job { public HelloJob() { System.out.println("HelloJob任务被创建"); } @Override public void execute(JobExecutionContext context) throws JobExecutionException { String string = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); System.out.println("数据库备份时间:"+string); } }
-
执行TestQuartz测试类,结果如下
-
HelloJob任务被创建 数据库备份时间:2021-08-27 18:11:50 HelloJob任务被创建 数据库备份时间:2021-08-27 18:11:55 HelloJob任务被创建 数据库备份时间:2021-08-27 18:12:00 ······
-
可以看到,每执行一次任务,HelloJo类就会被创建一次
6.3 JobDetail
-
jobDetail为Job实例提供了许多设置属性,以及JobDetalMap成员变量属性,用来存储特定Job实例的状态信息,调度器需要借助JobDatail对象添加Job实例
-
重要属性:name、group、jobClass、jobDataMap
-
修改TestQuartz测试类
-
/** * @Author: Hjx * @Date: 2021/8/27 17:05 */ public class TestQuartz { public static void main(String[] args) throws SchedulerException { // 1. 创建调度器Scheduler,从工厂中获取默认调度器 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 2. 创建JobDetail实例 // HelloJob.class 实现Job接口的实例类 // "JobDetailName" JobDetail的名称,用来标识任务 // "JobDetailGroup" 任务组名称,可省略,默认为DEFAULT // build() 创建JobDetail实例 JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) .withIdentity("JobDetailName", "JobDetailGroup") .build(); // 获取jobDetail设置的名称 String jobDetailName = jobDetail.getKey().getName(); System.out.println("设置的任务名称:"+jobDetailName); // 获取设置的任务组名称 String jobDetailGroup = jobDetail.getKey().getGroup(); System.out.println("设置的任务组名称:"+jobDetailGroup); // 获取Job实现类的名称 String simpleName = jobDetail.getJobClass().getSimpleName(); System.out.println("任务实例类名称:"+simpleName); // 3. 触发器 Trigger // "TriggerName" 触发器名称 用来标识触发器 // "TriggerGroup" 触发器组名称,可省略,默认为DEFAULT // startNow() 开始时间 立刻开始 // withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) 每5秒执行一次 // 创建触发器 Trigger实例 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("TriggerName", "TriggerGroup") .startNow() .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) .build(); // 将任务与触发器相关联 scheduler.scheduleJob(jobDetail, trigger); // 启动调度器 scheduler.start(); } }
-
打印JobDetail信息
-
设置的任务名称:JobDetailName 设置的任务组名称:JobDetailGroup 任务实例类名称:HelloJob HelloJob任务被创建 数据库备份时间:2021-08-27 18:28:24 HelloJob任务被创建 数据库备份时间:2021-08-27 18:28:29 ······
6.4 JobExecutionContext
-
当Scheduler调用一个ob,就会将JobExecutionContext传递给Job的execute()方法;
-
Job能通过JobExecutionContext对象访问到Quartz运行时候的环境以及Job本身的明细数据。
-
修改HelloJob类:
-
/** * @Author: Hjx * @Date: 2021/8/27 17:39 */ public class HelloJob implements Job { public HelloJob() { System.out.println("HelloJob任务被创建"); } @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDetail jobDetail = context.getJobDetail(); String jobDetailName = jobDetail.getKey().getName(); System.out.println("任务名称:"+jobDetailName); String jobDetailGroup = jobDetail.getKey().getGroup(); System.out.println("任务组名称:"+jobDetailGroup); String jobSimpleName = jobDetail.getJobClass().getSimpleName(); System.out.println("任务实现类名称:"+jobSimpleName); Trigger trigger = context.getTrigger(); String triggerName = trigger.getKey().getName(); System.out.println("触发器名称:"+triggerName); String triggerGroup = trigger.getKey().getGroup(); System.out.println("触发器组名称:"+triggerGroup); // 获取其他信息 Date date = context.getFireTime(); String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); System.out.println("任务执行的时间"+format); Date nextFireTime = context.getNextFireTime(); String format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(nextFireTime); System.out.println("下一次任务执行的时间"+format1); String string = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); System.out.println("数据库备份时间:"+string); } }
-
运行结果
-
HelloJob任务被创建 任务名称:JobDetailName 任务组名称:JobDetailGroup 任务实现类名称:HelloJob 触发器名称:TriggerName 触发器组名称:TriggerGroup 任务执行的时间2021-08-30 11:00:02 下一次任务执行的时间2021-08-30 11:02:30 数据库备份时间:2021-08-30 11:00:02 ······
6.5 JobDataMap
-
在进行任务调度时,JobDataMap存储在JobExecutionContext中
-
JobDataMap可以用来装载任何可序列化的数据对象,当job实例对象被执行时这些参数对象会传递给它
-
JobDataMap实现了JDK的Map接口
-
-
修改TestQuartz类
-
/** * @Author: Hjx * @Date: 2021/8/27 17:05 */ public class TestQuartz { public static void main(String[] args) throws SchedulerException { // 1. 创建调度器Scheduler,从工厂中获取默认调度器 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 2. 创建JobDetail实例 // HelloJob.class 实现Job接口的实例类 // "JobDetailName" JobDetail的名称,用来标识任务 // "JobDetailGroup" 任务组名称,可省略,默认为DEFAULT // build() 创建JobDetail实例 JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("message","打印日志"); JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) .withIdentity("JobDetailName", "JobDetailGroup") .usingJobData(jobDataMap) .build(); /*// 获取jobDetail设置的名称 String jobDetailName = jobDetail.getKey().getName(); System.out.println("设置的任务名称:"+jobDetailName); // 获取设置的任务组名称 String jobDetailGroup = jobDetail.getKey().getGroup(); System.out.println("设置的任务组名称:"+jobDetailGroup); // 获取Job实现类的名称 String simpleName = jobDetail.getJobClass().getSimpleName(); System.out.println("任务实例类名称:"+simpleName);*/ // 3. 触发器 Trigger // "TriggerName" 触发器名称 用来标识触发器 // "TriggerGroup" 触发器组名称,可省略,默认为DEFAULT // startNow() 开始时间 立刻开始 // withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) 每5秒执行一次 // 创建触发器 Trigger实例 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("TriggerName", "TriggerGroup") .usingJobData(jobDataMap) .startNow() .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) .build(); // 将任务与触发器相关联 scheduler.scheduleJob(jobDetail, trigger); // 启动调度器 scheduler.start(); } }
-
修改HelloJob类
-
/** * @Author: Hjx * @Date: 2021/8/27 17:39 */ public class HelloJob implements Job { public HelloJob() { System.out.println("HelloJob任务被创建"); } @Override public void execute(JobExecutionContext context) throws JobExecutionException { /*JobDetail jobDetail = context.getJobDetail(); String jobDetailName = jobDetail.getKey().getName(); System.out.println("任务名称:"+jobDetailName); String jobDetailGroup = jobDetail.getKey().getGroup(); System.out.println("任务组名称:"+jobDetailGroup); String jobSimpleName = jobDetail.getJobClass().getSimpleName(); System.out.println("任务实现类名称:"+jobSimpleName); Trigger trigger = context.getTrigger(); String triggerName = trigger.getKey().getName(); System.out.println("触发器名称:"+triggerName); String triggerGroup = trigger.getKey().getGroup(); System.out.println("触发器组名称:"+triggerGroup);*/ // 获取jobDataMap JobDataMap jobDetailDataMap = context.getJobDetail().getJobDataMap(); String JobDetailMessage = jobDetailDataMap.getString("message"); System.out.println("任务信息:"+JobDetailMessage); JobDataMap triggerJobDataMap = context.getTrigger().getJobDataMap(); String triggerMessage = triggerJobDataMap.getString("message"); System.out.println("触发器信息:"+triggerMessage); String string = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); System.out.println("数据库备份时间:"+string); } }
-
运行结果
-
HelloJob任务被创建 任务信息:打印日志 触发器信息:打印日志 数据库备份时间:2021-08-30 10:44:24 ······
-
Job实现类中添加setter方法对应JobDataMap的键值,Quartz框架默认的JobFactory实现类在初始化job实例对象时会自动地调用这些setter方法。
-
/** * @Author: Hjx * @Date: 2021/8/27 17:05 */ public class TestQuartz { public static void main(String[] args) throws SchedulerException { // 1. 创建调度器Scheduler,从工厂中获取默认调度器 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 2. 创建JobDetail实例 // HelloJob.class 实现Job接口的实例类 // "JobDetailName" JobDetail的名称,用来标识任务 // "JobDetailGroup" 任务组名称,可省略,默认为DEFAULT // build() 创建JobDetail实例 JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) .withIdentity("JobDetailName", "JobDetailGroup") .usingJobData("message","任务信息") .build(); /*// 获取jobDetail设置的名称 String jobDetailName = jobDetail.getKey().getName(); System.out.println("设置的任务名称:"+jobDetailName); // 获取设置的任务组名称 String jobDetailGroup = jobDetail.getKey().getGroup(); System.out.println("设置的任务组名称:"+jobDetailGroup); // 获取Job实现类的名称 String simpleName = jobDetail.getJobClass().getSimpleName(); System.out.println("任务实例类名称:"+simpleName);*/ // 3. 触发器 Trigger // "TriggerName" 触发器名称 用来标识触发器 // "TriggerGroup" 触发器组名称,可省略,默认为DEFAULT // startNow() 开始时间 立刻开始 // withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) 每5秒执行一次 // 创建触发器 Trigger实例 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("TriggerName", "TriggerGroup") .usingJobData("message","触发器信息") .startNow() .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) .build(); // 将任务与触发器相关联 scheduler.scheduleJob(jobDetail, trigger); // 启动调度器 scheduler.start(); } }
-
/** * @Author: Hjx * @Date: 2021/8/27 17:39 */ public class HelloJob implements Job { private String message; public HelloJob() { System.out.println("HelloJob任务被创建"); } public void setMessage(String message) { this.message = message; } @Override public void execute(JobExecutionContext context) throws JobExecutionException { /*JobDetail jobDetail = context.getJobDetail(); String jobDetailName = jobDetail.getKey().getName(); System.out.println("任务名称:"+jobDetailName); String jobDetailGroup = jobDetail.getKey().getGroup(); System.out.println("任务组名称:"+jobDetailGroup); String jobSimpleName = jobDetail.getJobClass().getSimpleName(); System.out.println("任务实现类名称:"+jobSimpleName); Trigger trigger = context.getTrigger(); String triggerName = trigger.getKey().getName(); System.out.println("触发器名称:"+triggerName); String triggerGroup = trigger.getKey().getGroup(); System.out.println("触发器组名称:"+triggerGroup);*/ /*// 获取jobDataMap JobDataMap jobDetailDataMap = context.getJobDetail().getJobDataMap(); String JobDetailMessage = jobDetailDataMap.getString("message"); System.out.println("任务信息:"+JobDetailMessage); JobDataMap triggerJobDataMap = context.getTrigger().getJobDataMap(); String triggerMessage = triggerJobDataMap.getString("message"); System.out.println("触发器信息:"+triggerMessage);*/ /*// 获取其他信息 Date date = context.getFireTime(); String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); System.out.println("任务执行的时间"+format); Date nextFireTime = context.getNextFireTime(); String format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(nextFireTime); System.out.println("下一次任务执行的时间"+format1);*/ System.out.println(message); String string = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); System.out.println("数据库备份时间:"+string); } }
-
成员的类型和名称需要和在TestQuartz中设置的对应
-
jobDetail和trigger的jobDataMap的key值相同,trigger的jobDataMap会覆盖jobDetail的
6.6 有状态的Job和无状态的Job
-
@PersistJobDataAfterExecution注解的使用
- 有状态的Job可以理解为多次job调用期间可以持有一些状态信息,这些状态信息存储在JobDataMap中
- 默认的无状态job每次调用时都会创建一个新的JobDataMap。
-
/** * @Author: Hjx * @Date: 2021/8/30 11:19 */ public class TestQuartz01 { public static void main(String[] args) throws SchedulerException { // 1. 获取调度器 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 2. 获取JobDetail JobDetail jobDetail = JobBuilder.newJob(TestJob.class) .withIdentity("JobDetail", "JobDetailGroup") .usingJobData("count",0) .build(); // 3. 获取trigger Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("Trigger", "TriggerGroup") .startNow() .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)) .build(); // 4. 通过调度器绑定任务和触发器 scheduler.scheduleJob(jobDetail,trigger); //5. 启动 scheduler.start(); } }
-
/** * @Author: Hjx * @Date: 2021/8/30 11:25 */ public class TestJob implements Job { private Integer count; public void setCount(Integer count) { this.count = count; } @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("TestJob"); count++; JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); jobDataMap.put("count",count); System.out.println("TestJob中的count值:"+count); System.out.println("jobDataMap的count值:"+jobDataMap.getInt("count")); } }
-
TestJob TestJob中的count值:1 jobDataMap的count值:1 TestJob TestJob中的count值:1 jobDataMap的count值:1 ······
-
添加@PersistJobDataAfterExecution注解
-
/** * @Author: Hjx * @Date: 2021/8/30 11:25 */ @PersistJobDataAfterExecution public class TestJob implements Job { private Integer count; public void setCount(Integer count) { this.count = count; } @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("TestJob"); count++; JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); jobDataMap.put("count",count); System.out.println("TestJob中的count值:"+count); System.out.println("jobDataMap的count值:"+jobDataMap.getInt("count")); } }
-
TestJob TestJob中的count值:1 jobDataMap的count值:1 TestJob TestJob中的count值:2 jobDataMap的count值:2 TestJob TestJob中的count值:3 jobDataMap的count值:3 ······
代码参考地址:[Spring/Quartz · 萝卜7/点点滴滴汇成河 - 码云 - 开源中国 (gitee.com)](