版权声明:本文由吴仙杰创做整理,转载请注明出处:http://www.javashuo.com/article/p-xsencvmg-y.htmlhtml
Quartz 设计有三个核心类,分别是 Scheduler(调度器)Job(任务)和 Trigger (触发器),它们是咱们使用 Quartz 的关键。java
1)Job:定义须要执行的任务。该类是一个接口,只定义一个方法 execute(JobExecutionContext context)
,在实现类的 execute
方法中编写所须要定时执行的 Job(任务), JobExecutionContext
类提供了调度应用的一些信息。Job 运行时的信息保存在 JobDataMap 实例中。shell
2)Trigger:负责设置调度策略。该类是一个接口,描述触发 job 执行的时间触发规则。主要有 SimpleTrigger 和 CronTrigger 这两个子类。当且仅当需调度一次或者以固定时间间隔周期执行调度,SimpleTrigger 是最适合的选择;而 CronTrigger 则能够经过 Cron 表达式定义出各类复杂时间规则的调度方案:如工做日周一到周五的 15:00~16:00 执行调度等。segmentfault
3)Scheduler:调度器就至关于一个容器,装载着任务和触发器。该类是一个接口,表明一个 Quartz 的独立运行容器, Trigger 和 JobDetail 能够注册到 Scheduler 中, 二者在 Scheduler 中拥有各自的组及名称, 组及名称是 Scheduler 查找定位容器中某一对象的依据, Trigger 的组及名称必须惟一, JobDetail 的组和名称也必须惟一(但能够和 Trigger 的组和名称相同,由于它们是不一样类型的)。Scheduler 定义了多个接口方法, 容许外部经过组及名称访问和控制容器中 Trigger 和 JobDetail。并发
Scheduler 能够将 Trigger 绑定到某一 JobDetail 中, 这样当 Trigger 触发时, 对应的 Job 就被执行。一个 Job 能够对应多个 Trigger, 但一个 Trigger 只能对应一个 Job。能够经过 SchedulerFactory 建立一个 SchedulerFactory 实例。Scheduler 拥有一个 SchedulerContext,它相似于 SchedulerContext,保存着 Scheduler 上下文信息,Job 和 Trigger 均可以访问 SchedulerContext 内的信息。SchedulerContext 内部经过一个 Map,以键值对的方式维护这些上下文数据,SchedulerContext 为保存和获取数据提供了多个 put()
和 getXxx()
的方法。能够经过 Scheduler#getContext()
获取对应的 SchedulerContext 实例。ide
4)JobDetail:描述 Job 的实现类及其它相关的静态信息,如:Job 名字、描述、关联监听器等信息。Quartz 每次调度 Job 时, 都从新建立一个 Job 实例, 因此它不直接接受一个 Job 的实例,相反它接收一个 Job 实现类,以便运行时经过 newInstance()
的反射机制实例化 Job。ui
5)ThreadPool:Scheduler 使用一个线程池做为任务运行的基础设施,任务经过共享线程池中的线程提升运行效率。.net
Job 有一个 StatefulJob 子接口(Quartz 2 后用 @PersistJobDataAfterExecution
注解代替),表明有状态的任务,该接口是一个没有方法的标签接口,其目的是让 Quartz 知道任务的类型,以便采用不一样的执行方案。线程
无状态任务在执行时拥有本身的 JobDataMap 拷贝,对 JobDataMap 的更改不会影响下次的执行。设计
有状态任务共享同一个 JobDataMap 实例,每次任务执行对 JobDataMap 所作的更改会保存下来,后面的执行能够看到这个更改,也即每次执行任务后都会对后面的执行发生影响。
正由于这个缘由,无状态的 Job 能并发执行,而有状态的 StatefulJob 不能并发执行。这意味着若是前次的 StatefulJob 尚未执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务须要考虑更多的因素,程序每每拥有更高的复杂度,所以除非必要,应该尽可能使用无状态的 Job。
6)Listener:Quartz 拥有完善的事件和监听体系,大部分组件都拥有事件,如:JobListener 监放任务执行前事件、任务执行后事件;TriggerListener 监听触发器触发前事件、触发后事件;TriggerListener 监听调度器开始事件、关闭事件等等,能够注册相应的监听器处理感兴趣的事件。
使用 Quartz 进行任务调度:
package org.quartz.examples; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import java.util.ArrayList; import java.util.Date; import java.util.List; public class QuartzTest implements Job { /** * Quartz requires a public empty constructor so that the * scheduler can instantiate the class whenever it needs. */ public QuartzTest() { } /** * 该方法实现须要执行的任务 */ @SuppressWarnings("unchecked") @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 从 context 中获取 instName, groupName 以及 dataMap String instName = context.getJobDetail().getKey().getName(); String groupName = context.getJobDetail().getKey().getGroup(); JobDataMap dataMap = context.getJobDetail().getJobDataMap(); // 从 dataMap 中获取 myDescription, myValue 以及 myArray String myDescription = dataMap.getString("myDescription"); int myValue = dataMap.getInt("myValue"); List<String> myArray = (List<String>) dataMap.get("myArray"); System.out.println("---> Instance = " + instName + ", group = " + groupName + ", description = " + myDescription + ", value =" + myValue + ", array item[0] = " + myArray.get(0)); System.out.println("Runtime: " + new Date().toString() + " <---"); } public static void main(String[] args) throws SchedulerException, InterruptedException { // 经过 schedulerFactory 获取一个调度器 SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); // 建立 jobDetail 实例,绑定 Job 实现类 // 指明 job 的名称,所在组的名称,以及绑定 job 类 JobDetail job = JobBuilder.newJob(QuartzTest.class).withIdentity("job1", "group1").build(); // 定义调度触发规则 // SimpleTrigger,从当前时间的下 1 秒开始,每隔 1 秒执行 1 次,重复执行 2 次 /*Trigger trigger = TriggerBuilder.newTrigger() // 指明 trigger 的 name 和 group .withIdentity("trigger1", "group1") // 从当前时间的下 1 秒开始执行,默认为当即开始执行(.startNow()) .startAt(DateBuilder.evenSecondDate(new Date())) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1) // 每隔 1 秒执行 1 次 .withRepeatCount(2)) // 重复执行 2 次,一共执行 3 次 .build();*/ // corn 表达式,先当即执行一次,而后每隔 5 秒执行 1 次 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?")) .build(); // 初始化参数传递到 job job.getJobDataMap().put("myDescription", "Hello Quartz"); job.getJobDataMap().put("myValue", 1990); List<String> list = new ArrayList<>(); list.add("firstItem"); job.getJobDataMap().put("myArray", list); // 把做业和触发器注册到任务调度中 sched.scheduleJob(job, trigger); // 启动计划程序(实际上直到调度器已经启动才会开始运行) sched.start(); // 等待 10 秒,使咱们的 job 有机会执行 Thread.sleep(10000); // 等待做业执行完成时才关闭调度器 sched.shutdown(true); } }
运行结果(设置了 sleep 10 秒,故在 0 秒调度一次,5 秒调度一次, 10 秒调度最后一次):
---> Instance = job1, group = group1, description = Hello Quartz, value =1990, array item[0] = firstItem Runtime: Wed Apr 19 11:24:15 CST 2017 <--- ---> Instance = job1, group = group1, description = Hello Quartz, value =1990, array item[0] = firstItem Runtime: Wed Apr 19 11:24:20 CST 2017 <--- ---> Instance = job1, group = group1, description = Hello Quartz, value =1990, array item[0] = firstItem Runtime: Wed Apr 19 11:24:25 CST 2017 <---
格式:[秒] [分] [时] [每个月的第几日] [月] [每周的第几日] [年]
字段名 | 必须的 | 容许值 | 容许的特殊字符 |
---|---|---|---|
Seconds | YES | 0-59 | , - * / |
Minutes | YES | 0-59 | , - * / |
Hours | YES | 0-23 | , - * / |
Day of month | YES | 1-31 | , - * ? / L W |
Month | YES | 1-12 or JAN-DEC | , - * / |
Day of week | YES | 1-7 or SUN-SAT | , - * ? / L # |
Year | NO | empty, 1970-2099 | , - * / |
特殊字符说明:
字符 | 含义 |
---|---|
* |
用于 指定字段中的全部值 。好比:* 在分钟中表示 每一分钟 。 |
? |
用于 指定日期中的某一天 ,或是 星期中的某一个星期 。 |
- |
用于 指定范围 。好比:10-12 在小时中表示 10 点,11 点,12 点 。 |
, |
用于 指定额外的值 。好比:MON,WED,FRI 在日期中表示 星期一, 星期三, 星期五 。 |
/ |
用于 指定增量 。好比:0/15 在秒中表示 0 秒, 15 秒, 30 秒, 45 秒 。5/15 在秒中表示 5 秒,20 秒,35 秒,50 秒 。 |
L |
在两个字段中拥有不一样的含义。好比:L 在日期(Day of month)表示 某月的最后一天 。在星期(Day of week)只表示 7 或 SAT 。可是,值L 在星期(Day of week)中表示 某月的最后一个星期几 。 好比:6L 表示 某月的最后一个星期五 。也能够在日期(Day of month)中指定一个偏移量(从该月的最后一天开始).好比:L-3 表示 某月的倒数第三天 。 |
W |
用于指定工做日(星期一到星期五)好比:15W 在日期中表示 到 15 号的最近一个工做日 。若是第十五号是周六, 那么触发器的触发在 第十四号星期五 。若是第十五号是星期日,触发器的触发在 第十六号周一 。若是第十五是星期二,那么它就会工做开始在 第十五号周二 。然而,若是指定 1W 而且第一号是星期六,那么触发器的触发在第三号周一,由于它不会 "jump" 过一个月的日子的边界。 |
L 和 W |
能够在日期(day-of-month)合使用,表示 月份的最后一个工做日 。 |
# |
用于 指定月份中的第几天 。好比:6#3 表示 月份的第三个星期五 (day 6 = Friday and "#3" = the 3rd one in the month)。其它的有,2#1 表示 月份第一个星期一 。4#5 表示 月份第五个星期三 。注意: 若是只是指定 #5 ,则触发器在月份中不会触发。 |
注意:字符不区分大小写,MON
和 mon
相同。
表达式 | 含义 |
---|---|
0 0 12 * * ? |
天天中午 12 点 |
0 15 10 ? * * |
天天上午 10 点 15 分 |
0 15 10 * * ? |
天天上午 10 点 15 分 |
0 15 10 * * ? * |
天天上午 10 点 15 分 |
0 15 10 * * ? 2005 |
在 2005 年里的天天上午 10 点 15 分 |
0 * 14 * * ? |
天天下午 2 点到下午 2 点 59 分的每一分钟 |
0 0/5 14 * * ? |
天天下午 2 点到 2 点 55 分每隔 5 分钟 |
0 0/5 14,18 * * ? |
天天下午 2 点到 2 点 55 分, 下午 6 点到 6 点 55 分, 每隔 5 分钟 |
0 0-5 14 * * ? |
天天下午 2 点到 2 点 5 分的每一分钟 |
0 10,44 14 ? 3 WED |
3 月每周三的下午 2 点 10 分和下午 2 点 44 分 |
0 15 10 ? * MON-FRI |
每周一到周五的上午 10 点 15 分 |
0 15 10 15 * ? |
每个月 15 号的上午 10 点 15 分 |
0 15 10 L * ? |
每个月最后一天的上午 10 点 15 分 |
0 15 10 L-2 * ? |
每个月最后两天的上午10点15分 |
0 15 10 ? * 6L |
每个月的最后一个星期五的上午 10 点 15 分 |
0 15 10 ? * 6L 2002-2005 |
2002 年到 2005 年每月的最后一个星期五的上午 10 点 15 分 |
0 15 10 ? * 6#3 |
每个月的第三个星期五的上午 10 点 15 分 |
0 0 12 1/5 * ? |
每个月的 1 号开始每隔 5 天的中午 12 点 |
0 11 11 11 11 ? |
每一年 11 月 11 号上午 11 点 11 分 |
监听器在运行时将其注册到调度程序中,而且必须给出一个名称(或者,他们必须经过他们的 getName()
来宣传本身的名称)。
侦听器与调度程序的 ListenerManager 一块儿注册,而且描述了监听器想要接收事件的做业/触发器的 Matcher。
1)注册对特定做业的 JobListener:
sched.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("job1", "group1")));
2)注册对特定组的全部做业的 JobListener:
sched.getListenerManager().addJobListener(new MyJobListener(), GroupMatcher.jobGroupEquals("group1"));
3)注册对两个特定组的全部做业的 JobListener:
sched.getListenerManager().addJobListener(new MyJobListener(), OrMatcher.or(GroupMatcher.jobGroupEquals("group1"), GroupMatcher.jobGroupEquals("group2")));
4)注册一个对全部做业的 JobListener:
sched.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
JobListener 实现类:
package org.quartz.examples; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener; import org.quartz.SchedulerException; public class MyJobListener implements JobListener { @Override public String getName() { return "MyJobListener"; // 必定要设置名称 } @Override public void jobToBeExecuted(JobExecutionContext jobExecutionContext) { } @Override public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) { } @Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { if (jobException != null) { try { // 当即关闭调度器 context.getScheduler().shutdown(); System.out.println("Error occurs when executing jobs, shut down the scheduler."); // 给管理员发送邮件... } catch (SchedulerException e) { e.printStackTrace(); } } } }
Job 实现类:
package org.quartz.examples; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.KeyMatcher; import java.util.ArrayList; import java.util.Date; import java.util.List; public class QuartzTest implements Job { /** * Quartz requires a public empty constructor so that the * scheduler can instantiate the class whenever it needs. */ public QuartzTest() { } /** * 该方法实现须要执行的任务 */ @SuppressWarnings("unchecked") @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 故意运行异常,观察监听器是否正常工做 int i = 1/0; // 从 context 中获取 instName, groupName 以及 dataMap String instName = context.getJobDetail().getKey().getName(); String groupName = context.getJobDetail().getKey().getGroup(); JobDataMap dataMap = context.getJobDetail().getJobDataMap(); // 从 dataMap 中获取 myDescription, myValue 以及 myArray String myDescription = dataMap.getString("myDescription"); int myValue = dataMap.getInt("myValue"); List<String> myArray = (List<String>) dataMap.get("myArray"); System.out.println("---> Instance = " + instName + ", group = " + groupName + ", description = " + myDescription + ", value =" + myValue + ", array item[0] = " + myArray.get(0)); System.out.println("Runtime: " + new Date().toString() + " <---"); } public static void main(String[] args) throws SchedulerException, InterruptedException { // 经过 schedulerFactory 获取一个调度器 SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler(); // 建立 jobDetail 实例,绑定 Job 实现类 // 指明 job 的名称,所在组的名称,以及绑定 job 类 JobDetail job = JobBuilder.newJob(QuartzTest.class).withIdentity("job1", "group1").build(); // 定义调度触发规则 // SimpleTrigger,从当前时间的下 1 秒开始,每隔 1 秒执行 1 次,重复执行 2 次 /*Trigger trigger = TriggerBuilder.newTrigger() // 指明 trigger 的 name 和 group .withIdentity("trigger1", "group1") // 从当前时间的下 1 秒开始执行,默认为当即开始执行(.startNow()) .startAt(DateBuilder.evenSecondDate(new Date())) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1) // 每隔 1 秒执行 1 次 .withRepeatCount(2)) // 重复执行 2 次,一共执行 3 次 .build();*/ // corn 表达式,先当即执行 1 次,而后每隔 5 秒执行 1 次 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .withSchedule(CronScheduleBuilder.cronSchedule("*/5 * * * * ?")) .build(); // 初始化参数传递到 job job.getJobDataMap().put("myDescription", "Hello Quartz"); job.getJobDataMap().put("myValue", 1990); List<String> list = new ArrayList<>(); list.add("firstItem"); job.getJobDataMap().put("myArray", list); // 注册对特定做业的监听器 sched.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("job1", "group1"))); // 把做业和触发器注册到任务调度中 sched.scheduleJob(job, trigger); // 启动计划程序(实际上直到调度器已经启动才会开始运行) sched.start(); // 等待 10 秒,使咱们的 job 有机会执行 Thread.sleep(10000); // 等待做业执行完成时才关闭调度器 sched.shutdown(true); } }
运行结果:
[ERROR] 19 四月 11:54:35.361 上午 DefaultQuartzScheduler_Worker-1 [org.quartz.core.JobRunShell] Job group1.job1 threw an unhandled Exception: java.lang.ArithmeticException: / by zero at org.quartz.examples.QuartzTest.execute(QuartzTest.java:27) at org.quartz.core.JobRunShell.run(JobRunShell.java:202) at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [ERROR] 19 四月 11:54:35.361 上午 DefaultQuartzScheduler_Worker-1 [org.quartz.core.ErrorLogger] Job (group1.job1 threw an exception. org.quartz.SchedulerException: Job threw an unhandled exception. [See nested exception: java.lang.ArithmeticException: / by zero] at org.quartz.core.JobRunShell.run(JobRunShell.java:213) at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) Caused by: java.lang.ArithmeticException: / by zero at org.quartz.examples.QuartzTest.execute(QuartzTest.java:27) at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ... 1 more Error occurs when executing jobs, shut down the scheduler.
...注册 TriggerListener 的工做原理相同。
SchedulerListener 在调度程序的 SchedulerListener 中注册。SchedulerListener 几乎能够实现任何实现 org.quartz.SchedulerListener
接口的对象。
注册对添加调度器时的 SchedulerListener:
scheduler.getListenerManager().addSchedulerListener(mySchedListener);
注册对删除调度器时的 SchedulerListener
:
scheduler.getListenerManager().removeSchedulerListener(mySchedListener);
PS:本文针对的 Quartz 版本为 Quartz 2.2.3。官方下载地址:Quartz 2.2.3 .tar.gz