任务调度之Quartz1
目标
1、了解任务调度的应用场景和Quartz的基本特性
2、掌握Quartz Java编程和Spring集成的使用
3、掌握Quartz动态调度和集群部署的实现
4、理解Quartz原理与线程模型
今天你将学到:
1、除了下视频下图片,定时任务还可以干什么?
2、当我们在用Spring Task的时候,我们在用什么?
3、节假日购买理财不计息,怎么实现?
4、任务跑完给管理员发一条短信,怎么实现?
5、明明配置了线程池,怎么就变成单线程了?
6、怎么kill一个进程里面的一个任务?
7、Quartz的幕后角色:包工头、工人、项目经理
8、当我想要任务重复执行的时候,为什么没有重复执行?
内容定位
适合没有用过Quartz或者只会Quartz基本配置的同学
说明:基于最新稳定版本2.3.0
漫谈任务调度
什么时候需要任务调度?
任务调度的背景
在业务系统中有很多这样的场景:
1、账单日或者还款日上午10 点,给每个信用卡客户发送账单通知,还款通知。如何判断客户的账单日、还款日,完成通知的发送?
2、银行业务系统,夜间要完成跑批的一系列流程,清理数据,下载文件,解析文件,对账清算、切换结算日期等等。如何触发一系列流程的执行?
3、金融机构跟人民银行二代支付系统对接,人民银行要求低于5W 的金额(小额支付)半个小时打一次包发送,以缓解并发压力。所以,银行的跨行转账分成了多个流程:录入、复核、发送。如何把半个小时以内的所有数据一次性发送?
类似于这种1、基于准确的时刻或者固定的时间间隔触发的任务,或者2、有批量数据需要处理,或者3、要实现两个动作解耦的场景,我们都可以用任务调度来实现。
任务调度需求分析
任务调度的实现方式有很多,如果要实现我们的调度需求,我们对这个工具有什么样的基本要求呢?
基本需求
1)可以定义触发的规则,比如基于时刻、时间间隔、表达式。
2)可以定义需要执行的任务。比如执行一个脚本或者一段代码。任务和规则是分开的。
3)集中管理配置,持久配置。不用把规则写在代码里面,可以看到所有的任务配置,方便维护。重启之后任务可以再次调度——配置文件或者配置中心。
4)支持任务的串行执行,例如执行A 任务后再执行B 任务再执行C 任务。
5)支持多个任务并发执行,互不干扰(例如ScheduledThreadPoolExecutor)。
6)有自己的调度器,可以启动、中断、停止任务。
7)容易集成到Spring。
任务调度工具对比
| 层次 | 举例 | 特点 |
|---|---|---|
| 操作系统 | Linux crontab Windows 计划任务 | 只能执行简单脚本或者命令 |
| 数据库 | MySQL、Oracle | 可以操作数据。不能执行Java 代码 |
| 工具 | Kettle | 可以操作数据,执行脚本。没有集中配置 |
| 开发语言 | JDK Timer、ScheduledThreadPool | Timer:单线程 JDK1.5 之后:ScheduledThreadPool(Cache、Fiexed、 Single):没有集中配置,日程管理不够灵活 |
| 容器 | Spring Task、@Scheduled | 不支持集群 |
| 分布式框架 | XXL-JOB,Elastic-Job |
package com.leon.jdktimer;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;public class TestTimerTask extends TimerTask {/*** 此计时器任务要执行的操作。*/public void run() {Date executeTime = new Date(this.scheduledExecutionTime());String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());System.out.println("任务执行了:" + dateStr);}
}
package com.leon.jdktimer;import java.util.Timer;
import java.util.TimerTask;public class TestTimer {public static void main(String[] args) {Timer timer = new Timer();TimerTask task = new TestTimerTask();timer.schedule(task, 5000L, 1000L);}
}
@Scheduled 也是用JUC 的ScheduledExecutorService 实现的
Scheduled(cron = “0 15 10 15 * ?”)
1、ScheduledAnnotationBeanPostProcessor 的postProcessAfterInitialization 方法将@Scheduled 的方法包装为指定的task
添加到ScheduledTaskRegistrar 中
2、ScheduledAnnotationBeanPostProcessor 会监听Spring 的容器初始化事件, 在Spring 容器初始化完成后进行TaskScheduler 实现类实例的查找,若发现有SchedulingConfigurer 的实现类实例,则跳过3
3、查找TaskScheduler 的实现类实例默认是通过类型查找,若有多个实现则会查找名字为"taskScheduler"的实现Bean,若没有找到则在ScheduledTaskRegistrar 调度任务的时候会创建一个newSingleThreadScheduledExecutor , 将TaskScheduler 的实现类实例设置到ScheduledTaskRegistrar 属性中
4、ScheduledTaskRegistrar 的scheduleTasks 方法触发任务调度
5、真正调度任务的类是TaskScheduler 实现类中的ScheduledExecutorService,由J.U.C 提供
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
引入依赖
org.quartz-scheduler quartz 2.3.0
默认配置文件
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
创建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("leon","2673").usingJobData("moon",5.21F).build();
创建Trigger
在测试类main()方法中,基于SimpleTrigger 定义了一个每2 秒钟运行一次、不断重复的Trigger:
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();
创建Scheduler
在测试类main()方法中,通过Factory 获取调度器的实例,把JobDetail 和Trigger绑定,注册到容器中。
Scheduler 先启动后启动无所谓,只要有Trigger 到达触发条件,就会执行任务。
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
注意这里,调度器一定是单例的。
体系结构总结

