最近在做批量调度系统,用到了quartz定时调度的功能,因此学习了解了quartz,写一篇博客总结与分享。
本文从引入jar包开始,手把手带你入门领略quartz的基础功能,涉及的代码可以直接复制到自己的ide中运行。
本文大纲:
1、什么是quartz
2、怎么使用quartz以及Demo代码
==========================================开始分割线===============================================
1、什么是quartz
官网介绍:
quartz是一个功能丰富、开源的任务调度库,它可以被集成到所有的Java程序,无论是很小的单节点还是规模庞大的商业系统。quartz可以被用来创建简单或者复杂的调度策略,以执行成千上万的任务。任务一般是指一个标准的Java组件,实际上可以是你写代码指定的任何逻辑。Quartz Scheduler还包括很多企业级的特性,例如JTA事务控制和集群。
换句话说,我们有时候需要一些定时调度程序的功能,单纯用JDK的Timer类并不能满足我们的需求,因此就有了quartz来处理我们的定时调度,quartz提供了一些Scheduler(调度策略),以便我们管理和执行我们的Job(任务)。
2、怎么使用quartz以及Demo代码
首先,当然是引入jar包,新建一个简单的Maven项目,引入以下的jar。(若是Spring或者SpringBoot则需要引入对应的jar)
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
本文案例的项目结构如下:

