Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。Quartz是一个完全由java编写的开源作业调度框架。不要让作业调度这个术语吓着你。尽管Quartz框架整合了许多额外功能, 但就其简易形式看,你会发现它易用得简直让人受不了!。Quartz框架的核心是调度器。调度器负责管理Quartz应用运行时环境。Quartz不仅仅是线程和线程管理。Quartz依赖一套松耦合的线程池管理部件来管理线程环境。本篇文章中,我们会多次提到线程池管理,但Quartz里面的每个对象是可配置的或者是可定制的。例如你想要插进自己线程池管理设施,我猜你一定能!Quartz框架有一个丰富的特征集。事实上,Quartz有太多特性以致不能在一种情况中全部领会,但没时间在此详细讨论。Quartz经常会用到cron表达式,可以使用国外网站cronmaker辅助生成cron表达式。
Quartz是基于Java线程池实现的。
四个主要部件
1、Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context)2、JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
3、Trigger 代表一个调度参数的配置,什么时候去调。
4、Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
下面演示Quartz编写的步骤。
Gradle依赖库:
// https://mvnrepository.com/artifact/org.quartz-scheduler/quartz compile group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.1'1、 定义Job类
public class HelloJob implements Job { public HelloJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("Hello World! - " + new Date()); } }2、创建调度器
SchedulerFactory sf = new StdSchedulerFactory(); Scheduler sched = sf.getScheduler();3、创建JobDetail或者说实例化Job
Date runTime = evenMinuteDate(new Date()); JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();4、创建调度逻辑
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();5、组合Job和trigger,调度器启动,调度器关闭。
sched.scheduleJob(job, trigger); sched.start(); sched.shutdown(true);Quartz的使用步骤都是这样的,只不过在不同的应用场景下使用的调度逻辑和Job要变化。
这两个注解是控制Job类的并发状态的,可以影响 Quartz 的行为。
@DisallowConcurrentExecution 添加到 Job 类后,Quartz 将不会同时执行一个Job 的多个实例。 Quartz定时任务默认都是并发执行的,不会等待上一次任务执行完毕,只要间隔时间到就会执行, 如果定时任执行太长,会长时间占用资源,导致其它任务堵塞。
@PersistJobDataAfterExecution 添加到 Job 类后,表示 Quartz 将会在成功执行 execute() 方法后(没有抛出异常)更新 JobDetail 的 JobDataMap,下一次执行相同的任务(JobDetail)将会得到更新后的值,而不是原始的值。就像@DisallowConcurrentExecution 一样,这个注释基于 JobDetail 而不是 Job 类的实例。
如果你使用了 @PersistJobDataAfterExecution 注释,那么强烈建议你使用 @DisallowConcurrentExecution 注释,这是为了避免出现并发问题,当多个 Job 实例同时执行的时候,到底使用了哪个数据将变得很混乱。
trigger的时间点定义的几种形式:
Date startTime = DateBuilder.nextGivenSecondDate(null, 15); Date runTime = evenMinuteDate(new Date()); Date startTime = nextGivenSecondDate(null, 15);Trigger的创建分为:1、函数式,2、cron表达式。每种都有不循环和循环执行。
函数式
// 单次执行 JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build(); Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build(); sched.scheduleJob(job, trigger); // 循环执行 job = newJob(SimpleJob.class).withIdentity("job3", "group1").build(); trigger = newTrigger().withIdentity("trigger3", "group1").startAt(startTime) .withSchedule(simpleSchedule().withIntervalInSeconds(3).withRepeatCount(10)).build(); // 循环执行3次 // 无限循环用 withRepeatCount().repeatForever()).build(); ft = sched.scheduleJob(job, trigger);Cron表达式的语法网上朋友们的文章写得很全面,所以我就不在此赘述了。
JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build(); CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("0/20 * * * * ?")) .build(); Date ft = sched.scheduleJob(job, trigger); // ############################################################ job = newJob(SimpleJob.class).withIdentity("job2", "group1").build(); trigger = newTrigger().withIdentity("trigger2", "group1").withSchedule(cronSchedule("15 0/2 * * * ?")).build(); ft = sched.scheduleJob(job, trigger);Cron trigger 应该都是不用定义 startAt时间点的。
Scheduler的方法还是不少的碍于篇幅不说了,不过它的方法大部分都是能见名知意的。
ft = sched.scheduleJob(job, trigger); sched.start(); sched.shutdown(false); SchedulerMetaData metaData = sched.getMetaData();在execute里处理异常通常是:
JobKey jobKey = context.getJobDetail().getKey(); _log.info("---" + jobKey + " executing at " + new Date()); // a contrived example of an exception that // will be generated by this job due to a // divide by zero error try { int zero = 0; calculation = 4815 / zero; } catch (Exception e) { _log.info("--- Error in job!"); JobExecutionException e2 = new JobExecutionException(e); // Quartz will automatically unschedule // all triggers associated with this job // so that it does not run again e2.setUnscheduleAllTriggers(true); throw e2; } _log.info("---" + jobKey + " completed at " + new Date()); } JobKey jobKey = context.getJobDetail().getKey(); JobDataMap dataMap = context.getJobDetail().getJobDataMap(); int denominator = dataMap.getInt("denominator"); System.out.println("---" + jobKey + " executing at " + new Date() + " with denominator " + denominator); // a contrived example of an exception that // will be generated by this job due to a // divide by zero error (only on first run) try { calculation = 4815 / denominator; } catch (Exception e) { System.out.println("--- Error in job!"); JobExecutionException e2 = new JobExecutionException(e); // fix denominator so the next time this job run // it won't fail again dataMap.put("denominator", "1"); // this job will refire immediately e2.setRefireImmediately(true); throw e2; } System.out.println("---" + jobKey + " completed at " + new Date()); }Quartz有三种监听器:
SchedulerListenerJobListenerTriggerListenerSchedulerListener 是在 Scheduler 级别的事件产生时得到通知,不管是增加还是移除 Scheduler 中的 Job、Trigger,Scheduler的start和shutdown或者是 Scheduler 遭遇到了严重的错误时。那些事件不光是是关于对 Scheduler 管理的,还关注 Job 或 Trigger 的相关事件。SchedulerListener 接口包含了一系列的回调方法,它们会在 Scheduler 的生命周期中有关键事件发生时被调用。
其它两个Listener都是对应具体组件。 具体事件Quartz开发者都定义的比较清晰就不解释了,有几个注意的点在下面的代码里有注释。
可以在具体组件上加监听器。日志记录Quartz的状态。
quartz.properties
默认配置:
# Default Properties file for use by StdSchedulerFactory # to create a Quartz Scheduler Instance, if a different # properties file is not explicitly specified. # 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可以对一些改变一些设置做匹配业务、场景优化等。 基本配置有:线程数量、线程优先级、线程池类等。 将修改过的 quartz.properties 文件放到 ClassPath路径(如:Resource路径 下),应用重启时会自动加载配置文件。在改动配置文件时要把默认配置的所有内容带上,不能有缺省。
Maven使用
<exclusions> <exclusion> <groupId>quartz</groupId> <artifactId>quartz</artifactId> </exclusion> </exclusions>消除传递依赖可以解决。