1. 概述
Quartz 是一个纯 Java 开发的分布式任务调度器,具有简单灵活的特点。
通过它我们可以创建很复制的调度任务,如每天运行的任务、每隔一个星期五晚上7:30运行的任务,或仅在每月的最后一天运行的任务。
2. Maven 依赖
我们需要添加下面的Maven依赖:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
最新版本请检查Maven中央仓库.
3. Quartz API接口
Scheduler - 调度器是Quartz框架的核心. 负责管理我们程序的运行时环境。
为同时运行多个作业,Quartz 依赖一组线程池管理组件,启动时,框架会初始化一组工作线程,供 Scheduler 用来执行作业。
Quartz 核心API包括:
- Scheduler – 与框架调度交互的主要接口
- Job – 我们希望执行的作业需要实现的接口
- JobDetail – 用于定于作业实例
- Trigger – 触发器,决定作业时间
- JobBuilder – 用于构建JobDetail实例 which define instances of Jobs
- TriggerBuilder – 用于构建触发器实例
下面我们详细看下每个组件
4. Scheduler 调度器
使用 Scheduler 前, 我们需要先使用SchedulerFactory工程创建实例:
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
一个 Scheduler 的生命周期由其 SchedulerFactory 创建开始,以shutdown()方法调用为止。创建后,Scheduler 接口可用于添加、删除和查询作业和触发器,以及执行其他与调度相关的操作(例如暂停触发器)。
最后,Scheduler在start()方法启动后,才会对任何触发器采取行动。
scheduler.start();
5. Job 作业
要执行的作业需要实现 Job 接口,只有一个方法:
public class SimpleJob implements Job {
public void execute(JobExecutionContext arg0) throws JobExecutionException {
System.out.println("这是一个 quartz 作业!");
}
}
当作业被调度器调度时候,execute() 方法会被执行了。
JobExecutionContext 参数为作业实例提供了有关其运行时环境的信息、执行该作业的 Scheduler 的句柄、触发执行的 Trigger 的句柄、作业的 JobDetail 对象以及一些其他对象。
JobDetail 对象是在将 Job 添加到 Scheduler 时由 Quartz client创建的。它本质上是作业实例的定义:
JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("myJob", "group1")
.build();
该对象还可包含 Job 的各种属性设置,以及 JobDataMap,可用于存储我们的作业类的给定实例的状态信息,下节详细说明。
5.1. JobDataMap
The JobDataMap is used to hold any amount of data objects that we wish to make available to the job instance when it executes. JobDataMap is an implementation of the Java Map interface and has some added convenience methods for storing and retrieving data of primitive types.
JobDataMap 用于保存我们希望在任务执行时提供给该Job实例的数据对象。JobDataMap 实现了 Map 接口,并增加了一些方便的方法来存储和检索原始类型的数据。
例如下面实例,我们在构建JobDetail时添加额外数据
JobDetail job = newJob(SimpleJob.class)
.withIdentity("myJob", "group1")
.usingJobData("jobSays", "Hello World!")
.usingJobData("myFloatValue", 3.141f)
.build();
在Job执行时,获取我们添加的数据:
public class SimpleJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
System.out.println("Job says: " + jobSays + ", and val is: " + myFloatValue);
}
}
我们可以在 Job 类上添加与JobDataMap key同名的setter方法,这样JobFactory在实例化Job时自动调用这些setter方法,从而避免我们显式地从map中获取需要的值。
6. Trigger 触发器
触发器对象用于触发作业的执行。
当我们想要调度一个作业时,我们需要实例化一个触发器并设置执行时间等这些参数。
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
触发器同样也可关联一个 JobDataMap。
针对不同的调度需求,有不同类型的触发器。每种触发器都有不同的 TriggerKey 属性来跟踪其身份。通用属性:
There are different types of triggers for different scheduling needs. Each one has different TriggerKey properties for tracking their identities. However, some other properties are common to all trigger types:
- jobKey属性表示触发器触发时应执行的作业的标识。
- startTime 属性表示触发器的计划首次生效的时间。该值是一个 Date 对象,它定义给定日历日期的某个时刻。对于某些触发器类型,触发器会在给定的开始时间触发。对于其他触发器类型,它只是标记计划应开始的时间。
- endTime 属性表示应何时取消触发器的计划。
Quartz 内置了几种不同的触发器类型,但最常用的是 SimpleTrigger 和 CronTrigger。
6.1. Priority 优先级
Sometimes, when we have many triggers, Quartz may not have enough resources to immediately fire all of the jobs are scheduled to fire at the same time. In this case, we may want to control which of our triggers gets available first. This is exactly what the priority property on a trigger is used for.
For example, when ten triggers are set to fire at the same time and merely four worker threads are available, the first four triggers with the highest priority will be executed first. When we do not set a priority on a trigger, it uses a default priority of five. Any integer value is allowed as a priority, positive or negative.
In the example below, we have two triggers with a different priority. If there aren’t enough resources to fire all the triggers at the same time, triggerA will be the first one to be fired:
Trigger triggerA = TriggerBuilder.newTrigger()
.withIdentity("triggerA", "group1")
.startNow()
.withPriority(15)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
Trigger triggerB = TriggerBuilder.newTrigger()
.withIdentity("triggerB", "group1")
.startNow()
.withPriority(10)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(20)
.repeatForever())
.build();
6.2. Misfire Instructions
A misfire occurs if a persistent trigger misses its firing time because of the Scheduler being shut down, or in case there are no available threads in Quartz’s thread pool.
The different trigger types have different misfire instructions available. By default, they use a smart policy instruction. When the scheduler starts, it searches for any persistent triggers that have misfired. After that, it updates each of them based on their individually configured misfire instructions.
Let’s take a look at the examples below:
Trigger misFiredTriggerA = TriggerBuilder.newTrigger()
.startAt(DateUtils.addSeconds(new Date(), -10))
.build();
Trigger misFiredTriggerB = TriggerBuilder.newTrigger()
.startAt(DateUtils.addSeconds(new Date(), -10))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withMisfireHandlingInstructionFireNow())
.build();
We have scheduled the trigger to run 10 seconds ago (so it is 10 seconds late by the time it is created) to simulate a misfire, e.g. because the scheduler was down or didn’t have a sufficient amount of worker threads available. Of course, in a real-world scenario, we would never schedule triggers like this.
In the first trigger (misFiredTriggerA) no misfire handling instructions are set. Hence a called smart policy is used in that case and is called: withMisfireHandlingInstructionFireNow(). This means that the job is executed immediately after the scheduler discovers the misfire.
The second trigger explicitly defines what kind of behavior we expect when misfiring occurs. In this example, it just happens to be the same smart policy.
6.3. SimpleTrigger
SimpleTrigger is used for scenarios in which we need to execute a job at a specific moment in time. This can either be exactly once or repeatedly at specific intervals.
An example could be to fire a job execution at exactly 12:20:00 AM on January 13, 2018. Similarly, we can start at that time, and then five more times, every ten seconds.
In the code below, the date myStartTime has previously been defined and is used to build a trigger for one particular timestamp**:**
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime)
.forJob("job1", "group1")
.build();
Next, let’s build a trigger for a specific moment in time, then repeating every ten seconds ten times:
SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity("trigger2", "group1")
.startAt(myStartTime)
.withSchedule(simpleSchedule()
.withIntervalInSeconds(10)
.withRepeatCount(10))
.forJob("job1")
.build();
6.4. CronTrigger
The CronTrigger is used when we need schedules based on calendar-like statements. For example, we can specify firing-schedules such as every Friday at noon or every weekday at 9:30 am.
Cron-Expressions are used to configure instances of CronTrigger. These expressions consist of Strings that are made up of seven sub-expressions. We can read more about Cron-Expressions here.
In the example below, we build a trigger that fires every other minute between 8 am and 5 pm, every day:
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 8-17 * * ?"))
.forJob("myJob", "group1")
.build();
7. 总结
In this article, we have shown how to build a Scheduler to trigger a Job. We also saw some of the most common trigger options used: SimpleTrigger and CronTrigger.
Quartz can be used to create simple or complex schedules for executing dozens, hundreds, or even more jobs. More information on the framework can be found on the main website.
The source code of the examples can be found over on GitHub.