目录结构很简单有木有,只有两个包一共涉及三个类,就可以测试quartz的大部分功能了。不要着急代码,看完包结构,先讲解一下quartz使用的几个核心API:
- Job:顾名思义,就是我们要定时调度的作业,也是我们使用quartz主要要执行的程序。
- JobDetail:用于定义作业的实例。
- JobBuilder:用于定义/构建JobDetail实例。
- Trigger(即触发器):定义执行给定作业的计划的组件。
- TriggerBuilder:用于定义/构建触发器实例。
- Scheduler:与调度程序交互的主要API。
可以看出来,整个定时调度,可以归类为三部分,
第一部分是Job
自定义一个Job类要实现Job接口,重写execute()方法,方法体的内部就是要调度执行的程序,方法参数为JobExecutionContext实例,为Job调度的上下文对象,可以从中拿出对应的触发器,JobDetail等一些调度的详细信息。废话不多说直接上Job的代码。
package job;
import org.quartz.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
/**
* @author fengjiale
* @create 2019-08-26 16:03
* @desc job
**/
public class HelloJob implements Job {
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//打印当前的执行时间 例如 2019-08-23 00:00:00
Date date = new Date();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("现在的时间是:"+ sf.format(date));
//根据上下文,拿出调度程序的JobDetail的信息
JobDetail jobDetail = jobExecutionContext.getJobDetail();
System.out.println(jobDetail.getKey());
Trigger trigger = jobExecutionContext.getTrigger();
System.out.println(trigger.getKey());
//具体的业务逻辑
System.out.println("Hello Quartz");
}
}
上述Job中,打印了执行这个Job的当前时间,顺便获取了Job调度上下文中的JobDetail对象和Trigger对象,拿出分别标识两个对象各自的key进行了输出。JobDetail对象是在将job加入scheduler前,由客户端程序(你的程序)创建的。它包含job的各种属性设置,以及用于存储job实例状态信息的JobDataMap。
第二部分是Trigger
Trigger用于触发Job的执行。当你准备调度一个job时,你创建一个Trigger的实例,然后设置调度相关的属性。Trigger也有一个相关联的JobDataMap,用于给Job传递一些触发相关的参数。Quartz自带了各种不同类型的Trigger,最常用的主要是SimpleTrigger和CronTrigger。
- SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2015年1月13日的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。SimpleTrigger的属性包括:开始时间、结束时间、重复次数以及重复的间隔。
SimpleTrigger trigger = (SimpleTrigger)newTrigger()
.withIdentity("name1","group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
.build();
上述代码创建了一个SimpleTrigger, 这个触发器将在现在触发,并且每两个小时触发一次,无限次重复。
- CronTrigger通常比Simple Trigger更有用,如果您需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。使用CronTrigger,您可以指定号时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。和SimpleTrigger一样,CronTrigger有一个startTime,它指定何时生效,以及一个(可选的)endTime,用于指定何时停止计划。
Cron Expressions
Cron-Expressions用于配置CronTrigger的实例。Cron Expressions是由七个子表达式组成的字符串,用于描述日程表的各个细节。这些子表达式用空格分隔,并表示:
- Seconds 秒
- Minutes 分
- Hours 时
- Day-of-Month 日期
- Month 月份
- Day-of-Week 星期
- Year (optional field) 年
一个完整的Cron-Expressions的例子是字符串“0 0 12?* WED“ - 这意味着”每个星期三下午12:00“。
'*'代表这个字段中的所有可能的值。
'/'字符可用于指定值的增量。例如,如果在“分钟”字段中输入“0/15”,则表示“每隔15分钟,从零开始”。
'?' 字符是允许的日期和星期几字段。用于指定“无特定值”。当您需要在两个字段中的一个字段中指定某个字符而不是另一个字段时,这很有用。
“L”字符允许用于月日和星期几字段。在这两个领域的每一个领域都有不同的含义。例如,“月”字段中的“L”表示“月的最后一天”,如果在本周的某一天使用,它只是意味着“7”或“SAT”。但是如果在星期几的领域中再次使用这个值,就意味着“最后一个月的xxx日”,例如“6L”或“FRIL”都意味着“月的最后一个星期五”。
'#'用于指定本月的“第n个”XXX工作日。例如,“星期几”字段中的“6#3”或“FRI#3”的值表示“本月的第三个星期五”。
Cron Expressions示例
创建一个触发器的表达式,每5分钟就会触发一次
“0 0/5 * * *?”
创建触发器的表达式,分钟的第十秒,每5分钟触发一次,(即上午10:10:10分,上午10:05:10等)。
“10 0/5 * * *?”
在每个星期三和星期五的10:30,11:30,12:30和13:30创建触发器的表达式。
“0 30 10-13?* WED,FRI“
建立CronTrigger的方式为:
CronTrigger trigger = newTrigger()
.withIdentity("name2", "group2")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?"))
.forJob("myJob", "group1")
.build();
所有类型的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号。
为什么既有Job,又有Trigger呢?
这样做有很多好处:
例如,Job被创建后,可以保存在Scheduler中,与Trigger是独立的,同一个Job可以有多个Trigger;这种松耦合的另一个好处是,当与Scheduler中的Job关联的trigger都过期时,可以配置Job稍后被重新调度,而不用重新定义Job;还有,可以随时修改或者替换Trigger,而不用重新定义与之关联的Job。
Key
将Job和Trigger注册到Scheduler时,可以为它们设置key,配置其身份属性。一个Job或Trigger的key由名称(name)和分组(group)组成,可以用于将Job和Trigger放到不同的分组(group)里,然后基于分组进行操作。同一个分组下的Job或Trigger的名称必须唯一。
第三部分Scheduler
走到这一步,我们已经看过了Job和Trigger,那么我们只需要有一个调度程序,将指定的Job和Trigger编织在一起,进行调度,就OK了,因此这里Scheduler我就直接贴代码了。
package scheduler;
import job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
/**
* @author fengjiale
* @create 2019-08-26 16:04
* @desc SimpleTrigger调度
**/
public class HelloScheduler {
public static void main(String[] args) throws SchedulerException {
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("testName1","testGroup1").build();
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("mytrigger").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();
StdSchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = factory.getScheduler();
scheduler.start();
scheduler.scheduleJob(jobDetail,trigger);
}
}
这个是最简单的调度方式,首先用JobDetail包装了Job,指定Job为HelloJob.class,其次创建了一个SimpleTrigger,定义了从现在开启,每两秒触发一次,永远重复下去。然后,用StdSchedulerFactory工厂,拿到Scheduler对象,将JobDetail和Trigger定义进去,调用start()方法,就开始调度了。下面是启动后开头的日志,可以看到,是每两秒执行一次Job的方法。
现在的时间是:2019-08-28 16:03:41
testGroup1.testName1
DEFAULT.mytrigger
Hello Quartz
现在的时间是:2019-08-28 16:03:43
testGroup1.testName1
DEFAULT.mytrigger
Hello Quartz
现在的时间是:2019-08-28 16:03:45
testGroup1.testName1
DEFAULT.mytrigger
Hello Quartz
现在的时间是:2019-08-28 16:03:47
testGroup1.testName1
DEFAULT.mytrigger
Hello Quartz
测试完最简单的功能之后,我们做一个稍微复杂的。既然quartz将Job和Trigger分开,实现了松散耦合,实现了可以在不更换Job的情况下,替换或者更改触发器,那么我们接下来就测试一下这个功能。
package scheduler;
import job.HelloJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
/**
* @author fengjiale
* @create 2019-08-27 11:10
* @desc 调度过程中替换触发器
**/
public class HelloScheduler2 {
public static void main(String[] args) throws SchedulerException {
//首先用CronTrigger调度任务,每隔5秒执行一次Job
final JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("testName","testGroup").usingJobData("name","jiale").build();
CronTrigger trigger1 = TriggerBuilder.newTrigger().withIdentity("testTriggerName1","testTriggerGroup1").withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * ? * *")).build();
StdSchedulerFactory factory = new StdSchedulerFactory();
final Scheduler scheduler = factory.getScheduler();
scheduler.scheduleJob(jobDetail,trigger1);
scheduler.start();
final TriggerKey triggerKey = TriggerKey.triggerKey("testTriggerName1", "testTriggerGroup1");
//重新启动一个线程,等待30秒后创建个SimpleTrigger,是每10秒触发一次,替换这个Trigger
new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000*15);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"另一个线程进来了,要修改触发器啦");
SimpleTrigger trigger2 = TriggerBuilder.newTrigger().withIdentity("testTriggerName2","testTriggerGroup2").withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();
try {
//更换触发器
scheduler.rescheduleJob(triggerKey, trigger2);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}.start();
}
}
上述代码运行的日志如下:
现在的时间是:2019-08-28 16:26:35
testGroup.testName
testTriggerGroup1.testTriggerName1
Hello Quartz
现在的时间是:2019-08-28 16:26:40
testGroup.testName
testTriggerGroup1.testTriggerName1
Hello Quartz
现在的时间是:2019-08-28 16:26:45
testGroup.testName
testTriggerGroup1.testTriggerName1
Hello Quartz
现在的时间是:2019-08-28 16:26:50
testGroup.testName
testTriggerGroup1.testTriggerName1
Hello Quartz
Thread-0另一个线程进来了,要修改触发器啦
现在的时间是:2019-08-28 16:26:50
testGroup.testName
testTriggerGroup2.testTriggerName2
Hello Quartz
现在的时间是:2019-08-28 16:27:00
testGroup.testName
testTriggerGroup2.testTriggerName2
Hello Quartz
现在的时间是:2019-08-28 16:27:10
testGroup.testName
testTriggerGroup2.testTriggerName2
Hello Quartz
可以清楚的看到,刚开始每5秒触发一次,后面更换了触发器之后,每十秒触发一次,并且第二个触发器的TriggerKey的值也跟第一个输出的不同。
==========================================开始分割线===============================================
到这里,quartz的基本使用,已经全部结束了,有什么疑问或者想要了解更多的小伙伴,可以评论留言,我会第一时间回复。
&spm=1001.2101.3001.5002&articleId=100099025&d=1&t=3&u=41e0d219a1064573b4445eaefb18a9fe)
2193





