Introduction to Quartz – 石英简介

最后修改: 2017年 6月 19日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Quartz is an open source job-scheduling framework written entirely in Java and designed for use in both J2SE and J2EE applications. It offers great flexibility without sacrificing simplicity.

Quartz是一个完全用Java编写的开源工作调度框架,旨在用于J2SEJ2EE应用程序。它提供了极大的灵活性,同时又不失简单性。

You can create complex schedules for executing any job. Examples are e.g. tasks that run daily, every other Friday at 7:30 p.m. or only on the last day of every month.

你可以为执行任何工作创建复杂的时间表。例如,每天运行的任务,每隔一个星期五晚上7:30运行的任务,或者只在每个月的最后一天运行的任务。

In this article, we’ll take a look at elements to build a job with the Quartz API. For an introduction in combination with Spring, we recommend Scheduling in Spring with Quartz.

在这篇文章中,我们将看一下用Quartz API构建作业的元素。关于与Spring结合的介绍,我们推荐Scheduling in Spring with Quartz>。

2. Maven Dependencies

2.Maven的依赖性

We need to add the following dependency to the pom.xml:

我们需要在pom.xml中添加以下依赖关系:</em

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
</dependency>

The latest version can be found in the Maven Central repository.

最新版本可在Maven Central资源库中找到。

3. The Quartz API

3.Quartz的API

The heart of the framework is the Scheduler. It is responsible for managing the runtime environment for our application.

该框架的核心是Scheduler。它负责管理我们应用程序的运行环境。

To ensure scalability, Quartz is based on a multi-threaded architecture. When started, the framework initializes a set of worker threads that are used by the Scheduler to execute Jobs.

为了确保可扩展性,Quartz是基于多线程架构的。启动时,该框架初始化一组工作线程,由Scheduler来执行Job

This is how the framework can run many Jobs concurrently. It also relies on a loosely coupled set of ThreadPool management components for managing the thread environment.

这就是该框架如何能够并发地运行许多Job。它还依赖一套松散耦合的ThreadPool管理组件来管理线程环境。

The key interfaces of the API are:

API的关键接口是。

  • Scheduler – the primary API for interacting with the scheduler of the framework
  • Job – an interface to be implemented by components that we wish to have executed
  • JobDetail – used to define instances of Jobs
  • Trigger – a component that determines the schedule upon which a given Job will be performed
  • JobBuilder – used to build JobDetail instances, which define instances of Jobs
  • TriggerBuilder – used to build Trigger instances

Let’s take a look at each one of those components.

让我们来看看这些组件中的每一个。

4. Scheduler

4.调度器

Before we can use the Scheduler, it needs to be instantiated. To do this, we can use the factory SchedulerFactory:

在我们使用Scheduler之前,它需要被实例化。要做到这一点,我们可以使用工厂SchedulerFactory

SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();

A Scheduler’s life-cycle is bounded by its creation, via a SchedulerFactory and a call to its shutdown() method. Once created the Scheduler interface can be used to add, remove, and list Jobs and Triggers, and perform other scheduling-related operations (such as pausing a trigger).

一个Scheduler的生命周期以其创建为界限,通过SchedulerFactory和对其shutdown()方法的调用。一旦创建了Scheduler接口,就可以用来添加、删除和列出JobTriggers,并执行其他与调度相关的操作(例如暂停一个触发器)。

However, the Scheduler will not act on any triggers until it has been started with the start() method:

然而,在用start()方法启动之前,Scheduler将不会对任何触发器采取行动。

scheduler.start();

5. Jobs

5.乔布斯

A Job is a class that implements the Job interface. It has only one simple method:

一个Job是一个实现Job界面的类。它只有一个简单的方法。

public class SimpleJob implements Job {
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        System.out.println("This is a quartz job!");
    }
}

When the Job’s trigger fires, the execute() method gets invoked by one of the scheduler’s worker threads.

Job的触发器启动时,execute()方法会被调度器的一个工作线程调用。

The JobExecutionContext object that is passed to this method provides the job instance, with information about its runtime environment, a handle to the Scheduler that executed it, a handle to the Trigger that triggered the execution, the job’s JobDetail object, and a few other items.

传递给该方法的JobExecutionContext对象提供了作业实例,包括关于其运行环境的信息、执行它的Scheduler的句柄、触发执行的Trigger的句柄、作业的JobDetail对象,以及其他一些项目。

The JobDetail object is created by the Quartz client at the time the Job is added to the Scheduler. It is essentially the definition of the job instance:

JobDetail对象是由Quartz客户端在Job被添加到Scheduler时创建。它基本上是工作实例的定义

JobDetail job = JobBuilder.newJob(SimpleJob.class)
  .withIdentity("myJob", "group1")
  .build();

This object may also contain various property settings for the Job, as well as a JobDataMap, which can be used to store state information for a given instance of our job class.

这个对象还可能包含Job的各种属性设置,以及一个JobDataMap,它可以用来存储我们工作类的一个特定实例的状态信息。

5.1. 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用于保存任何数量的数据对象,我们希望在作业实例执行时将其提供给它。JobDataMap是Java Map接口的一个实现,并且有一些额外的方便方法用于存储和检索原始类型的数据。

Here’s an example of putting data into the JobDataMap while building the JobDetail, before adding the job to the scheduler:

下面是一个在建立JobDetail时将数据放入JobDataMap的例子,在将作业添加到调度器之前。

JobDetail job = newJob(SimpleJob.class)
  .withIdentity("myJob", "group1")
  .usingJobData("jobSays", "Hello World!")
  .usingJobData("myFloatValue", 3.141f)
  .build();

