Springboot实现定时器quartz中⽂说明
背景:
定时任务,在企业开发中尤其重要,很多业务都是需要定时任务去做的。⽐如说10点开售某件东西,凌晨0点统计注册⼈数,统计其他各种等等。这个时候不可能说让⼈为的去开启某个开关或者怎么怎么样的,如果这样的话,估计都要崩溃了。今天给⼤家介绍如何在项⽬中使⽤Quartz并且在后台动态配置定时任务的启动,暂停,重启,停⽌,还有修改启动的时间,修改执⾏的任务等。
我们需要明⽩ Quartz 的⼏个核⼼概念,这样理解起 Quartz 的原理就会变得简单了。
1,Job 表⽰⼀个⼯作,要执⾏的具体内容。此接⼝中只有⼀个⽅法
2,JobDetail 表⽰⼀个具体的可执⾏的调度程序,Job 是这个可执⾏程调度程序所要执⾏的内容,另外 JobDetail 还包含了这个任务调度的⽅案和策略。
3,Trigger 代表⼀个调度参数的配置,什么时候去调。
4,Scheduler 代表⼀个调度容器,⼀个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被Scheduler 容器调度了。
⼀,在使⽤Scheduler之前,需要实例化(这点你是否知道)。
scheduler实例化后,可以启动(start)、暂停(stand-by)、停⽌(shutdown)。注意:scheduler被停⽌后,除⾮重新实例化,否则不能重新启动;只有当scheduler启动后,即使处于暂停状态也不⾏,trigger才会被触发(job才会被执⾏)。
下⾯的代码⽚段,实例化并启动⼀个scheduler,调度执⾏⼀个job:
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = Scheduler();
sched.start();
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
⼆,Quartz API,Jobs和Triggers
Quartz API的关键接⼝是:
Scheduler - 与调度程序交互的主要API。
Job - 由希望由调度程序执⾏的组件实现的接⼝。
JobDetail - ⽤于定义作业的实例。
Trigger(即触发器) - 定义执⾏给定作业的计划的组件。
JobBuilder - ⽤于定义/构建JobDetail实例,⽤于定义作业的实例。
TriggerBuilder - ⽤于定义/构建触发器实例。
Scheduler的⽣命期,从SchedulerFactory创建它时开始,到Scheduler调⽤shutdown()⽅法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执⾏其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调⽤start()⽅法后,才会真正地触发trigger(即执⾏job),见教程⼀。
Quartz提供的“builder”类,可以认为是⼀种领域特定语⾔(DSL,Domain Specific Language)。教程
⼀中有相关⽰例,这⾥是其中的代码⽚段:(校对注:这种级联的API⾮常⽅便⽤户使⽤,⼤家以后写对外接⼝时也可以使⽤这种⽅式,这种⽅式前⾯我已经介绍过了)
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
定义job的代码使⽤的是从JobBuilder静态导⼊的⽅法。同样,定义trigger的代码使⽤的是从TriggerBuilder静态导⼊的⽅法 。 另外,也导⼊了SimpleSchedulerBuilder类的静态⽅法;
SchedulerBuilder接⼝的各种实现类,可以定义不同类型的调度计划(schedule);
DateBuilder类包含很多⽅法,可以很⽅便地构造表⽰不同时间点的java.util.Date实例(如定义下⼀个⼩时为偶数的时间点,如果当前时间为9:43:27,则定义的时间为10:00:00)。
Job和Trigger
Job接⼝:
⼀个job就是⼀个实现了Job接⼝的类
package org.quartz;
public interface Job {
public void execute(JobExecutionContext context)
throws JobExecutionException;
}
当Job的⼀个trigger被触发(稍后会讲到)时,execute()⽅法由调度程序的⼀个⼯作线程调⽤。传递给execute()⽅法的JobExecutionContext对象向作业实例提供有关其“运⾏时”环job的⼀个trigger被触发后(稍后会讲到),execute()⽅法会被scheduler的⼀个⼯作线程调⽤;传递给execute()⽅法的JobExecutionContext对象中保存着该job运⾏时的⼀些信息 ,执⾏job的scheduler的引⽤,触发job的trigger的引⽤,JobDetail对象引⽤,以及⼀些其它信息。
JobDetail对象是在将job加⼊scheduler时,由客户端程序(你的程序)创建的。它包含job的各种属性设置,以及⽤于存储job实例状态信息的JobDataMap。
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的时候,我们认为将调度和要调度的任务分离是合理的。在我们看来,这可以带来很多好处。
Key(设置后便于管理)
将Job和Trigger注册到Scheduler时,可以为它们设置key,配置其⾝份属性。Job和Trigger的key(JobKey和TriggerKey)可以⽤于将Job和Trigger放到不同的分组(group)⾥,然后基于分组进⾏操作。同⼀个分组下的Job或Trigger的名称必须唯⼀,即⼀个Job或Trigger的key由名称(name)和分组(group)组成。
Job的特点、Job接⼝的execute⽅法:
你定义了⼀个实现Job接⼝的类,这个类仅仅表明该job需要完成什么类型的任务,除此之外,Quartz还需要知道该Job实例所包含的属性;这将由JobDetail类来完成。
JobDetail实例:
JobDetail实例是通过JobBuilder类创建的,导⼊该类下的所有静态⽅法,会让你编码时有DSL的感觉:
现在考虑这样定义的作业类“HelloJob”:
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
}
}
可以看到,我们传给scheduler⼀个JobDetail实例,因为我们在创建JobDetail时,将要执⾏的job的类名传给了JobDetail,所以scheduler就知道了要执⾏何种类型的job;每次当scheduler执⾏job时,在调⽤其execute(…)⽅法之前会创建该类的⼀个新的实例;执⾏完毕,对该实例的引⽤就被丢弃了,实例会被垃圾回收;这种执⾏策略带来的⼀个后果是,job必须有⼀个⽆参的构造函数(当使⽤默认的JobFactory时);另⼀个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执⾏中,这些属性的值不会保留。
那么如何给job实例增加属性或配置呢?如何在job的多次执⾏中,跟踪job的状态呢?答案就是:JobDataMap,JobDetail对象的⼀部分。在Job执⾏时,JobExecutionContext中的JobDataMap为我们提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。
如果你希望使⽤JobFactory实现数据的⾃动“注⼊”,则⽰例代码为:(个⼈⽐较推荐何种⽅式)
public class DumbJob implements Job {
String jobSays;
float myFloatValue;
ArrayList state;
public DumbJob() {
}
public void execute(JobExecutionContext context)spring ioc注解
throws JobExecutionException
{
JobKey key = JobDetail().getKey();
JobDataMap dataMap = MergedJobDataMap(); // Note the difference from the previous example
state.add(new Date());
}
public void setJobSays(String jobSays) {
this.jobSays = jobSays;
}
public void setMyFloatValue(float myFloatValue) {
myFloatValue = myFloatValue;
}
public void setState(ArrayList state) {
state = state;
}
}
你也许发现,整体上看代码更多了,但是execute()⽅法中的代码更简洁了。⽽且,虽然代码更多了,但如果你的IDE可以⾃动⽣成setter⽅法,你就不需要写代码调⽤相应的⽅法从JobDataMap中获取数据了,所以你实际需要编写的代码更少了。当前,如何选择,由你决定。
Job实例(这个概念很重要)
很多⽤户对于Job实例到底由什么构成感到很迷惑。我们在这⾥解释⼀下,并在接下来的⼩节介绍job状态和并发。
你可以只创建⼀个job类,然后创建多个与该job关联的JobDetail实例,每⼀个实例都有⾃⼰的属性集和JobDataMap,最后,将所有的实例都加到scheduler中。
⽐如,你创建了⼀个实现Job接⼝的类“SalesReportJob”。该job需要⼀个参数(通过JobdataMap传⼊),表⽰负责该销售报告的销售员的名字。因此,你可以创建该job的多个实例(JobDetail),⽐如“SalesReportForJoe”、“SalesReportForMike”,
将“joe”和“mike”作为JobDataMap的数据传给对应的job实例。
当⼀个trigger被触发时,与之关联的JobDetail实例会被加载,JobDetail引⽤的job类通过配置在Scheduler上的JobFactory进⾏初始化。默认的JobFactory实现,仅仅是调⽤job类的newInstance()⽅法,然后尝试调⽤JobDataMap中的key的setter⽅法。你也可以创建⾃⼰的JobFactory实现,⽐如让你的IOC或DI容器可以创建/初始化job实例。
在Quartz的描述语⾔中,我们将保存后的JobDetail称为“job定义”或者“JobDetail实例”,将⼀个正在执⾏的job称为“job实例”或
者“job定义的实例”。当我们使⽤“job”时,⼀般指代的是job定义,或者JobDetail;当我们提到实现Job接⼝的类时,通常使⽤“job 类”。
Job状态与并发
关于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中存储的数据很可能是不确定的。
Quartz中Triggers介绍
最常⽤的两种trigger:SimpleTrigger和CronTrigger。
Trigger的公共属性
所有类型的trigger都有TriggerKey这个属性,表⽰trigger的⾝份;除此之外,trigger还有很多其它的公共属性。这些属性,在构建trigger的时候可以通过TriggerBuilder设置。
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号。
SimpleTrigger可以满⾜的调度需求是:在具体的时间点执⾏⼀次,或者在具体的时间点执⾏,并且以指定的间隔重复执⾏若⼲次。⽐如,你有⼀个trigger,你可以设置它在2015年1⽉13⽇的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发⼀次,⼀共重复5次。
指定时间开始触发,不重复:
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime) // some Date
.forJob("job1", "group1") // identify job with name, group strings
.build();
指定时间触发,每隔10秒执⾏⼀次,重复10次:
trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(myTimeToStartFiring) // if a start time is not given (if this line were omitted), "now" is implied
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.
withRepeatCount(10)) // note that 10 repeats will give a total of 11 firings
.forJob(myJob) // identify job with handle to its JobDetail itself
.build();
5分钟以后开始触发,仅执⾏⼀次:
trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger5", "group1")
.startAt(futureDate(5, IntervalUnit.MINUTE)) // use DateBuilder to create a date in the future
.forJob(myJobKey) // identify job with its JobKey
.build();
⽴即触发,每个5分钟执⾏⼀次,直到22:00:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论