任务调度之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、ScheduledThreadPoolTimer:单线程
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

引入依赖

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

默认配置文件

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 表达式

位置时间域 特殊值
10-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 {

		// JobDetail
		JobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();

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

		// SchedulerFactory
		SchedulerFactory  factory = new StdSchedulerFactory();

		// Scheduler
		Scheduler scheduler = factory.getScheduler();

		scheduler.scheduleJob(jobDetail, trigger);

		// 创建并注册一个全局的Job Listener
		scheduler.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 {

		// JobDetail
		JobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();

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

		// SchedulerFactory
		SchedulerFactory  factory = new StdSchedulerFactory();

		// Scheduler
		Scheduler scheduler = factory.getScheduler();

		scheduler.scheduleJob(jobDetail, trigger);

		// 创建Scheduler Listener
		scheduler.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 {

		// JobDetail
		JobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();

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

		// SchedulerFactory
		SchedulerFactory  factory = new StdSchedulerFactory();

		// Scheduler
		Scheduler scheduler = factory.getScheduler();

		scheduler.scheduleJob(jobDetail, trigger);


		// 创建并注册一个全局的Trigger Listener
		scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener1"), EverythingMatcher.allTriggers());

		// 创建并注册一个局部的Trigger Listener
		scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener2"), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "gourp1")));

		// 创建并注册一个特定组的Trigger Listener
		GroupMatcher<TriggerKey> 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_TRIGGERSTrigger 作为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 的信息

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值