一.介绍
Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
二.Quartz核心概念
我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。
1.Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context)
方法中式具体工作要做的内容
2.JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
3.Trigger 代表一个调度参数的配置,什么时候去调。
4.Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
三.demo搭建
1.导入依赖的jar包
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
2.新建一个class,自定义job类
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));
}
}
3.创建Schedule,执行任务:
public class MySchedule {
public static void main(String[] args) throws SchedulerException {
// 1、创建调度器Scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1").build();
// 3、构建Trigger实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever()).build();
//4、执行
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
4.运行可以看到程序每隔1s会打印出内容,且在一分钟后结束
四.详解
下面就程序中出现的几个参数,看一下Quartz框架中的几个重要参数:
Job和JobDetail
JobExecutionContext
JobDataMap
Trigger、SimpleTrigger、CronTrigger
1.Job和JobDetail
Job是Quartz中的一个接口,接口下只有execute方法,在这个方法中编写业务逻辑。
接口中的源码:
public interface Job {
void execute(JobExecutionContext var1) throws JobExecutionException;
}
JobDetail用来绑定Job,为Job实例提供许多属性:
name
group
jobClass
jobDataMap
JobDetail绑定指定的Job,每次Scheduler调度执行一个Job的时候,首先会拿到对应的Job,然后创建该Job实例,再去执行Job中的execute()的内容,任务执行结束后,关联的Job对象实例会被释放,且会被JVM GC清除。
为什么设计成JobDetail + Job,不直接使用Job
jobDetail定义的是任务数据,而真正的执行逻辑是在Job中。
这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,Sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。
2.JobExecutionContext
JobExecutionContext中包含了Quartz运行时的环境以及Job本身的详细数据信息。
当Schedule调度执行一个Job的时候,就会将JobExecutionContext传递给该Job的execute()中,Job就可以通过JobExecutionContext对象获取信息。
主要信息有:
3.JobDataMap
JobDataMap实现了JDK的Map接口,可以以Key-Value的形式存储数据。
JobDetail、Trigger都可以使用JobDataMap来设置一些参数或信息,
Job执行execute()方法的时候,JobExecutionContext可以获取到JobExecutionContext中的信息:
public static void main(String[] args) throws SchedulerException {
// 1、创建调度器Scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.usingJobData("message","message jobDetail")
.withIdentity("job1", "group1").build();
// 3、构建Trigger实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.startNow()
.usingJobData("message","message trigger")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever()).build();
//4、执行
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
上面通过JobDataMap传递了key为meaage的信息
Job执行的时候,可以获取到这些参数信息:
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String printTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss").format(new Date());
System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));
jobExecutionContext.getJobDetail().getJobDataMap().get("message");
jobExecutionContext.getTrigger().getJobDataMap().get("message");
}
}
当然也可以将message定义一个类变量,加上setter方法,在job执行的时候同样可以传递过来,获取到
private String message;
public void setMessage(String message) {
this.message = message;
}
4.Trigger、SimpleTrigger、CronTrigger
Trigger是Quartz的触发器,会去通知Scheduler何时去执行对应Job。
new Trigger().startAt():表示触发器首次被触发的时间;
new Trigger().endAt():表示触发器结束触发的时间;
SimpleTrigger
SimpleTrigger可以实现在一个指定时间段内执行一次作业任务或一个时间段内多次执行作业任务。
// 构建Trigger实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.startNow()
.usingJobData("message","message trigger")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever()).build();
CronTrigger
CronTrigger功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用。CroTrigger是基于Cron表达式的,
通过@Schedule注解都应该清楚cron表达式
下面是每周一到周五上午10:30执行定时任务
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.startNow()
.startAt(startDate)
.endAt(endDate)
.usingJobData("message","message trigger")
.withSchedule(CronScheduleBuilder.cronSchedule("* 30 10 ? * 1/5 2020")).build();
4.有状态的job和无状态的job
@PersistJobDataAfterExecution的使用
有状态的job可以理解为多次调用job期间可以持有获得一些信息,这些信息储存在
jobDataMap中,而默认的无状态的job每次调用都会创建一个新的jobDataMap
比如在jobDataMap里面赋值一个count,值为0
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.usingJobData("count",0)
.withIdentity("job1", "group1").build();
在job的excute中获取到count的值进行+1操作,
Integer count= (Integer) jobExecutionContext.getJobDetail().getJobDataMap().get("count");
count++;
System.out.println("count的值:"+count);
jobExecutionContext.getJobDetail().getJobDataMap().put("count",count);
运行结果:每次输出的都是1
在job类上加上@PersistJobDataAfterExecution注解,使之声明为有状态的job
@PersistJobDataAfterExecution
public class MyJob implements Job {
在运行结果:每次count的值都+1
五.监听器
Listeners是您创建的对象,用于根据调度程序中发生的事件执行操作。
job相关事件包括:job即将执行的通知,以及job完成执行时的通知。
org.quartz.JobListener接口
JobListeners
public interface JobListener {
public String getName();
public void jobToBeExecuted(JobExecutionContext context);
public void jobExecutionVetoed(JobExecutionContext context);
public void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException);
}
TriggerListeners
与触发相关的事件包括:触发器触发,触发失灵(在本文档的“触发器”部分中讨论),并触发完成(触发器关闭的作业完成)。
org.quartz.TriggerListener接口
public interface TriggerListener {
public String getName();
public void triggerFired(Trigger trigger, JobExecutionContext context);
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
public void triggerMisfired(Trigger trigger);
public void triggerComplete(Trigger trigger, JobExecutionContext context,
int triggerInstructionCode);
}
SchedulerListeners
SchedulerListeners非常类似于TriggerListeners和JobListeners,除了它们在Scheduler本身中接收到事件的通知 - 不一定与特定触发器(trigger)或job相关的事件。
与计划程序相关的事件包括:添加job/触发器,删除job/触发器,调度程序中的严重错误,关闭调度程序的通知等。
org.quartz.SchedulerListener接口
public interface SchedulerListener {
public void jobScheduled(Trigger trigger);
public void jobUnscheduled(String triggerName, String triggerGroup);
public void triggerFinalized(Trigger trigger);
public void triggersPaused(String triggerName, String triggerGroup);
public void triggersResumed(String triggerName, String triggerGroup);
public void jobsPaused(String jobName, String jobGroup);
public void jobsResumed(String jobName, String jobGroup);
public void schedulerError(String msg, SchedulerException cause);
public void schedulerStarted();
public void schedulerInStandbyMode();
public void schedulerShutdown();
public void schedulingDataCleared();
}
六.JobStore
JobStore负责跟踪您提供给调度程序的所有“工作数据”:jobs,triggers,日历等。为您的Quartz调度程序实例选择适当的JobStore是重要的一步。幸运的是,一旦你明白他们之间的差异,那么选择应该是一个非常简单的选择。
您声明您提供给用于生成调度程序实例的SchedulerFactory的属性文件(或对象)中您的调度程序应使用哪个JobStore(以及它的配置设置)。
1.RAMJobStore
RAMJobStore是使用最简单的JobStore,它也是性能最高的(在CPU时间方面)。RAMJobStore以其明显的方式获取其名称:它将其所有数据保存在RAM中。这就是为什么它是闪电般快的,也是为什么这么简单的配置。缺点是当您的应用程序结束(或崩溃)时,所有调度信息都将丢失 - 这意味着RAMJobStore无法履行作业和triggers上的“非易失性”设置。对于某些应用程序,这是可以接受的 - 甚至是所需的行为,但对于其他应用程序,这可能是灾难性的。
要使用RAMJobStore(并假设您使用的是StdSchedulerFactory),只需将类名称org.quartz.simpl.RAMJobStore指定为用于配置石英的JobStore类属性:
配置Quartz以使用RAMJobStore
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
2.JDBC JobStore
JDBCJobStore也被恰当地命名 - 它通过JDBC将其所有数据保存在数据库中。因此,配置比RAMJobStore要复杂一点,而且也不是那么快。但是,性能下降并不是很糟糕,特别是如果您在主键上构建具有索引的数据库表.
JDBCJobStore几乎与任何数据库一起使用,已被广泛应用于Oracle,PostgreSQL,MySQL,MS SQLServer,HSQLDB和DB2。要使用JDBCJobStore,必须首先创建一组数据库表以供Quartz使用。您可以在Quartz发行版的“docs / dbTables”目录中找到表创建SQL脚本。如果您的数据库类型尚未有脚本,请查看其中一个脚本,然后以数据库所需的任何方式进行修改。需要注意的一点是,在这些脚本中,所有的表都以前缀“QRTZ_”开始(如表“QRTZ_TRIGGERS”和“QRTZ_JOB_DETAIL”)。只要你通知JDBCJobStore前缀是什么(在你的Quartz属性中),这个前缀实际上可以是你想要的。对于多个调度程序实例,使用不同的前缀可能有助于创建多组表,
创建表后,在配置和启动JDBCJobStore之前,您还有一个重要的决定。您需要确定应用程序需要哪种类型的事务。如果您不需要将调度命令(例如添加和删除triggers)绑定到其他事务,那么可以通过使用JobStoreTX作为JobStore 来管理事务(这是最常见的选择)。
如果您需要Quartz与其他事务(即J2EE应用程序服务器)一起工作,那么您应该使用JobStoreCMT - 在这种情况下,Quartz将让应用程序服务器容器管理事务。
最后一个难题是设置一个DataSource,JDBCJobStore可以从中获取与数据库的连接。DataSources在Quartz属性中使用几种不同的方法之一进行定义。一种方法是让Quartz创建和管理DataSource本身 - 通过提供数据库的所有连接信息。另一种方法是让Quartz使用由Quartz正在运行的应用程序服务器管理的DataSource,通过提供JDBCJobStore DataSource的JNDI名称。有关属性的详细信息,请参阅“docs / config”文件夹中的示例配置文件。
要使用JDBCJobStore(并假定您使用的是StdSchedulerFactory),首先需要将Quartz配置的JobStore类属性设置为org.quartz.impl.jdbcjobstore.JobStoreTX或org.quartz.impl.jdbcjobstore.JobStoreCMT - 具体取决于根据上述几段的解释,您所做的选择
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
接下来,您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作。StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托。如果没有为您的数据库专门制作另一个代理,请尝试使用此委托 - 我们仅为数据库制作了特定于数据库的代理,我们使用StdJDBCDelegate(似乎最多!)发现了问题。其他代理可以在“org.quartz.impl.jdbcjobstore”包或其子包中找到。其他代理包括DB2v6Delegate(用于DB2版本6及更早版本),HSQLDBDelegate(用于HSQLDB),MSSQLDelegate(用于Microsoft SQLServer),PostgreSQLDelegate(用于PostgreSQL)),WeblogicDelegate(用于使用Weblogic创建的JDBC驱动程序)
选择委托后,将其类名设置为JDBCJobStore的委托使用。
配置JDBCJobStore以使用DriverDelegate
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
接下来,您需要通知JobStore您正在使用的表前缀(如上所述)。
使用表前缀配置JDBCJobStore
org.quartz.jobStore.tablePrefix = QRTZ_
最后,您需要设置JobStore应该使用哪个DataSource。命名的DataSource也必须在Quartz属性中定义。在这种情况下,我们指定Quartz应该使用DataSource名称“myDS”(在配置属性中的其他位置定义)。
使用要使用的DataSource的名称配置JDBCJobStore
org.quartz.jobStore.dataSource = myDS
示例:
org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@ 10.0.1.23:1521:demodb
org.quartz.dataSource.myDS.user = myUser
org.quartz.dataSource.myDS.password = myPassword
org.quartz.dataSource.myDS.maxConnections = 30
3.TerracottaJobStore
TerracottaJobStore提供了一种缩放和鲁棒性的手段,而不使用数据库。这意味着您的数据库可以免受Quartz的负载,可以将其所有资源保存为应用程序的其余部分。
TerracottaJobStore可以运行群集或非群集,并且在任一情况下,为应用程序重新启动之间持续的作业数据提供存储介质,因为数据存储在Terracotta服务器中。它的性能比通过JDBCJobStore使用数据库要好得多(约一个数量级更好),但比RAMJobStore要慢。
要使用TerracottaJobStore(并且假设您使用的是StdSchedulerFactory),只需将类名称org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore指定为用于配置石英的JobStore类属性,并添加一个额外的行配置来指定Terracotta服务器的位置:
配置Quartz以使用TerracottaJobStore
org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
org.quartz.jobStore.tcConfigUrl = localhost:9510