Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的灵 活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,EJB 作业预构 建,JavaMail 及其它,支持 cron-like 表达式等等。
<dependency>
<groupId>org.quartz-schedulergroupId>
<artifactId>quartzartifactId>
<version>${quartz.version}version>
dependency>
创建Job作业,实现Job类的execute方法,execute()方法用于定义需要执行的内容。
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 获取任务调度中提供的参数
Object tv1 = context.getTrigger().getJobDataMap().get("t1");
Object tv2 = context.getTrigger().getJobDataMap().get("t2");
Object jv1 = context.getJobDetail().getJobDataMap().get("j1");
Object jv2 = context.getJobDetail().getJobDataMap().get("j2");
Object sv = null;
try {
sv = context.getScheduler().getContext().get("skey");
} catch (SchedulerException e) {
e.printStackTrace();
}
System.out.println(tv1+":"+tv2);
System.out.println(jv1+":"+jv2);
System.out.println(sv);
System.out.println("hello:"+LocalDateTime.now());
}
}
编写测试代码,创建任务调度
public class TestQuarteJob extends TestConfig {
@Test
public void jobTest() throws SchedulerException, InterruptedException {
//创建一个scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.getContext().put("skey", "svalue");
//创建一个Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.usingJobData("t1", "tv1")
// 定义触发时间
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3) // 每3秒执行一次
.repeatForever() // 重复执行
).build();
trigger.getJobDataMap().put("t2", "tv2");
//创建一个job
JobDetail job = JobBuilder.newJob(HelloJob.class)
.usingJobData("j1", "jv1")
.withIdentity("myjob", "mygroup")
.build();
job.getJobDataMap().put("j2", "jv2");
//注册trigger并启动scheduler
scheduler.scheduleJob(job,trigger);
scheduler.start();
// 休眠当前线程,方便观察定时作业的结果
Thread.sleep(30*1000);
}
}
Quartz API的关键接口是:
job的一个 trigger 被触发后,execute() 方法会被 scheduler 的一个工作线程调用;传递给 execute() 方法的 JobExecutionContext 对象中保存着该 job 运行时的一些信息 ,执行 job 的 scheduler 的引用,触发 job 的 trigger 的引用,JobDetail 对象引用,以及一些其它信息。
JobDetail 对象是在将 job 加入 scheduler 时,由客户端程序(你的程序)创建的。它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap。本节是对 job 实例的简单介绍,更多的细节将在下一节讲到。
Trigger 用于触发 Job 的执行。当你准备调度一个 job 时,你创建一个 Trigger 的实例,然后设置调度相关的属性。Trigger 也有一个相关联的 JobDataMap,用于给 Job 传递一些触发相关的参数。Quartz 自带了各种不同类型的 Trigger,最常用的主要是 SimpleTrigger 和 CronTrigger。
SimpleTrigger 主要用于一次性执行的 Job(只在某个特定的时间点执行一次),或者 Job 在特定的时间点执行,重复执行 N 次,每次执行间隔T个时间单位。CronTrigger 在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午 10:15”等。
为什么既有 Job,又有 Trigger 呢?很多任务调度器并不区分 Job 和 Trigger。有些调度器只是简单地通过一个执行时间和一些 job 标识符来定义一个 Job;其它的一些调度器将 Quartz 的 Job 和 Trigger 对象合二为一。在开发 Quartz 的时候,我们认为将调度和要调度的任务分离是合理的。在我们看来,这可以带来很多好处。
例如,Job 被创建后,可以保存在 Scheduler 中,与 Trigger 是独立的,同一个 Job可以有多个 Trigger;这种松耦合的另一个好处是,当与 Scheduler 中的 Job 关联的 trigger 都过期时,可以配置 Job 稍后被重新调度,而不用重新定义 Job;还有,可以修改或者替换 Trigger,而不用重新定义与之关联的 Job。
按照上述的示例,在创建调度任务时,会调用JobDetail,而在JobDetail中会指定需要执行的作业(实现了Job接口的实现类)和执行的频率等信息。因此在调用时都会创建新的Job实例,旧的实例将被系统回收,因此无法在Job中定义有状态的数据属性。
此时就需要用到Quartz提供的参数JobDataMap,该类可以提供一些基础数据类型的赋值。
public class DumbJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 获取key
JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
// 获取JobDataMap
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
String username = jobDataMap.getString("username");
int age = jobDataMap.getInt("age");
ArrayList<Integer> arr = (ArrayList) jobDataMap.get("arr");
System.out.println("jobKey:" + jobKey + ", username:" + username + ", age:" + age + ", arr:" + arr);
}
}
编写测试类
@Test
public void testDumbJob() {
try {
ArrayList<Integer> arr = new ArrayList<>();
arr.add(11);
arr.add(12);
arr.add(13);
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("arr", arr);
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail jobDetail = JobBuilder.newJob(DumbJob.class)
.withIdentity("dumbJob", "group1")
.usingJobData("username", "Tom")
.usingJobData("age", 18)
.usingJobData("arr", arr)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("dumbJob", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
Thread.sleep(30*1000);
} catch (SchedulerException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:

根据运行结果看出,获取的jobKey值就是获取调度任务时赋予的任务名和分组信息。从任务调度中可以为Job中传递各种类型的数据,例如数值型、字符型以及一些集合类型,这里需要注意的是在创建JobDetail时,根据usingJobData()方法,可以添加基础数据类型,如果还需要添加其他类型的数据可以自行创建JobDataMap对象,调用put()方法,为其添加需要的数据。
除了调用JobDataMap的get()方法获取传递的数据外,还可以通过setter方法进行自动赋值。例如:
public class DumbAutoJob implements Job {
private String username;
private int age;
private ArrayList arr;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 获取key
JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
System.out.println("jobKey:" + jobKey + ", username:" + username + ", age:" + age + ", arr:" + arr);
}
public void setUsername(String username) {
this.username = username;
}
public void setAge(int age) {
this.age = age;
}
public void setArr(ArrayList arr) {
this.arr = arr;
}
}
通过Job类的setter方法,可以将scheduler中的JobDataMap数据根据key值自动填充进去。
关于job的状态数据(即JobDataMap)和并发性,还有一些地方需要注意。在job类上可以加入一些注解,这些注解会影响job的状态和并发性。
@DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例。请注意这里的用词。拿前一小节的例子来说,如果“SalesReportJob”类上有该注解,则同一时刻仅允许执行一个“SalesReportForJoe”实例,但可以并发地执行“SalesReportForMike”类的一个实例。所以该限制是针对JobDetail的,而不是job类的。但是我们认为(在设计Quartz的时候)应该将该注解放在job类上,因为job类的改变经常会导致其行为发生变化。
@PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。和 @DisallowConcurrentExecution注解一样,尽管注解是加在job类上的,但其限制作用是针对job实例的,而不是job类的。由job类来承载注解,是因为job类的内容经常会影响其行为状态(比如,job类的execute方法需要显式地“理解”其”状态“)。
如果你使用了@PersistJobDataAfterExecution注解,我们强烈建议你同时使用@DisallowConcurrentExecution注解,因为当同一个job(JobDetail)的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。
通过JobDetail对象,可以给job实例配置的其它属性有:
Trigger用于设置定时作业的调度信息,例如何时开始、何时结束、作业的执行频率等信息。
所有类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此之外,trigger还有很多其它的公共属性。这些属性,在构建trigger的时候可以通过TriggerBuilder设置。
Trigger的公共属性有:
@Test
public void testTriggerJob() {
try {
Date triggerStartTime = new Date();
Date triggerEndTime = new Date();
Calendar c = Calendar.getInstance();
c.set(2022, Calendar.AUGUST, 9, 15, 52, 0);
triggerStartTime = c.getTime();
c.set(2022, Calendar.AUGUST, 9, 15, 53, 0);
triggerEndTime = c.getTime();
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail jobDetail = JobBuilder.newJob(DumbAutoJob.class)
.withIdentity("dumbJob", "group1")
.usingJobData("username", "Tom")
.usingJobData("age", 18)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("dumbJob", "group1")
// .startNow() // 在当前时间启动
.startAt(triggerStartTime) // 设置开始时间
.endAt(triggerEndTime) // 设置结束时间
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
// .withIntervalInMilliseconds(1) // 设置间隔时间,单位:毫秒
.withIntervalInSeconds(10) // 设置间隔时间,单位:秒
// .withIntervalInMinutes(1) // 设置间隔时间,单位:分
// .withIntervalInHours(1) // 设置间隔时间,单位:小时
// .withRepeatCount(10) // 设置重复次数
.repeatForever()) // 永远执行
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
Thread.sleep(300*1000);
} catch (SchedulerException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
这里需要注意
withRepeatCount()在调用时,如果指定执行10次,那么会执行11次。
如果设置了定时作业的结束时间,就可以不用考虑设置执行次数,只需要设置终止时间并将重复次数设置为
REPEAT_INDEFINITELY
CronTrigger通常比Simple Trigger更有用,如果您需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。
使用CronTrigger,您可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。
即使如此,和SimpleTrigger一样,CronTrigger有一个startTime,它指定何时生效,以及一个(可选的)endTime,用于指定何时停止计划。
Cron表达式由7位组成用空格分离,分别是秒、分、时、日、月、周、年。每一位都可以设置为频次、周期、循环、指定时间,灵活设定要执行的频率。
* 通配符,表示作业每秒执行一次;1-2 周期,表示从第1秒执行到第2秒,第一位为开始时间,第二位为结束时间;0/15 循环,表示从第0秒开始,每15秒执行一次,第一位为开始时间,第二位为多长时间执行一次;0,1,2 指定,表示每逢0秒,1秒,2秒时执行一次,将指定的时间点用,连接。* 通配符,表示作业每分种执行一次;1-2 周期,表示从第1分种执行到第2分种,第一位为开始时间,第二位为结束时间;0/15 循环,表示从第0分种开始,每15分种执行一次,第一位为开始时间,第二位为多长时间执行一次;0,1,2 指定,表示每逢0分,1分,2分时执行一次,将指定的时间点用,连接。* 通配符,表示作业每小时执行一次;1-2 周期,表示从1点开始执行到2点结束,第一位为开始时间,第二位为结束时间;0/1 循环,表示从0点开始,每1小时执行一次,第一位为开始时间,第二位为多长时间执行一次;0,1,2 指定,表示每逢0点,1点,2点时执行一次,将指定的时间点用,连接,采用24小时计时。* 通配符,表示作业每日执行一次;? 不指定;1-2 周期,表示从1号开始执行到2号结束,第一位为开始时间,第二位为结束时间;1/1 循环,表示从1号开始,每1天执行一次,第一位为开始时间,第二位为多长时间执行一次;15W 工作日,表示最近给定日期的工作日(星期一至星期五);L 每月的最后一天;1,2,3 指定,表示每逢1号,2号,3号时执行一次,将指定的时间点用,连接。* 通配符,表示作业每月执行一次;1-2 周期,表示从1月开始执行到2月结束,第一位为开始时间,第二位为结束时间;1/1 循环,表示从1月开始,每1个月执行一次,第一位为开始时间,第二位为多长时间执行一次;1,2,3 指定,表示每逢1月,2月,3月时执行一次,将指定的时间点用,连接。* 通配符,表示作业每周执行一次;? 不指定;1-2 周期,表示从星期一开始执行到星期二结束,第一位为开始时间,第二位为结束时间;1#2 指定周,表示本月的第2个星期一;1L 最后一个星期一,表示本月最后一个星期一,取值是1-7,超过7后进行循环;1,2 指定,表示每逢星期一、星期二执行一次,将指定的时间点用,连接。* 通配符,表示作业每年执行一次;? 不指定;2022-2023 周期,表示从2022年开始执行到2023年结束,第一位为开始时间,第二位为结束时间。具体可以使用一些在线的cron表达式编辑器设定。
@Test
public void testCronTriggerJob() throws Exception {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail jobDetail = JobBuilder.newJob(DumbJob.class)
.withIdentity("dumbJob", "group")
.usingJobData("username", "Tom")
.usingJobData("age", 18)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("dumbJob", "group")
// 设定每分钟的0秒时执行
.withSchedule(CronScheduleBuilder.cronSchedule("0 * * * * ? "))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
Thread.sleep(300*1000);
scheduler.shutdown();
}
如果你的trigger很多(或者Quartz线程池的工作线程太少),Quartz可能没有足够的资源同时触发所有的trigger;这种情况下,你可能希望控制哪些trigger优先使用Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性。比如,你有N个trigger需要同时触发,但只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值可以是任意整数,正数、负数都可以。
@Test
public void testPriorityJob() {
try {
ArrayList<Integer> arr = new ArrayList<>();
arr.add(11);
arr.add(12);
arr.add(13);
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("arr", arr);
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail jobDetail = JobBuilder.newJob(DumbAutoJob.class)
.withIdentity("dumbJob1", "group1")
.usingJobData("username", "Tom")
.usingJobData("age", 18)
.usingJobData(jobDataMap)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("dumbJob1", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
JobDetail jobDetail1 = JobBuilder.newJob(DumbAutoJob.class)
.withIdentity("dumbJob2", "group2")
.usingJobData("username", "Jack")
.usingJobData("age", 23)
.usingJobData(jobDataMap)
.build();
Trigger trigger1 = TriggerBuilder.newTrigger()
.withIdentity("dumbJob2", "group2")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.withPriority(10)
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.scheduleJob(jobDetail1, trigger1);
scheduler.start();
Thread.sleep(30*1000);
} catch (SchedulerException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
通过调用withPriorty()方法为触发器设置优先级,必须是同时执行的任务可以进行优先级比较,存在先后的顺序的任务不用比较优先级。
TriggerBuilder(以及Quartz的其它builder)会为那些没有被显式设置的属性选择合理的默认值。比如:如果你没有调用withIdentity(…)方法,TriggerBuilder会为trigger生成一个随机的名称;如果没有调用startAt(…)方法,则默认使用当前时间,即trigger立即生效。
Job监听器作用于作业执行过程中,用于处理作业执行前,执行后,未执行时的操作。创建监听器,只需要实现Quarte的JobListener类,这个类需要实现4个方法:
public class JobDetailListener implements JobListener {
@Override
public String getName() {
return "JobDetailListener";
}
/**
* 当JobDetail即将执行时(发生了关联的触发器),Scheduler调用。
*
* 如果作业的执行被TriggerListener拒绝,则不会调用此方法。
*
* @param jobExecutionContext 工作执行上下文
* @author DoGH
* @MethodName jobToBeExecuted
* @since 2022-08-10 15:00:06
*/
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
System.out.println("==========开始执行JobDetailListener==========");
}
/**
* 当JobDetail即将执行时(发生了关联的Trigger), Scheduler调用它,但是TriggerListener否决了它的执行。
*
* @param jobExecutionContext 工作执行上下文
* @author DoGH
* @MethodName jobExecutionVetoed
* @since 2022-08-10 15:00:36
*/
@Override
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
System.out.println("==========中断执行JobDetailListener==========");
}
/**
* 在执行JobDetail之后由Scheduler调用,并用于关联的Trigger的已触发方法被调用。
*
* @param jobExecutionContext 工作执行上下文
* @param e e
* @author DoGH
* @MethodName jobWasExecuted
* @since 2022-08-10 15:01:12
*/
@Override
public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
System.out.println("==========结束执行JobDetailListener==========");
}
}
创建测试类,调用addJobListener(JobListener jobListener, Matcher方法,为作业绑定监听器。
@Test
public void testJobListener() throws Exception {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail jobDetail1 = JobBuilder.newJob(DumbAutoJob.class)
.withIdentity("dumbJob1", "group1")
.usingJobData("username", "Tom")
.usingJobData("age", 18)
.build();
Trigger trigger1 = TriggerBuilder.newTrigger()
.withIdentity("dumbJob1", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ? "))
.build();
JobDetail jobDetail2 = JobBuilder.newJob(DumbAutoJob.class)
.withIdentity("dumbJob2", "group2")
.usingJobData("username", "Jack")
.usingJobData("age", 23)
.build();
Trigger trigger2 = TriggerBuilder.newTrigger()
.withIdentity("dumbJob2", "group2")
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ? "))
.build();
scheduler.scheduleJob(jobDetail1, trigger1);
scheduler.scheduleJob(jobDetail2, trigger2);
scheduler.getListenerManager().addJobListener(new JobDetailListener(), KeyMatcher.keyEquals(JobKey.jobKey("dumbJob1", "group1")));
scheduler.start();
Thread.sleep(30*1000);
scheduler.shutdown();
}
对于Match类可以调用
KeyMatcher.keyEquals()方法进行创建,获取对应的作业,其中jobKey()方法的参数为作业的名称和分组,若不存在分组可直接填写作业名称。
Trigger监听器主要作用域触发器的过程中,用于在触发前,触发后,是否进行触发的操作。创建监听器,实现Quarte的TriggerListener类:
public class JobTriggerListener implements TriggerListener {
@Override
public String getName() {
return "JobTriggerListener";
}
/**
* 当触发一个Trigger并且它关联的JobDetail即将被执行时,Scheduler会调用它。
*
* 它在该接口的vetoJobExecution(..)方法之前调用。
* @param trigger 触发
* @param jobExecutionContext 工作执行上下文
* @author DoGH
* @MethodName triggerFired
* @since 2022-08-10 16:03:58
*/
@Override
public void triggerFired(Trigger trigger, JobExecutionContext jobExecutionContext) {
System.out.println("===============开始执行JobTriggerListener===============");
}
/**
* 当触发一个Trigger并且它关联的JobDetail即将被执行时,Scheduler会调用它。如果实现拒绝执行(通过返回true),作业的execute方法将不会被调用。
*
* 它在该接口的triggerFired(..)方法之后调用。
* @param trigger 触发
* @param jobExecutionContext 工作执行上下文
* @return boolean
* @author DoGH
* @MethodName vetoJobExecution
* @since 2022-08-10 16:04:59
*/
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext jobExecutionContext) {
return (Boolean) jobExecutionContext.getJobDetail().getJobDataMap().get("status");
}
/**
* 当触发器错误触发时,由调度器调用。
*
* 应该考虑在此方法中花费的时间,因为它将影响所有失效的触发器。如果你有很多触发器同时失效,这可能是一个问题,因为这个方法做了很多。
* @param trigger 触发
* @author DoGH
* @MethodName triggerMisfired
* @since 2022-08-10 16:09:02
*/
@Override
public void triggerMisfired(Trigger trigger) {
}
/**
* 当触发一个Trigger时,Scheduler会调用它,它关联的JobDetail已经被执行,并且已经调用了它的Trigger (xx)方法。
*
* @param trigger 触发
* @param jobExecutionContext 工作执行上下文
* @param completedExecutionInstruction 完成执行指令
* @author DoGH
* @MethodName triggerComplete
* @since 2022-08-10 16:09:43
*/
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {
System.out.println("===============结束JobTriggerListener===============");
}
}
编写测试类,调用addTriggerListener()方法,为作业绑定监听器。
@Test
public void testJobTriggerListener() throws Exception {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobDetail jobDetail1 = JobBuilder.newJob(DumbJob.class)
.withIdentity("dumbJob1", "group1")
.usingJobData("username", "Tom")
.usingJobData("age", 18)
.usingJobData("status", true)
.build();
Trigger trigger1 = TriggerBuilder.newTrigger()
.withIdentity("dumbJob1", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ? "))
.build();
JobDetail jobDetail2 = JobBuilder.newJob(DumbJob.class)
.withIdentity("dumbJob2", "group2")
.usingJobData("username", "Jack")
.usingJobData("age", 23)
.usingJobData("status", false)
.build();
Trigger trigger2 = TriggerBuilder.newTrigger()
.withIdentity("dumbJob2", "group2")
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ? "))
.build();
scheduler.scheduleJob(jobDetail1, trigger1);
scheduler.scheduleJob(jobDetail2, trigger2);
scheduler.getListenerManager().addJobListener(new JobDetailListener(), KeyMatcher.keyEquals(JobKey.jobKey("dumbJob1", "group1")));
scheduler.getListenerManager().addTriggerListener(new JobTriggerListener(), KeyMatcher.keyEquals(TriggerKey.triggerKey("dumbJob1", "group1")));
scheduler.start();
Thread.sleep(30*1000);
scheduler.shutdown();
}
定义的作业可以同时绑定作业监听器和触发器监听器,触发器监听器可以控制作业是否执行,作业监听器可以根据是否被触发做出响应。
对于Match类可以调用
KeyMatcher.keyEquals()方法进行创建,获取对应的作业,其中triggerKey()方法的参数为触发器的名称和分组,若不存在分组可直接填写作业名称。
Schedule监听器主要用于任务调度过程中的监测,主要实现SchedulerListener类,该类需要实现20个方法,其中涉及作业、触发器、任务的启动、暂停、恢复的过程。例如:
public class JobSchedulerListener implements SchedulerListener {
/**
* 在调度JobDetail时由调度器调用
*
* @param trigger 触发
* @author DoGH
* @MethodName jobScheduled
* @since 2022-08-11 10:59:25
*/
@Override
public void jobScheduled(Trigger trigger) {
System.out.println("=============任务开始调用,jobKey:" + trigger.getJobKey().toString() + "=============");
}
/**
* 当JobDetail未计划时,由调度器调用
*
* @param triggerKey 触发关键
* @author DoGH
* @MethodName jobUnscheduled
* @since 2022-08-11 11:00:37
*/
@Override
public void jobUnscheduled(TriggerKey triggerKey) {
System.out.println("=============任务调度未计划,jobKey:" + triggerKey.toString() + "=============");
}
/**
* 当触发器达到不再触发的条件时,由调度器调用
*
* @param trigger 触发
* @author DoGH
* @MethodName triggerFinalized
* @since 2022-08-11 11:04:52
*/
@Override
public void triggerFinalized(Trigger trigger) {
System.out.println("=============触发器已停止,jobKey:" + trigger.getJobKey().toString() + "=============");
}
/**
* 在暂停触发器时由调度器调用
*
* @param triggerKey 触发关键
* @author DoGH
* @MethodName triggerPaused
* @since 2022-08-11 11:07:21
*/
@Override
public void triggerPaused(TriggerKey triggerKey) {
System.out.println("=============触发器已暂停,jobKey:" + triggerKey.toString() + "=============");
}
/**
* 在暂停一组触发器时由调度器调用
*
* @param s 触发器组名
* @author DoGH
* @MethodName triggersPaused
* @since 2022-08-11 11:12:57
*/
@Override
public void triggersPaused(String s) {
System.out.println("=============触发器组已暂停,groupName:" + s + "=============");
}
/**
* 在恢复作业时由调度器调用
*
* @param triggerKey 触发关键
* @author DoGH
* @MethodName triggerResumed
* @since 2022-08-11 11:15:14
*/
@Override
public void triggerResumed(TriggerKey triggerKey) {
System.out.println("=============触发器已恢复,jobKey:" + triggerKey.toString() + "=============");
}
/**
* 在恢复作业组时由调度器调用
*
* @param s 作业组名
* @author DoGH
* @MethodName triggersResumed
* @since 2022-08-11 11:19:38
*/
@Override
public void triggersResumed(String s) {
System.out.println("=============触发器组已恢复,groupName:" + s + "=============");
}
/**
* 当添加了JobDetail时,由调度器调用
*
* @param jobDetail 工作细节
* @author DoGH
* @MethodName jobAdded
* @since 2022-08-11 11:24:19
*/
@Override
public void jobAdded(JobDetail jobDetail) {
String jobKey = jobDetail.getKey().toString();
System.out.println("=============作业" + jobKey + "已加入调度=============");
}
/**
* 删除任务时执行
*
* @param jobKey 工作关键
* @author DoGH
* @MethodName jobDeleted
* @since 2022-08-11 11:28:24
*/
@Override
public void jobDeleted(JobKey jobKey) {
System.out.println("=============作业" + jobKey.toString() + "已被删除=============");
}
/**
* 暂停任务时执行
*
* @param jobKey 工作关键
* @author DoGH
* @MethodName jobPaused
* @since 2022-08-11 11:31:36
*/
@Override
public void jobPaused(JobKey jobKey) {
System.out.println("=============作业" + jobKey.toString() + "已暂停=============");
}
/**
* 暂停工作组时执行
*
* @param s 年代
* @author DoGH
* @MethodName jobsPaused
* @since 2022-08-11 11:32:04
*/
@Override
public void jobsPaused(String s) {
System.out.println("=============作业组" + s + "已暂停=============");
}
/**
* 恢复作业时执行
*
* @param jobKey 工作关键
* @author DoGH
* @MethodName jobResumed
* @since 2022-08-11 14:46:31
*/
@Override
public void jobResumed(JobKey jobKey) {
System.out.println("=============作业已恢复,jobKey" + jobKey.toString() + "=============");
}
/**
* 恢复作业组时执行
*
* @param s 年代
* @author DoGH
* @MethodName jobsResumed
* @since 2022-08-11 14:47:14
*/
@Override
public void jobsResumed(String s) {
System.out.println("=============作业组已恢复,groupName:" + s + "=============");
}
/**
* 当调度器中发生严重错误时——比如JobStore中重复出现错误,或者当Job实例的Trigger被触发时无法实例化Job实例时,调度器调用
*
* @param s 年代
* @param e e
* @author DoGH
* @MethodName schedulerError
* @since 2022-08-11 14:48:18
*/
@Override
public void schedulerError(String s, SchedulerException e) {
}
/**
* 由Scheduler调用,以通知侦听器它已进入待机模式
*
* @author DoGH
* @MethodName schedulerInStandbyMode
* @since 2022-08-11 14:51:01
*/
@Override
public void schedulerInStandbyMode() {
System.out.println("调度器进入待机模式");
}
/**
* 由调度器调用,以通知侦听器它已经启动
*
* @author DoGH
* @MethodName schedulerStarted
* @since 2022-08-11 14:51:30
*/
@Override
public void schedulerStarted() {
System.out.println("调度器已启动");
}
/**
* 由调度器调用,以通知侦听器它正在启动
*
* @author DoGH
* @MethodName schedulerStarting
* @since 2022-08-11 14:52:18
*/
@Override
public void schedulerStarting() {
System.out.println("调度器正在启动中");
}
/**
* 由调度器调用,通知侦听器它已经关闭
*
* @author DoGH
* @MethodName schedulerShutdown
* @since 2022-08-11 14:52:56
*/
@Override
public void schedulerShutdown() {
System.out.println("调度器已关闭");
}
/**
* 由调度器调用,通知侦听器它已经开始关闭序列
*
* @author DoGH
* @MethodName schedulerShuttingdown
* @since 2022-08-11 14:53:22
*/
@Override
public void schedulerShuttingdown() {
System.out.println("调度器正在关闭");
}
/**
* 由调度器调用,通知侦听器所有作业、触发器和日历都已删除
*
* @author DoGH
* @MethodName schedulingDataCleared
* @since 2022-08-11 14:53:43
*/
@Override
public void schedulingDataCleared() {
System.out.println("调度器已删除");
}
}
编写测试了,可用于测试作业启动到结束的过程。
@Test
public void testSchedulerListener() throws Exception {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("dumbJobTrigger", "group")
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ? ")
)
.build();
JobDetail jobDetail = JobBuilder.newJob(DumbJob.class)
.withIdentity("dumbJobDetail", "group")
.usingJobData("username", "Tom")
.usingJobData("age", 18)
.build();
scheduler.getListenerManager().addSchedulerListener(new JobSchedulerListener());
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
Thread.sleep(30*1000);
// 暂停任务
scheduler.pauseTrigger(TriggerKey.triggerKey("dumbJobTrigger", "group"));
scheduler.pauseJob(JobKey.jobKey("dumbJobDetail", "group"));
Thread.sleep(30*1000);
// 恢复任务
scheduler.resumeJob(JobKey.jobKey("dumbJobDetail", "group"));
scheduler.resumeTrigger(TriggerKey.triggerKey("dumbJobTrigger", "group"));
Thread.sleep(30*1000);
scheduler.shutdown();
}
调用addSchedulerListener()方法,为调度任务绑定监听器,这里需要注意的是,绑定监听器一定要在绑定作业之前,才能触发jobAdded()的监听方法。
根据运行结果可以看出整个任务的运行情况。



当调用
pauseJob()方法暂停调度任务后,当该任务恢复后,会先执行未完成的任务后继续调度任务。
对于triggerFinalized()方法,可以编写一个执行有限次数的任务,当执行结束后,就会调用triggerFinalized()方法和jobDeleted()方法。例如:
@Test
public void testSchedulerListener() throws Exception {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("dumbJobTrigger", "group")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withRepeatCount(3)
.withIntervalInSeconds(5))
.build();
JobDetail jobDetail = JobBuilder.newJob(DumbJob.class)
.withIdentity("dumbJobDetail", "group")
.usingJobData("username", "Tom")
.usingJobData("age", 18)
.build();
scheduler.getListenerManager().addSchedulerListener(new JobSchedulerListener());
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
Thread.sleep(30*1000);
scheduler.pauseTrigger(TriggerKey.triggerKey("dumbJobTrigger", "group"));
scheduler.pauseJob(JobKey.jobKey("dumbJobDetail", "group"));
Thread.sleep(30*1000);
scheduler.shutdown();
}
运行结果如下:



当作业被移除时,就会调用jobDeleted()方法和jobUnscheduled()方法。例如:
@Test
public void testSchedulerListener() throws Exception {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("dumbJobTrigger", "group")
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ? "))
.build();
JobDetail jobDetail = JobBuilder.newJob(DumbJob.class)
.withIdentity("dumbJobDetail", "group")
.usingJobData("username", "Tom")
.usingJobData("age", 18)
.build();
scheduler.getListenerManager().addSchedulerListener(new JobSchedulerListener());
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
Thread.sleep(30*1000);
scheduler.deleteJob(JobKey.jobKey("dumbJobDetail", "group"));
Thread.sleep(30*1000);
scheduler.shutdown();
}
运行结果如下:

JobStore负责跟踪您提供给调度程序的所有“工作数据”:jobs,triggers,日历等。为您的Quartz调度程序实例选择适当的JobStore是重要的一步。幸运的是,一旦你明白他们之间的差异,那么选择应该是一个非常简单的选择。
您声明您提供给用于生成调度程序实例的SchedulerFactory的属性文件(或对象)中您的调度程序应使用哪个JobStore(以及它的配置设置)。
切勿在代码中直接使用JobStore实例。由于某种原因,许多人试图这样做。JobStore用于Quartz本身的幕后使用。你必须告诉Quartz(通过配置)使用哪个JobStore,但是你应该只在代码中使用Scheduler界面。
RAMJobStore是使用最简单的JobStore,它也是性能最高的(在CPU时间方面)。RAMJobStore以其明显的方式获取其名称:它将其所有数据保存在RAM中。这就是为什么它是闪电般快的,也是为什么这么简单的配置。缺点是当您的应用程序结束(或崩溃)时,所有调度信息都将丢失 - 这意味着RAMJobStore无法履行作业和triggers上的“非易失性”设置。对于某些应用程序,这是可以接受的 - 甚至是所需的行为,但对于其他应用程序,这可能是灾难性的。
JDBCJobStore也被恰当地命名 - 它通过JDBC将其所有数据保存在数据库中。因此,配置比RAMJobStore要复杂一点,而且也不是那么快。但是,性能下降并不是很糟糕,特别是如果您在主键上构建具有索引的数据库表。在相当现代的一套具有体面的LAN(在调度程序和数据库之间)的机器上,检索和更新触发triggers的时间通常将小于10毫秒。
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 来管理事务(这是最常见的选择)。
简述数据表的作用:
| 表名 | 说明 |
|---|---|
| QRTZ_CALENDARS | 存储Quartz日历信息 |
| QRTZ_CRON_TRIGGERS | 存放Cron类型的Trigger,包括Cron表达式和时区信息 |
| QRTZ_FIRED_TRIGGERS | 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息 |
| QRTZ_PAUSED_TRIGGER_GRPS | 存储已暂停的Trigger组的信息 |
| QRTZ_SCHEDULER_STATE | 存储少量的Scheduler相关的状态信息 |
| QRTZ_LOCKS | 存储锁信息,为多个节点调度提供分布式锁,实现分布式调度,默认有2个锁: STATE_ACCESS, TRIGGER_ACCESS |
| QRTZ_JOB_DETAILS | 存储每一个已配置的JobDetail信息 |
| QRTZ_SIMPLE_TRIGGERS | 存储Simple类型的Trigger,包括重复次数、间隔、以及已触的次数 |
| QRTZ_BLOG_TRIGGERS | 以Blob类型存储的Trigger |
| QRTZ_TRIGGERS | 存储已配置的Trigger的基本信息 |
| QRTZ_SIMPROP_TRIGGERS | 存储CalendarIntervalTrigger和DailyTimeIntervalTrigger两种类型的触发器 |
具体的脚本可以从官网下载的文档中找到,文件位置在
src\org\quartz\impl\jdbcjobstore,取用的quartz-2.3.0-SNAPSHOT版本。
quartz.properties包含一些作业的配置,具体可以查看官方文档的描述。在quartz包中,包含一个quartz.properties文件,其中设置了一下默认的配置。当创建Scheduler时如果不指定该文件,就会调用jar包内的默认配置,如果需要配置其他的内容就需要指定自定义的文件。
这里可以查看StdSchedulerFactory()的源码:
public class StdSchedulerFactory implements SchedulerFactory {
public StdSchedulerFactory() {
}
public StdSchedulerFactory(Properties props) throws SchedulerException {
this.initialize(props);
}
public StdSchedulerFactory(String fileName) throws SchedulerException {
this.initialize(fileName);
}
public void initialize() throws SchedulerException {
if (this.cfg == null) {
if (this.initException != null) {
throw this.initException;
} else {
String requestedFile = System.getProperty("org.quartz.properties");
String propFileName = requestedFile != null ? requestedFile : "quartz.properties";
File propFile = new File(propFileName);
Properties props = new Properties();
Object in = null;
try {
if (propFile.exists()) {
try {
if (requestedFile != null) {
this.propSrc = "specified file: '" + requestedFile + "'";
} else {
this.propSrc = "default file in current working dir: 'quartz.properties'";
}
in = new BufferedInputStream(new FileInputStream(propFileName));
props.load((InputStream)in);
} catch (IOException var19) {
this.initException = new SchedulerException("Properties file: '" + propFileName + "' could not be read.", var19);
throw this.initException;
}
} else if (requestedFile != null) {
in = Thread.currentThread().getContextClassLoader().getResourceAsStream(requestedFile);
if (in == null) {
this.initException = new SchedulerException("Properties file: '" + requestedFile + "' could not be found.");
throw this.initException;
}
this.propSrc = "specified file: '" + requestedFile + "' in the class resource path.";
in = new BufferedInputStream((InputStream)in);
try {
props.load((InputStream)in);
} catch (IOException var18) {
this.initException = new SchedulerException("Properties file: '" + requestedFile + "' could not be read.", var18);
throw this.initException;
}
} else {
this.propSrc = "default resource file in Quartz package: 'quartz.properties'";
ClassLoader cl = this.getClass().getClassLoader();
if (cl == null) {
cl = this.findClassloader();
}
if (cl == null) {
throw new SchedulerConfigException("Unable to find a class loader on the current thread or class.");
}
in = cl.getResourceAsStream("quartz.properties");
if (in == null) {
in = cl.getResourceAsStream("/quartz.properties");
}
if (in == null) {
in = cl.getResourceAsStream("org/quartz/quartz.properties");
}
if (in == null) {
this.initException = new SchedulerException("Default quartz.properties not found in class path");
throw this.initException;
}
try {
props.load((InputStream)in);
} catch (IOException var17) {
this.initException = new SchedulerException("Resource properties file: 'org/quartz/quartz.properties' could not be read from the classpath.", var17);
throw this.initException;
}
}
} finally {
if (in != null) {
try {
((InputStream)in).close();
} catch (IOException var16) {
}
}
}
this.initialize(overrideWithSysProps(props, this.getLog()));
}
}
}
public void initialize(String filename) throws SchedulerException {
if (this.cfg == null) {
if (this.initException != null) {
throw this.initException;
} else {
InputStream is = null;
Properties props = new Properties();
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename);
try {
if (is != null) {
is = new BufferedInputStream((InputStream)is);
this.propSrc = "the specified file : '" + filename + "' from the class resource path.";
} else {
is = new BufferedInputStream(new FileInputStream(filename));
this.propSrc = "the specified file : '" + filename + "'";
}
props.load((InputStream)is);
} catch (IOException var12) {
this.initException = new SchedulerException("Properties file: '" + filename + "' could not be read.", var12);
throw this.initException;
} finally {
if (is != null) {
try {
((InputStream)is).close();
} catch (IOException var11) {
}
}
}
this.initialize(props);
}
}
}
public Scheduler getScheduler() throws SchedulerException {
if (this.cfg == null) {
this.initialize();
}
SchedulerRepository schedRep = SchedulerRepository.getInstance();
Scheduler sched = schedRep.lookup(this.getSchedulerName());
if (sched != null) {
if (!sched.isShutdown()) {
return sched;
}
schedRep.remove(this.getSchedulerName());
}
sched = this.instantiate();
return sched;
}
}
可以从源码看出在创建StdSchedulerFactory()方法时,会调用Initialize()方法,可以传入quartz.properties文件的名称,或者创建一个Properties类,将需要的添加的配置增加到Properties再传入initialize()方法。示例:
@Test
public void testQuartzPropertiesJob() throws Exception {
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
Properties properties = new Properties();
// 分配线程池
properties.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
// 设置线程数
properties.put("org.quartz.threadPool.threadCount", "-1");
stdSchedulerFactory.initialize(properties);
Scheduler scheduler = stdSchedulerFactory.getScheduler();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("dumbJobTrigger", "group")
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ? "))
.build();
JobDetail jobDetail = JobBuilder.newJob(DumbJob.class)
.withIdentity("dumbJobDetail", "group")
.usingJobData("username", "Tom")
.usingJobData("age", 18)
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
Thread.sleep(30*1000);
scheduler.shutdown(false);
}
线程数需要设置大于0的正整数,所以上述示例会直接报错。运行结果如下:

在添加配置信息时,可以使用
StdSchedulerFactory的常量设置属性。
public class StdSchedulerFactory implements SchedulerFactory {
public static final String PROPERTIES_FILE = "org.quartz.properties";
public static final String PROP_SCHED_INSTANCE_NAME = "org.quartz.scheduler.instanceName";
public static final String PROP_SCHED_INSTANCE_ID = "org.quartz.scheduler.instanceId";
public static final String PROP_SCHED_INSTANCE_ID_GENERATOR_PREFIX = "org.quartz.scheduler.instanceIdGenerator";
public static final String PROP_SCHED_INSTANCE_ID_GENERATOR_CLASS = "org.quartz.scheduler.instanceIdGenerator.class";
public static final String PROP_SCHED_THREAD_NAME = "org.quartz.scheduler.threadName";
public static final String PROP_SCHED_BATCH_TIME_WINDOW = "org.quartz.scheduler.batchTriggerAcquisitionFireAheadTimeWindow";
public static final String PROP_SCHED_MAX_BATCH_SIZE = "org.quartz.scheduler.batchTriggerAcquisitionMaxCount";
public static final String PROP_SCHED_JMX_EXPORT = "org.quartz.scheduler.jmx.export";
public static final String PROP_SCHED_JMX_OBJECT_NAME = "org.quartz.scheduler.jmx.objectName";
public static final String PROP_SCHED_JMX_PROXY = "org.quartz.scheduler.jmx.proxy";
public static final String PROP_SCHED_JMX_PROXY_CLASS = "org.quartz.scheduler.jmx.proxy.class";
public static final String PROP_SCHED_RMI_EXPORT = "org.quartz.scheduler.rmi.export";
public static final String PROP_SCHED_RMI_PROXY = "org.quartz.scheduler.rmi.proxy";
public static final String PROP_SCHED_RMI_HOST = "org.quartz.scheduler.rmi.registryHost";
public static final String PROP_SCHED_RMI_PORT = "org.quartz.scheduler.rmi.registryPort";
public static final String PROP_SCHED_RMI_SERVER_PORT = "org.quartz.scheduler.rmi.serverPort";
public static final String PROP_SCHED_RMI_CREATE_REGISTRY = "org.quartz.scheduler.rmi.createRegistry";
public static final String PROP_SCHED_RMI_BIND_NAME = "org.quartz.scheduler.rmi.bindName";
public static final String PROP_SCHED_WRAP_JOB_IN_USER_TX = "org.quartz.scheduler.wrapJobExecutionInUserTransaction";
public static final String PROP_SCHED_USER_TX_URL = "org.quartz.scheduler.userTransactionURL";
public static final String PROP_SCHED_IDLE_WAIT_TIME = "org.quartz.scheduler.idleWaitTime";
public static final String PROP_SCHED_DB_FAILURE_RETRY_INTERVAL = "org.quartz.scheduler.dbFailureRetryInterval";
public static final String PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON = "org.quartz.scheduler.makeSchedulerThreadDaemon";
public static final String PROP_SCHED_SCHEDULER_THREADS_INHERIT_CONTEXT_CLASS_LOADER_OF_INITIALIZING_THREAD = "org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer";
public static final String PROP_SCHED_CLASS_LOAD_HELPER_CLASS = "org.quartz.scheduler.classLoadHelper.class";
public static final String PROP_SCHED_JOB_FACTORY_CLASS = "org.quartz.scheduler.jobFactory.class";
public static final String PROP_SCHED_JOB_FACTORY_PREFIX = "org.quartz.scheduler.jobFactory";
public static final String PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN = "org.quartz.scheduler.interruptJobsOnShutdown";
public static final String PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN_WITH_WAIT = "org.quartz.scheduler.interruptJobsOnShutdownWithWait";
public static final String PROP_SCHED_CONTEXT_PREFIX = "org.quartz.context.key";
public static final String PROP_THREAD_POOL_PREFIX = "org.quartz.threadPool";
public static final String PROP_THREAD_POOL_CLASS = "org.quartz.threadPool.class";
public static final String PROP_JOB_STORE_PREFIX = "org.quartz.jobStore";
public static final String PROP_JOB_STORE_LOCK_HANDLER_PREFIX = "org.quartz.jobStore.lockHandler";
public static final String PROP_JOB_STORE_LOCK_HANDLER_CLASS = "org.quartz.jobStore.lockHandler.class";
public static final String PROP_TABLE_PREFIX = "tablePrefix";
public static final String PROP_SCHED_NAME = "schedName";
public static final String PROP_JOB_STORE_CLASS = "org.quartz.jobStore.class";
public static final String PROP_JOB_STORE_USE_PROP = "org.quartz.jobStore.useProperties";
public static final String PROP_DATASOURCE_PREFIX = "org.quartz.dataSource";
public static final String PROP_CONNECTION_PROVIDER_CLASS = "connectionProvider.class";
}
参考文件:
w3cschool Quartz教程
Quartz官方文档