JobDetail
我们创建一个实现Job 接口的类,使用JobBuilder 包装成JobDetail,它可以携带KV 的数据。
Trigger
定义任务的触发规律,Trigger,使用TriggerBuilder 来构建。
JobDetail 跟Trigger 是1:N 的关系。
思考:为什么要解耦?
Trigger 接口在Quartz 有4 个继承的子接口:
| 子接口 | 描述 | 特点 |
|---|---|---|
| SimpleTrigger | 简单触发器 | 固定时刻或时间间隔,毫秒 |
| CalendarIntervalTrigger | 基于日历的触发器 | 比简单触发器更多时间单位,支持非固定时 间的触发,例如一年可能365/366,一个月 可能28/29/30/31 |
| DailyTimeIntervalTrigger | 基于日期的触发器 | 每天的某个时间段 |
| CronTrigger | 基于Cron 表达式的触发器 |

代码:standalone com.leon.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.leon.calendar.CalendarDemo
package com.leon.calendar;import com.leon.job.MyJob1;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.calendar.AnnualCalendar;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;/*** @Author: qingshan* @Date: 2019/9/5 10:40* @Description: 咕泡学院,只为更好的你*/
public class CalendarDemo {public static void main(String[] args) throws Exception {SchedulerFactory sf = new StdSchedulerFactory();Scheduler scheduler = sf.getScheduler();scheduler.start();// 定义日历AnnualCalendar holidays = new AnnualCalendar();// 排除咕泡日Calendar leonDay = (Calendar) new GregorianCalendar(2019, 8, 8);holidays.setDayExcluded(leonDay, true);// 排除中秋节Calendar midAutumn = new GregorianCalendar(2019, 9, 13);holidays.setDayExcluded(midAutumn, true);// 排除圣诞节Calendar christmas = new GregorianCalendar(2019, 12, 25);holidays.setDayExcluded(christmas, true);// 调度器添加日历scheduler.addCalendar("holidays", holidays, false, false);JobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").usingJobData("leon","青山 2673").build();Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().modifiedByCalendar("holidays").withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();Date firstRunTime = scheduler.scheduleJob(jobDetail, trigger);System.out.println(jobDetail.getKey() + " 第一次触发: " + firstRunTime);}
}
| 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()
这些方法非常重要,可以实现任务的动态调度。
Listener
我们有这么一种需求,在每个任务运行结束之后发送通知给运维管理员。那是不是要在每个任务的最后添加一行代码呢?这种方式对原来的代码造成了入侵,不利于维护。如果代码不是写在任务代码的最后一行,怎么知道任务执行完了呢?或者说,怎么监测到任务的生命周期呢?
观察者模式:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖它的对象都会得到通知并自动更新。
Quartz 中提供了三种Listener,监听Scheduler 的,监听Trigger 的,监听Job 的。只需要创建类实现相应的接口,并在Scheduler 上注册Listener,便可实现对核心对象的监听。
standalone 工程:com.leon.listener
MyJobListenerTest
MySchedulerListenerTest
MyTriggerListenerTest
package com.leon.listener;import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;public class MyJobListener implements JobListener {public String getName() {String name = getClass().getSimpleName();System.out.println( "Method 111111 :"+ "获取到监听器名称:"+name);return name;}public void jobToBeExecuted(JobExecutionContext context) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Method 222222 :"+ jobName + " ——任务即将执行 ");}public void jobExecutionVetoed(JobExecutionContext context) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Method 333333 :"+ jobName + " ——任务被否决 ");}public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Method 444444 :"+ jobName + " ——执行完毕 ");System.out.println("------------------");}
}
package com.leon.listener;import com.leon.job.MyJob1;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.EverythingMatcher;/*** 测试监听器*/
public class MyJobListenerTest {public static void main(String[] args) throws SchedulerException {// JobDetailJobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();// TriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();// SchedulerFactorySchedulerFactory factory = new StdSchedulerFactory();// SchedulerScheduler scheduler = factory.getScheduler();scheduler.scheduleJob(jobDetail, trigger);// 创建并注册一个全局的Job Listenerscheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());scheduler.start();}}
package com.leon.listener;import org.quartz.*;public class MySchedulerListener implements SchedulerListener {public void jobScheduled(Trigger trigger) {String jobName = trigger.getJobKey().getName();System.out.println( jobName + " has been scheduled");}public void jobUnscheduled(TriggerKey triggerKey) {System.out.println(triggerKey + " is being unscheduled");}public void triggerFinalized(Trigger trigger) {System.out.println("Trigger is finished for " + trigger.getJobKey().getName());}public void triggerPaused(TriggerKey triggerKey) {System.out.println(triggerKey + " is being paused");}public void triggersPaused(String triggerGroup) {System.out.println("trigger group "+triggerGroup + " is being paused");}public void triggerResumed(TriggerKey triggerKey) {System.out.println(triggerKey + " is being resumed");}public void triggersResumed(String triggerGroup) {System.out.println("trigger group "+triggerGroup + " is being resumed");}public void jobAdded(JobDetail jobDetail) {System.out.println(jobDetail.getKey()+" is added");}public void jobDeleted(JobKey jobKey) {System.out.println(jobKey+" is deleted");}public void jobPaused(JobKey jobKey) {System.out.println(jobKey+" is paused");}public void jobsPaused(String jobGroup) {System.out.println("job group "+jobGroup+" is paused");}public void jobResumed(JobKey jobKey) {System.out.println(jobKey+" is resumed");}public void jobsResumed(String jobGroup) {System.out.println("job group "+jobGroup+" is resumed");}public void schedulerError(String msg, SchedulerException cause) {System.out.println(msg + cause.getUnderlyingException().getStackTrace());}public void schedulerInStandbyMode() {System.out.println("scheduler is in standby mode");}public void schedulerStarted() {System.out.println("scheduler has been started");}public void schedulerStarting() {System.out.println("scheduler is being started");}public void schedulerShutdown() {System.out.println("scheduler has been shutdown");}public void schedulerShuttingdown() {System.out.println("scheduler is being shutdown");}public void schedulingDataCleared() {System.out.println("scheduler has cleared all data");}
}
package com.leon.listener;import com.leon.job.MyJob1;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.EverythingMatcher;/*** 测试监听器*/
public class MySchedulerListenerTest {public static void main(String[] args) throws SchedulerException {// JobDetailJobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();// TriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();// SchedulerFactorySchedulerFactory factory = new StdSchedulerFactory();// SchedulerScheduler scheduler = factory.getScheduler();scheduler.scheduleJob(jobDetail, trigger);// 创建Scheduler Listenerscheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());scheduler.start();}}
package com.leon.listener;import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;public class MyTriggerListener implements TriggerListener {private String name;public MyTriggerListener(String name) {this.name = name;}public String getName() {return name;}// Trigger 被触发,Job 上的 execute() 方法将要被执行时public void triggerFired(Trigger trigger, JobExecutionContext context) {String triggerName = trigger.getKey().getName();System.out.println("Method 11111 " + triggerName + " was fired");}// 在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方法// 返回true时,这个任务不会被触发public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {String triggerName = trigger.getKey().getName();System.out.println("Method 222222 " + triggerName + " was not vetoed");return false;}public void triggerMisfired(Trigger trigger) {String triggerName = trigger.getKey().getName();System.out.println("Method 333333 " + triggerName + " misfired");}public void triggerComplete(Trigger trigger, JobExecutionContext context,Trigger.CompletedExecutionInstruction triggerInstructionCode) {String triggerName = trigger.getKey().getName();System.out.println("Method 444444 " + triggerName + " is complete");System.out.println("------------");}
}
package com.leon.listener;import com.leon.job.MyJob1;
import com.leon.listener.MyJobListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.EverythingMatcher;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.matchers.KeyMatcher;/*** 测试监听器*/
public class MyTriggerListenerTest {public static void main(String[] args) throws SchedulerException {// JobDetailJobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();// TriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();// SchedulerFactorySchedulerFactory factory = new StdSchedulerFactory();// SchedulerScheduler scheduler = factory.getScheduler();scheduler.scheduleJob(jobDetail, trigger);// 创建并注册一个全局的Trigger Listenerscheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener1"), EverythingMatcher.allTriggers());// 创建并注册一个局部的Trigger Listenerscheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener2"), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "gourp1")));// 创建并注册一个特定组的Trigger ListenerGroupMatcher matcher = GroupMatcher.triggerGroupEquals("gourp1");scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener3"), matcher);scheduler.start();}}
JobListener
四个方法:
| 方法 | 作用或执行实际 |
|---|---|
| getName() | 返回JobListener 的名称 |
| jobToBeExecuted() | Scheduler 在JobDetail 将要被执行时调用这个方法 |
| jobExecutionVetoed() | 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/leon?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 的信息 |
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