And here is an example of how to access these data during the job’s execution:

下面是一个例子,说明如何在作业执行过程中访问这些数据。

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);
    } 
}

The above example will print “Job says Hello World!, and val is 3.141”.

上面的例子将打印 “Job说Hello World!,Val是3.141″。

We can also add setter methods to our job class that corresponds to the names of keys in the JobDataMap.

我们还可以在我们的工作类中添加与JobDataMap中的键名相对应的setter方法。

If we do this, Quartz’s default JobFactory implementation automatically calls those setters when the job is instantiated, thus preventing the need to explicitly get the values out of the map within our execute method.

如果我们这样做,Quartz的默认JobFactory实现会在作业实例化时自动调用这些设置器,从而避免在执行方法中明确地从地图中获取数值。

6. Triggers

6.触发因素

Trigger objects are used to trigger the execution of Jobs.

Trigger对象被用来触发Job的执行。

When we wish to schedule a Job, we need to instantiate a trigger and adjust its properties to configure our scheduling requirements:

当我们希望安排一个Job时,我们需要实例化一个触发器并调整其属性以配置我们的调度要求。

Trigger trigger = TriggerBuilder.newTrigger()
  .withIdentity("myTrigger", "group1")
  .startNow()
  .withSchedule(SimpleScheduleBuilder.simpleSchedule()
    .withIntervalInSeconds(40)
    .repeatForever())
  .build();

A Trigger may also have a JobDataMap associated with it. This is useful for passing parameters to a Job that are specific to the executions of the trigger.

一个Trigger也可以有一个JobDataMap与之关联。这对于向Job传递特定于触发器执行的参数很有用。

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:

有不同类型的触发器用于不同的调度需求。每个触发器都有不同的TriggerKey属性来追踪它们的身份。然而,其他一些属性对所有触发器类型都是通用的。

  • The jobKey property indicates the identity of the job that should be executed when the trigger fires.
  • The startTime property indicates when the trigger’s schedule first comes into effect. The value is a java.util.Date object that defines a moment in time for a given calendar date. For some trigger types, the trigger fires at the given start time. For others, it simply marks the time that the schedule should start.
  • The endTime property indicates when the trigger’s schedule should be canceled.

Quartz ships with a handful of different trigger types, but the most commonly used ones are SimpleTrigger and CronTrigger.

Quartz有几种不同的触发器类型,但最常用的是SimpleTriggerCronTrigger

6.1. Priority

6.1.优先权

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.

有时,当我们有许多触发器时,Quartz可能没有足够的资源来立即启动所有的工作,并计划在同一时间启动。在这种情况下,我们可能想控制哪一个触发器先被使用。这正是触发器上的priority属性的用处。

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.

例如,当10个触发器被设置为同时启动,而只有4个工作线程可用时,具有最高优先级的前4个触发器将被首先执行。当我们没有在一个触发器上设置优先级时,它就会使用默认的5级优先级。任何整数值都可以作为优先级,无论是正还是负。

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:

在下面的例子中,我们有两个具有不同优先级的触发器。如果没有足够的资源同时启动所有的触发器,触发器A将是第一个被启动的。

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

6.2.失火说明

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.

如果由于Scheduler被关闭,或者Quartz的线程池中没有可用的线程,持久性触发器错过了其触发时间,就会发生误射。

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.

我们将触发器安排在10秒前运行(所以在它被创建时已经晚了10秒),以模拟误操作,例如,由于调度器停机或没有足够数量的工作线程可用。当然,在真实世界的场景中,我们永远不会这样调度触发器。

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.

在第一个触发器(misFiredTriggerA)中没有设置失火处理指令。因此,在这种情况下使用了一个被调用的智能策略并被调用。withMisfireHandlingInstructionFireNow().这意味着作业在调度器发现失火后立即被执行。

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

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.

SimpleTrigger用于我们需要在特定的时间点上执行一项工作的场景。这可以是精确的一次,也可以是在特定的时间间隔内重复执行。

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.

一个例子可以是在2018年1月13日凌晨12:20:00准时启动一个工作执行。同样,我们可以在那个时间开始,然后再执行五次,每十秒一次。

In the code below, the date myStartTime has previously been defined and is used to build a trigger for one particular timestamp:

在下面的代码中,日期myStartTime之前已经被定义,并被用来建立一个特定时间戳的触发器

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

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.

CronTrigger用于我们需要基于类似日历语句的时间表。例如,我们可以指定诸如每周五中午每个工作日上午9:30的解雇时间表。

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.

Cron-Expressions被用来配置CronTrigger的实例。这些表达式由字符串组成,这些字符串由七个子表达式组成。我们可以在这里阅读更多关于Cron-Expressions的信息

In the example below, we build a trigger that fires every other minute between 8 am and 5 pm, every day:

在下面的例子中,我们建立了一个触发器,在每天早上8点到下午5点之间,每隔一分钟发射一次。

CronTrigger trigger = TriggerBuilder.newTrigger()
  .withIdentity("trigger3", "group1")
  .withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 8-17 * * ?"))
  .forJob("myJob", "group1")
  .build();

7. Conclusion

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.

在这篇文章中,我们展示了如何构建一个Scheduler来触发一个Job。我们还看到了一些最常用的触发器选项。SimpleTriggerCronTrigger

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.

Quartz可用于创建简单或复杂的时间表,以执行几十个、几百个、甚至更多的工作。关于该框架的更多信息可以在主网站上找到。

The source code of the examples can be found over on GitHub.

这些例子的源代码可以在GitHub上找到