常用任务调度组件介绍
1. quartz(最新2.3.1版本)
quartz中,关键组成部分为:Job、Trigger、Scheduler
- Scheduler: 调度程序维护作业详细信息(JobDetail)和触发器的注册表。一旦注册,调度程序将负责在相关的触发器触发时(调度时间到达时)执行作业。
- Job:调度程序当触发器触发时,具体执行的作业任务。
- Triiger:控制某个具体作业任务执行的规律,如什么时候开始执行,什么时候结束执行,期间执行间隔什么(每秒/分钟/小时等)。
1.1 Job与JobDetail介绍
job是调度器调度执行的具体任务,JobDetail是对这个job任务进行具体的修饰,比如这个任务的基本描述(description),唯一标识(JobKey)等信息。
1.1.1 Job
Job是一个接口,里面只有一个方法:execute(JobExecutionContext context)
public interface Job {
void execute(JobExecutionContext context)
throws JobExecutionException;
}
任何需要任务调取程序执行的任务类都需要实现Job接口,execute(JobExecutionContext context)方法中实现具体任务。如图中MyJob类实现Job接口: public class MyJob implements Job {
public class MyJob implements Job {
public void execute(JobExecutionContext jobExecutionContext) {
JobKey jobKey=jobExecutionContext.getJobDetail().getKey();
System.out.println(new SimpleDateFormat("yyyyMMddhhmmsssSSS").format(new Date())+":执行"+ jobKey.getName()+":"+jobKey.getGroup());
}
}
myJob作业就是打印任务详情(JobDetail下面将介绍)。
1.1.2 JobDetail介绍
之前介绍过,任务调度程序Schedule必须是作业详细信息(JobDetail)和触发器,而JobDetail又是对Job(如MyJob)信息的具体修饰。
JobDetail的是一个接口,其实现类只有一个:
JobDetail可以通过JobBuilder类的build方法进行创建(最终创建的是JobDetailmple),JobBuilder结构如下:
在通过build()创建jobDetail之前,可以通过 newJob()方法管理具体的作业任务(如MyJob),设置描述信息widthDescription()、唯一标识withdIdentity(),执行数据usingJobData()等。
下面是创建JobDetail的代码:
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withDescription("my first job")
.withIdentity("jobName", "jobGroupName")
.build();
截止为止,我们已经对Quartz中三要素之一:job与JobDetail 有了一个了解,那么如何控制作业的执行,将有下面Trigger去实现。
1.2 Trigger介绍
Trigger就是一个触发器,控制某一任务具体在什么时候触发执行,比如在某一时间点, 或者在某一时间段,执行几次,执行间隔等。
Trigger实现有如下六种,其中已经实现类:CalendarIntervalTriggerImpl, CronTriggerImpl, DailyTimeIntervalTriggerImpl, SimpleTriggerImpl
1.2.1 SimpleTriggerImple 简单触发器
根据其名字子可知,简单的触发器,是触发器类型中最简单的一种,其只能用于在给定的时间点触发作业,并可选地在指定的时间间隔内重复。下面是我创建一个SimpleTriggerImple:
Trigger trigger = TriggerBuilder.newTrigger()//1创建TriggerBuilder
.withIdentity("triggerName", "triggerGroupName")//2定义触发器唯一标识
.withSchedule(repeatSecondlyForever())//3-1设置执行触发时间
.startAt(date)//3-2设置开始时间
.endAt(end)//3-2设置结束时间
.build();//4通过2-3的数据,生成SimpleTriggerBuilder
其中第2步,通过设置trigger的名字和分组生成唯一标识TriggerKey 源码:
public TriggerBuilder<T> withIdentity(String name, String group) {
key = new TriggerKey(name, group);
return this;
}
重点第3步,通过设置不同的ScheduleBuilder,如果是生成SimpleTriggerImple,那这边必须是SimpleScheduleBuilder,如果是CronTriggerImple则必须是CronScheduleBuilder,以此类推CalendarIntervalTriggerImpl=>CalendarIntervalScheduleBuilder,DailyTimeIntervalTriggerImpl=>DailyTimeIntervalcheduleBuilder。
其中repeatSecondlyForever()是SimpleScheduleBuilder的static方法,返回值是SimpleScheduleBuilder实例,还有类似方法,如:repeatHourlyForever()、repeatMinutelyForTotalCount()、repeatMinutelyForTotalCount()等,具体方法作用我们通过其方法名称可以了解。
其他以withXXX方法,我们可以调用他们进行设置,比如执行间隔,触发器不生效时的处理策略等。详情可以通过quartz的api进行了解。
TriggerBuilder 最后通过build()方法生成一个SimpleTriggerImple(源码):
public T build() {
if (this.scheduleBuilder == null) {
//当scheduleBuilder为空时,默认赋值SimpleScheduBuilder
this.scheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
}
//在通过SimpleScheduBuilder去生成simplerTriggerImple
MutableTrigger trig = this.scheduleBuilder.build();
trig.setCalendarName(this.calendarName);
trig.setDescription(this.description);
trig.setStartTime(this.startTime);
trig.setEndTime(this.endTime);
if (this.key == null) {
this.key = new TriggerKey(Key.createUniqueName((String)null), (String)null);
}
trig.setKey(this.key);
if (this.jobKey != null) {
trig.setJobKey(this.jobKey);
}
trig.setPriority(this.priority);
if (!this.jobDataMap.isEmpty()) {
trig.setJobDataMap(this.jobDataMap);
}
return trig;
}
simpleTriggerImple触发器创建完后,如果将JobDetailImple与SimpleTriggerImple,注册到调度程序:
/**
* SimpleTrigger简单定时任务,定点执行。
*/
public static void demo1() throws ParseException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
//DateBuilder 获取各种时间点的方法类
Date startDate=DateBuilder.evenSecondDate(new SimpleDateFormat("yyyyMMddHHmmssSSS").parse("20190728234700341"));
System.out.println(startDate);
Date endDate=DateBuilder.evenMinuteDate(new SimpleDateFormat("yyyyMMddHHmmssSSS").parse("20190728235000341"));
System.out.println(endDate);
scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withDescription("my first job")
.withIdentity("jobName", "jobGroupName")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("triggerName", "triggerGroup")
.withSchedule(repeatSecondlyForever())
.startAt(startDate)
.endAt(endDate)
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (SchedulerException e1) {
e1.printStackTrace();
}
}
此方法运行的结果是:程序启动后,将在当时间为 2019-07-28 23:47:00.341 执行,每秒执行一次,无限循环,且在时间为2019-07-28 23:50:00.341一下分钟前结束。执行结果如下,47分1秒开始执行,50分59秒结束:
Sun Jul 28 23:47:01 CST 2019
Sun Jul 28 23:51:00 CST 2019
20190728114701020:执行jobName:jobGroupName
20190728114702001:执行jobName:jobGroupName
20190728114703001:执行jobName:jobGroupName
......
20190728115057000:执行jobName:jobGroupName
20190728115058000:执行jobName:jobGroupName
20190728115059000:执行jobName:jobGroupName
其中DateBuilder类中提供了很多获取时间的方法,具体可以查看API文档。
1.2.2 CronTriggerImple介绍
cronTrrigger触发器,相对于Simple触发器,可以进行复杂的调度执行计划,通过使用Cron定义具体的执行计划,通过定义不同的cron表达式,cronTrigger几乎可以替代其他的触发器。CronTriggerImple创建方式类似于SimpleTriggerImple,其却区别在于上节中的第三步,ScheduleBuilder的设置。其中创建CronTriggerImple则必须通过设置CronScheduleBuilder,详见代码:
/**
* CromTrigger定时任务
*/
public static final void demo2()
{
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withDescription("my first job")
.withIdentity("jobName", "jobGroupName")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("triggerName", "triggerGroup")
.withSchedule(cronSchedule("*/2 * * * * ?"))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (SchedulerException e1) {
e1.printStackTrace();
}
}
与SimpleTrigger的区别在于withSchedule() 中使用了cronSchedule("*/2 * * * * ?") ,cronSchedule方法是CronScheduleBuilder中的static方法。类似cronSchedule的方法还有如下图中红框中的方法,通过其方法名可以了解方法的作用,详情见CronScheduleBuilder的API:
最常用的方法就是cronSchedule(String cron)。通过定义cron字符串,定义任务的执行计划,如“*/2 * * * * ?”:每两秒钟执行一次。执行结果如下:
20190729121536085:执行jobName:jobGroupName
20190729121538001:执行jobName:jobGroupName
20190729121540002:执行jobName:jobGroupName
20190729121542002:执行jobName:jobGroupName
20190729121544002:执行jobName:jobGroupName
......
其中cron编写使用规则可以参考:https://blog.csdn.net/fanrenxiang/article/details/80361582
1.2.3 DailyTimeIntervalTriggerImple与CalendarIntervalTriggerImple介绍
- DailyTimeIntervalTriggerImple是DailyTimeIntervalTrigger的一个具体实现,用于基于每天重复的时间间隔触发。
/**
* DailyTimeIntervalTrigger定时任务
*/
public static final void demo3()
{
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withDescription("my first job")
.withIdentity("jobName", "jobGroupName")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("triggerName", "triggerGroup")
//通过设置执行次数和开始执行时间。当在每天的12点03分执行2次后结束,其他方式详情参考DailyTimeIntervalScheduleBuilder api
.withSchedule(dailyTimeIntervalSchedule().startingDailyAt(TimeOfDay.hourAndMinuteOfDay(12,3)).endingDailyAfterCount(2))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (SchedulerException e1) {
e1.printStackTrace();
}
}
运行结果:
20190729120300085:执行jobName:jobGroupName
20190729120400001:执行jobName:jobGroupName
20190730120300008:执行jobName:jobGroupName
20190730120400005:执行jobName:jobGroupName
- CalendarIntervalTriggerImple是CalendarIntervalTrigger一个具体的触发器,用于根据重复的日历时间间隔触发作业。
/**
* CalendarIntervalTrigger定时任务
*/
public static final void demo4()
{
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withDescription("my first job")
.withIdentity("jobName", "jobGroupName")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("triggerName", "triggerGroup")
//当前时间执行,每隔三秒执行一次。其他方法详情参考CalendarIntervalScheduleBuilder api
.withSchedule(calendarIntervalSchedule().withInterval(3, DateBuilder.IntervalUnit.SECOND))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (SchedulerException e1) {
e1.printStackTrace();
}
}
运行结果:
20190730122545578:执行jobName:jobGroupName
20190730122548492:执行jobName:jobGroupName
20190730122551494:执行jobName:jobGroupName
20190730122554493:执行jobName:jobGroupName
很显然,这其他触发器的使用方式类似,详情请参考DailyTimeIntervalScheduleBuilder与CalendarIntervalScheduleBuilder。
根据上面几节的介绍,我们不难发现,很多核心对象的创建都是来至于XXXBuilder,比如:
- JobDetialImple 通过JobBuilder的build方法生成
- XXXTriggerImple 通过 TriggerBuilder的build方法(最终还是调用XXXXScheduleBuilder的build方法)生成
- XXXXScheduleBuilder
- 获取特定条件的日期,通过DateBuilder中很多方法可以获取。
综上,有关Job、JobDeatil、Trigger介绍已经告一段落,下面我将介绍如何将运行时参数传递到quartz作业,以及如何在作业中维护状态。
1.3作业参数和作业状态
保存作业的参数和状态一般使用JobDataMap,他的作用就是保存作业实例的状态信息。将作业添加到调度程序时,JobDataMap实例只存储一次。在每次执行带有@PersistJobDataAfterExecution注释的作业之后,它们还会被重新持久化,如果执行作业上没有此注解,将不会对数据进行持久化。JobDataMap实例也可以用触发器存储。如果您的作业存储在调度程序中,供多个触发器定期/重复使用,但是每个独立的触发器都希望为作业提供不同的数据输入,那么这种方法非常有用。
在作业实例中添加JobDataMap 用于保存作业参数
@PersistJobDataAfterExecution//只有此注解,才会对JobDataMap数据持久化
@DisallowConcurrentExecution
public class ColorJob implements Job {
public static final String FAVORITE_COLOR="favoriteColor";
public static final String EXECUTION_COUNT="executionCount";
private int count=1;
public void execute(JobExecutionContext jobExecutionContext) {
JobDataMap data = jobExecutionContext.getJobDetail().getJobDataMap();//获取当前作业的参数
String favoriteColor = data.getString(FAVORITE_COLOR);//根据key值获取对应参数获状态
Integer executionCount =data.getInt(EXECUTION_COUNT);
JobKey jobKey=jobExecutionContext.getJobDetail().getKey();
System.out.println(new SimpleDateFormat("yyyyMMddhhmmssSSS").format(new Date())+":执行"+ jobKey.getName()+":"+jobKey.getGroup()+" favorite color is " + favoriteColor + "\n" +
" execution count (from job map) is " + executionCount + "\n" +
" execution count (from job member variable) is " + count);
executionCount++;
data.put(EXECUTION_COUNT, executionCount);//对JobDataMap参数状态进行更新
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
public static final void demo() {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail1 = JobBuilder.newJob(ColorJob.class)
.usingJobData(ColorJob.FAVORITE_COLOR,"Green")//通过JobBuilder对接设置JobDataMap值
.usingJobData(ColorJob.EXECUTION_COUNT,1)
.withIdentity("job1", "jobGroupName")
.build();
Trigger trigger1 = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "triggerGroup")
.withSchedule(simpleSchedule()
.withIntervalInSeconds(2)
.withRepeatCount(4))
.build();
// jobDetail1.getJobDataMap().put(ColorJob.FAVORITE_COLOR, "Green");
// jobDetail1.getJobDataMap().put(ColorJob.EXECUTION_COUNT, 1);
JobDetail jobDetail2 = JobBuilder.newJob(ColorJob.class)
.usingJobData(ColorJob.FAVORITE_COLOR,"Red")
.usingJobData(ColorJob.EXECUTION_COUNT,1)
.withIdentity("job2", "jobGroupName")
.build();
Trigger trigger2 = TriggerBuilder.newTrigger()
.withIdentity("trigger2", "triggerGroup")
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.withRepeatCount(4))
.build();
// jobDetail2.getJobDataMap().put(ColorJob.FAVORITE_COLOR, "Red");//通过JobDetail设置JobDataMap值
// jobDetail2.getJobDataMap().put(ColorJob.EXECUTION_COUNT, 1);
scheduler.scheduleJob(jobDetail1, trigger1);
scheduler.scheduleJob(jobDetail2, trigger2);
scheduler.start();
//scheduler.shutdown(true);
} catch (SchedulerException e1) {
e1.printStackTrace();
}
}
上面代码中,通过两种方式设置作业参数,一种是通过JobBuilder设置,一种是通过JobDetail设置。其中通过JobBuilder的设置方式还有其他几种,如下图:
上面程序运行的结果如下:
20190730033215781:执行job2:jobGroupName favorite color is Red
execution count (from job map) is 1
execution count (from job member variable) is 1
20190730033215783:执行job1:jobGroupName favorite color is Green
execution count (from job map) is 1
execution count (from job member variable) is 1
20190730033216721:执行job2:jobGroupName favorite color is Red
execution count (from job map) is 2
execution count (from job member variable) is 1
20190730033217674:执行job1:jobGroupName favorite color is Green
execution count (from job map) is 2
execution count (from job member variable) is 1
20190730033217716:执行job2:jobGroupName favorite color is Red
execution count (from job map) is 3
execution count (from job member variable) is 1
20190730033218718:执行job2:jobGroupName favorite color is Red
execution count (from job map) is 4
execution count (from job member variable) is 1
20190730033219675:执行job1:jobGroupName favorite color is Green
execution count (from job map) is 3
execution count (from job member variable) is 1
20190730033219719:执行job2:jobGroupName favorite color is Red
execution count (from job map) is 5
execution count (from job member variable) is 1
20190730033221679:执行job1:jobGroupName favorite color is Green
execution count (from job map) is 4
execution count (from job member variable) is 1
20190730033223675:执行job1:jobGroupName favorite color is Green
execution count (from job map) is 5
execution count (from job member variable) is 1
job1和job2的任务的作业参数都被独立的保存下来了,并且进行了持久化。
1.4 任务处理异常JobExecutionException介绍
作业可以引发的异常,当执行作业时遇到错了,以指示Quartz调度程序在执行时发生错误,以及作业请求是否要立即重新启动(使用相同的JobExecutionContext,或作业是否希望被取消调度)
当作业执行期间抛出Exception时,我们可以通过JobExecutionException 封装Exception异常,并通过JobExecutionException实例,去执行具体的异常操作,比如,重新启动任务、停止当前任务的触发器,停止当前任务的所有触发器JobExecutionException的详情见下:
其中:
- refireImmediately():是指立即重新启动当前作业
- setUnscheduleFiringTrigger(boolean unscheduleTrigg):指停止当前作业的触发器,即终止当前作业
- setUnscheduleAllTriggers(boolean unscheduleAllTriggs):指停止当前作业其他所有触发器,如果有3个触发器,执行此作业,其他2个触发器也会停止触发
实例代码如下:
MyExceptionJob1:
public class MyExceptionJob1 implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey jobKey=context.getJobDetail().getKey();
try {
int zero = 0;
int calculation = 4815 / zero;
}
catch (Exception e) {
System.out.println(new SimpleDateFormat("yyyyMMddhhmmssSSS").format(new Date())+":执行Error in job"+ jobKey.getName()+":"+jobKey.getGroup());
JobExecutionException e2 =
new JobExecutionException(e);
// this job will refire immediately
e2.refireImmediately();//立即重新启动作业
System.out.println(e.fillInStackTrace());
throw e2;
}
}
}
MyExceptionJob2:
public class MyExceptionJob2 implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey jobKey=context.getJobDetail().getKey();
try {
int zero = 0;
int calculation = 4815 / zero;
}
catch (Exception e) {
System.out.println(new SimpleDateFormat("yyyyMMddhhmmssSSS").format(new Date())+":执行Error in job"+ jobKey.getName()+":"+jobKey.getGroup());
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);//停止该作业的所有触发器
e2.setUnscheduleFiringTrigger(true);//停止当前作业触发器
throw e2;
}
}
}
主方法:
public static final void demo() {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail1 = JobBuilder.newJob(MyExceptionJob1.class)
.withIdentity("job1", "jobGroupName")
.build();
Trigger trigger1 = TriggerBuilder.newTrigger()
.usingJobData(ColorJob.EXECUTION_COUNT,1)
.withSchedule(simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();
JobDetail jobDetail2 = JobBuilder.newJob(MyExceptionJob2.class)
.withIdentity("job2", "jobGroupName")
.build();
Trigger trigger2 = TriggerBuilder.newTrigger()
.withIdentity("trigger2", "triggerGroup")
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
JobDetail jobDetail3= JobBuilder.newJob(MyExceptionJob2.class)
.withIdentity("job3", "jobGroupName")
.build();
Trigger trigger3 = TriggerBuilder.newTrigger()
.withIdentity("trigger3", "triggerGroup")
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
scheduler.scheduleJob(jobDetail1, trigger1);
scheduler.scheduleJob(jobDetail2, trigger2);
scheduler.scheduleJob(jobDetail3, trigger3);
scheduler.start();
//scheduler.shutdown(true);
} catch (SchedulerException e1) {
e1.printStackTrace();
}
}
执行结果:
20190730062017644:执行Error in jobjob3:jobGroupName
20190730062017650:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
20190730062017651:执行Error in jobjob2:jobGroupName
20190730062019304:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
20190730062021304:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
20190730062023304:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
20190730062025305:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
20190730062027304:执行Error in jobjob1:jobGroupName
java.lang.ArithmeticException: / by zero
可见,job2和job3都已经停止作业触发,只有job1在不停的重复执行。