Quartz是什么,可以做什么
Quartz是一个任务调度框架.Quartz可以在某个固定的时间点进行一些提前设置好需要做的事情.比如,在每天的12点提醒下班,每天凌晨0点自动发一封邮件等.
Quartz涉及到几个要素:
- Scheduler:调度器,所有的定时任务都是 由它进行调度的
- Job:具体执行的逻辑(也就是说,需要这个Quartz做什么事情是从这个Job定义的)
- Trigger:触发器(简单来说就是定义Job被执行的时间,具体什么时间什么方式执行这个Job,由触发器来设置,比如每隔1秒执行一次,或者每天的12点执行一次等).SimpleTrigger和CronTrigger 是最常用的两种.Trigger实例
- SimpleTrigger
- SimpleTrigger主要用于一次性执行的Job(只在某个特定的时间点执行一次),或者Job在特定的时间点执行,重复执行N次,每次执行间隔T个时间单位
- CronTrigger
- CronTrigger在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午10:15”等。
- SimpleTrigger
- JobDetail:定义一些任务数据,Job是具体的任务,而JobDetail则是定义一些其他的任务执行时的任务数据.比如要指定哪个Job实例.这个实例叫什么名字,属于哪个分组等等
简单的示例:
package com.cn.hj.quartz.one;
import com.mchange.v1.db.sql.schemarep.SimpleSchemaRep;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.JobBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.DateBuilder.*;
public class FirstUsingQuartz {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory factory = new StdSchedulerFactory(); //构建定时任务工厂
Scheduler scheduler = factory.getScheduler(); //获取定时任务实例.
scheduler.start(); //启动
JobDetail job = newJob(MyJob.class).withIdentity("myJob", "group1").build(); //定义需要执行的实例,设置工作名称
//同一个分组下的Job实例名称必须要唯一
Trigger trigger = newTrigger().withIdentity("myTrigger", "group1") //定义触发器.名称,分组.同一个分组下的Trigger实例名称必须要唯一
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever()).build();
scheduler.scheduleJob(job, trigger);
}
}
示例采用的是导入静态包的方式编写的.
JobDetail:是利用 JobBuilder 进行构建的.JobBuilder 用于定义/构建JobDetail实例,用于定义作业的实例
Trigger: 是利用TriggerBuilder 进行构建的.TriggerBuilder 用于定义/构建触发器实例
在使用Scheduler之前,需要实例化.可以利用SchedulerFactory进行.scheduler实例化后,可以启动(start)、暂停(stand-by)、停止(shutdown)。注意:scheduler被停止后,除非重新实例化,否则不能重新启动;只有当scheduler启动后,即使处于暂停状态也不行,trigger才会被触发(job才会被执行)。这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。
那么如何给job实例增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:JobDataMap,JobDetail对象的一部分。
JobDataMap
JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。如果你使用的是持久化的存储机制(本教程的JobStore部分会讲到),在决定JobDataMap中存放什么数据的时候需要小心,因为JobDataMap中存储的对象都会被序列化,因此很可能会导致类的版本不一致的问题
package com.cn.hj.quartz.one;
import com.mchange.v1.db.sql.schemarep.SimpleSchemaRep;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.JobBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.DateBuilder.*;
public class FirstUsingQuartz {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory factory = new StdSchedulerFactory(); //构建定时任务工厂
Scheduler scheduler = factory.getScheduler(); //获取定时任务实例.
scheduler.start(); //启动
//构造JobDataMap,并且添加数据
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobSays", "my name is shensha");
jobDataMap.put("firstName", "hejian");
JobDetail job = newJob(MyJob.class)
.withIdentity("myJob", "group1") //定义需要执行的实例,设置工作名称.同一个分组下的Job实例名称必须要唯一
.usingJobData(jobDataMap) //加入数据
.build();
Trigger trigger = newTrigger().withIdentity("myTrigger", "group1") //定义触发器.名称,分组.同一个分组下的Trigger实例名称必须要唯一
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever()).build();
scheduler.scheduleJob(job, trigger);
}
}
MyJob:
package com.cn.hj.quartz.one;
import org.quartz.*;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey key = context.getJobDetail().getKey();
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String jobSays = dataMap.getString("jobSays");
String firstName = dataMap.getString("firstName");
System.out.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + firstName);
}
}
Trigger
除了TriggerKey可以表明Trigger的身份之外,Trigger还有一些公共属性
Trigger公共属性
- jobKey属性:当trigger触发时被执行的job的身份;
- startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。
- endTime属性:表示trigger失效的时间点。比如,”每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。
优先级
如果Trigger很多,或者Quartz的工作线程太少,Quartz可能没有足够的资源同时触发所有的trigger.这种情况下,你可能希望控制哪些trigger优先使用Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性.如果没有设置优先级,那么trigger则会使用默认的优先级,默认的优先级是5.priority属性的值可以是任意整数,正数、负数都可以..
只有同时触发的trigger才有优先级.而且10:59的trigger永远要比11:00的trigger先执行,即使11:00的trigger优先级比10:59的要高.
如果trigger是可以恢复的(出现意外之后重新恢复),那么优先级与恢复之前是保持一致的.
错过触发
trigger还有一个重要的属性misfire;如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不同类型的trigger,有不同的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为。当scheduler启动的时候,查询所有错过触发(misfire)的持久性trigger。然后根据它们各自的misfire机制更新trigger的信息
所有触发器类型的默认值 MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
SimpleTrigger策略:
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_FIRE_NOW
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
策略具体的含义需要查看API
CornTrigger策略
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_DO_NOTHING
MISFIRE_INSTRUCTION_FIRE_NOW
策略具体含义可查看API
排除时间(在某个或者某段时间不执行)
Quartz的Calendar对象(不是java.util.Calendar对象)可以在定义和存储trigger的时候与trigger进行关联.可以利用这个对象排除一些不需要执行此调度任务的时间,比如在每天的12:00 到14:00 不用发送邮件等等.
所有任何实现了Calendar接口的可序列化对象都可以作为Calendar对象
简单示例:
package com.cn.hj.quartz.one;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.calendar.DailyCalendar;
public class SecondQuartz {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
scheduler.start();
DailyCalendar calendar = new DailyCalendar("15:56:00", "15:57:00"); //构造一个时间范围
scheduler.addCalendar("nodate", calendar, false, false); //将时间范围添加到调度器当中
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("myjob2", "group2").build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger2")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
.modifiedByCalendar("nodate") //设置需要排除的指定日期的名称
.build();
scheduler.scheduleJob(jobDetail, trigger);
}
}
示例中排除了每天的 15:56:00 至 15:57:00 这个时间段之内不会执行调度任务.Quartz为我们默认实现了很多个Calendar接口的视实现类,具体可查看相关API.一个Calendar接口对象可以用作多个Trigger触发器.
SimpleTrigger
SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2019年1月1日的上午00:00:00准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。根据描述,你可能已经发现了,SimpleTrigger的属性包括:开始时间、结束时间、重复次数以及重复的间隔
重复次数,可以是0、正整数,以及常量SimpleTrigger.REPEAT_INDEFINITELY。重复的间隔,必须是0,或者long型的正数,表示毫秒。注意,如果重复间隔为0,trigger将会以重复次数并发执行(或者以scheduler可以处理的近似并发数)。
endTime属性的值会覆盖设置重复次数的属性值;比如,你可以创建一个trigger,在终止时间之前每隔10秒执行一次,你不需要去计算在开始时间和终止时间之间的重复次数,只需要设置终止时间并将重复次数设置为REPEAT_INDEFINITELY(当然,你也可以将重复次数设置为一个很大的值,并保证该值比trigger在终止时间之前实际触发的次数要大即可)。
示例:
指定是开始触发,不重复.
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime) // 设置时间
.forJob("job1", "group1") // 标识名称,分组
.build();
指定时间触发,每隔去10秒执行一次,重复10次
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(myTimeToStartFiring) // 设置触发时间,如果不设置,或者为空,那么表示现在
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10)) // 设置重复10次,实际执行11次, 程序运行的那一刻,会执行一次
.forJob(myJob) // 标识名称
.build();
5分钟以后触发,仅执行一次
trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger5", "group1")
.startAt(futureDate(5, IntervalUnit.MINUTE)) // 使用DateBuilder在将来创建日期
.forJob(myJobKey) // 表示名称
.build();
立即触发,每5分钟执行一次,直到22:00
trigger = newTrigger()
.withIdentity("trigger7", "group1")
.withSchedule(simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever())
.endAt(dateOf(22, 0, 0))
.build();
建立一个触发器,将在下一个小时的整点触发,然后每2小时重复一次:
trigger = newTrigger()
.withIdentity("trigger8") // 没有指定分组,将使用默认分组
.startAt(evenHourDate(null)) // 得到下一个偶数小时(分和秒0(“00:00”))
.withSchedule(simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
.build();
scheduler.scheduleJob(trigger, job);
CronTrigger
CronTrigger通常比Simple Trigger更有用,如果您需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。使用CronTrigger,您可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。即使如此,和SimpleTrigger一样,CronTrigger有一个startTime,它指定何时生效,以及一个(可选的)endTime,用于指定何时停止计划。
Corn表达式相当灵活,Corn的表达式相关可以查询资料学习.
每隔一分钟,每天上午8点至下午5点之间
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?"))
.forJob("myJob", "group1")
.build();
建立一个触发器,将在上午10:42每天发射
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(dailyAtHourAndMinute(10, 42))
.forJob(myJobKey)
.build();
或者
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 42 10 * * ?"))
.forJob(myJobKey)
.build();
scheduler生命周期
Scheduler的生命期,从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job)
在示例中,我们在创建JobDetail的时候,传入了一个Job实例(实现了Job接口的实例),这样的话Scheduler就知道了要执行何种类型的Job.Scheduler在执行execute()方法之前会创建该类的一个新的实例.执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收
文章参考w3c中文Quartz教程,以及官方文档.