Task Execution and Scheduling

  • Trigger

  • CronTrigger

  • TriggerContext

  • Scheduled

  • Async

  • @EnableScheduling

  • @Scheduled

  • @Async

  • task:scheduler

  • task:executor

  • task:scheduled-tasks

Spring 框架提供抽象操作,可通过 TaskExecutorTaskScheduler 接口分别异步执行和计划任务。Spring 还提供对这些接口的实现,支持应用程序服务器环境中的线程池或委派到 CommonJ。最终,在常用接口后面使用这些实现从 Java SE 和 Jakarta EE 环境中抽象出差异。

The Spring Framework provides abstractions for the asynchronous execution and scheduling of tasks with the TaskExecutor and TaskScheduler interfaces, respectively. Spring also features implementations of those interfaces that support thread pools or delegation to CommonJ within an application server environment. Ultimately, the use of these implementations behind the common interfaces abstracts away the differences between Java SE and Jakarta EE environments.

Spring 还提供集成类来支持使用 Quartz Scheduler 进行计划。

Spring also features integration classes to support scheduling with the Quartz Scheduler.

The Spring TaskExecutor Abstraction

Executor 是 JDK 中线程池概念的名称。“executor” 命名归因于一个事实,即无法保证底层实现事实上是一个池。执行器可能是单线程,甚至是同步的。Spring 的抽象层隐藏了 Java SE 和 Jakarta EE 环境中的实现详情。

Executors are the JDK name for the concept of thread pools. The “executor” naming is due to the fact that there is no guarantee that the underlying implementation is actually a pool. An executor may be single-threaded or even synchronous. Spring’s abstraction hides implementation details between the Java SE and Jakarta EE environments.

Spring 的 TaskExecutor 接口与 java.util.concurrent.Executor 接口相同。实际上,最初,它存在的主要原因是为了在使用线程池时抽象掉对 Java 5 的需求。该接口具有一个方法(execute(Runnable task)),它基于线程池的语义和配置接受一个任务执行。

Spring’s TaskExecutor interface is identical to the java.util.concurrent.Executor interface. In fact, originally, its primary reason for existence was to abstract away the need for Java 5 when using thread pools. The interface has a single method (execute(Runnable task)) that accepts a task for execution based on the semantics and configuration of the thread pool.

TaskExecutor 最初是为了为其他需要线程池的 Spring 组件提供抽象而创建的。诸如 ApplicationEventMulticaster、JMS 中的 AbstractMessageListenerContainer 和 Quartz 集成的组件都使用 TaskExecutor 抽象来池化线程。然而,如果你的 Bean 需要线程池行为,你也可以将此抽象用于你的自身需求。

The TaskExecutor was originally created to give other Spring components an abstraction for thread pooling where needed. Components such as the ApplicationEventMulticaster, JMS’s AbstractMessageListenerContainer, and Quartz integration all use the TaskExecutor abstraction to pool threads. However, if your beans need thread pooling behavior, you can also use this abstraction for your own needs.

TaskExecutor Types

Spring 包括许多 TaskExecutor 的预构建实现。你很可能永远不需要实现自己的实现。Spring 提供的变体如下:

Spring includes a number of pre-built implementations of TaskExecutor. In all likelihood, you should never need to implement your own. The variants that Spring provides are as follows:

  • SyncTaskExecutor: This implementation does not run invocations asynchronously. Instead, each invocation takes place in the calling thread. It is primarily used in situations where multi-threading is not necessary, such as in simple test cases.

  • SimpleAsyncTaskExecutor: This implementation does not reuse any threads. Rather, it starts up a new thread for each invocation. However, it does support a concurrency limit that blocks any invocations that are over the limit until a slot has been freed up. If you are looking for true pooling, see ThreadPoolTaskExecutor, later in this list.

  • ConcurrentTaskExecutor: This implementation is an adapter for a java.util.concurrent.Executor instance. There is an alternative (ThreadPoolTaskExecutor) that exposes the Executor configuration parameters as bean properties. There is rarely a need to use ConcurrentTaskExecutor directly. However, if the ThreadPoolTaskExecutor is not flexible enough for your needs, ConcurrentTaskExecutor is an alternative.

  • ThreadPoolTaskExecutor: This implementation is most commonly used. It exposes bean properties for configuring a java.util.concurrent.ThreadPoolExecutor and wraps it in a TaskExecutor. If you need to adapt to a different kind of java.util.concurrent.Executor, we recommend that you use a ConcurrentTaskExecutor instead.

  • DefaultManagedTaskExecutor: This implementation uses a JNDI-obtained ManagedExecutorService in a JSR-236 compatible runtime environment (such as a Jakarta EE application server), replacing a CommonJ WorkManager for that purpose.

从 6.1 开始,ThreadPoolTaskExecutor 提供了一个暂停/恢复功能,并通过 Spring 的生命周期管理来优雅关闭。SimpleAsyncTaskExecutor 上还有一个新的“virtualThreads”选项,它与 JDK 21 的虚拟线程保持一致,以及为 SimpleAsyncTaskExecutor 提供的优雅关闭功能。

As of 6.1, ThreadPoolTaskExecutor provides a pause/resume capability and graceful shutdown through Spring’s lifecycle management. There is also a new "virtualThreads" option on SimpleAsyncTaskExecutor which is aligned with JDK 21’s Virtual Threads, as well as a graceful shutdown capability for SimpleAsyncTaskExecutor as well.

Using a TaskExecutor

Spring 的 TaskExecutor 实现通常与依赖注入一起使用。在以下示例中,我们定义了一个使用 ThreadPoolTaskExecutor 来异步打印一组消息的 Bean:

Spring’s TaskExecutor implementations are commonly used with dependency injection. In the following example, we define a bean that uses the ThreadPoolTaskExecutor to asynchronously print out a set of messages:

  • Java

  • Kotlin

public class TaskExecutorExample {

	private class MessagePrinterTask implements Runnable {

		private String message;

		public MessagePrinterTask(String message) {
			this.message = message;
		}

		public void run() {
			System.out.println(message);
		}
	}

	private TaskExecutor taskExecutor;

	public TaskExecutorExample(TaskExecutor taskExecutor) {
		this.taskExecutor = taskExecutor;
	}

	public void printMessages() {
		for(int i = 0; i < 25; i++) {
			taskExecutor.execute(new MessagePrinterTask("Message" + i));
		}
	}
}
class TaskExecutorExample(private val taskExecutor: TaskExecutor) {

	private inner class MessagePrinterTask(private val message: String) : Runnable {
		override fun run() {
			println(message)
		}
	}

	fun printMessages() {
		for (i in 0..24) {
			taskExecutor.execute(
				MessagePrinterTask(
					"Message$i"
				)
			)
		}
	}
}

如你所见,不是从池中检索线程并自己执行线程,而是将 Runnable 添加到队列中。然后,TaskExecutor 会使用其内部规则来决定在何时运行任务。

As you can see, rather than retrieving a thread from the pool and executing it yourself, you add your Runnable to the queue. Then the TaskExecutor uses its internal rules to decide when the task gets run.

为了配置 TaskExecutor 使用的规则,我们公开了简单的 Bean 属性:

To configure the rules that the TaskExecutor uses, we expose simple bean properties:

  • Java

  • Kotlin

  • Xml

@Bean
ThreadPoolTaskExecutor taskExecutor() {
	ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
	taskExecutor.setCorePoolSize(5);
	taskExecutor.setMaxPoolSize(10);
	taskExecutor.setQueueCapacity(25);
	return taskExecutor;
}

@Bean
TaskExecutorExample taskExecutorExample(ThreadPoolTaskExecutor taskExecutor) {
	return new TaskExecutorExample(taskExecutor);
}
@Bean
fun taskExecutor() = ThreadPoolTaskExecutor().apply {
	corePoolSize = 5
	maxPoolSize = 10
	queueCapacity = 25
}

@Bean
fun taskExecutorExample(taskExecutor: ThreadPoolTaskExecutor) = TaskExecutorExample(taskExecutor)
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
	<property name="corePoolSize" value="5"/>
	<property name="maxPoolSize" value="10"/>
	<property name="queueCapacity" value="25"/>
</bean>

<bean id="taskExecutorExample" class="TaskExecutorExample">
	<constructor-arg ref="taskExecutor"/>
</bean>

The Spring TaskScheduler Abstraction

除了 TaskExecutor 抽象层外,Spring 还具有 TaskScheduler SPI,它提供了多种方法来计划任务以便某个时间点运行。以下清单显示了 TaskScheduler 接口定义:

In addition to the TaskExecutor abstraction, Spring has a TaskScheduler SPI with a variety of methods for scheduling tasks to run at some point in the future. The following listing shows the TaskScheduler interface definition:

public interface TaskScheduler {

	Clock getClock();

	ScheduledFuture schedule(Runnable task, Trigger trigger);

	ScheduledFuture schedule(Runnable task, Instant startTime);

	ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);

	ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);

	ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);

	ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);

最简单的方法是一个名为 schedule 的方法,它只采用一个 Runnable 和一个 Instant。该方法会导致任务在指定时间后运行一次。所有其他方法都能够计划任务重复运行。固定速率和固定延迟方法用于简单,周期性执行,但是接受 Trigger 的方法要灵活得多。

The simplest method is the one named schedule that takes only a Runnable and an Instant. That causes the task to run once after the specified time. All of the other methods are capable of scheduling tasks to run repeatedly. The fixed-rate and fixed-delay methods are for simple, periodic execution, but the method that accepts a Trigger is much more flexible.

Trigger Interface

Trigger 接口基本上受 JSR-236 启发。Trigger 的基本思想是执行时间可能会根据过去的执行结果甚至任意条件来确定。如果这些确定会考虑前一个执行的结果,那么这些信息将在 TriggerContext 中可用。Trigger 接口本身非常简单,如下面的清单所示:

The Trigger interface is essentially inspired by JSR-236. The basic idea of the Trigger is that execution times may be determined based on past execution outcomes or even arbitrary conditions. If these determinations take into account the outcome of the preceding execution, that information is available within a TriggerContext. The Trigger interface itself is quite simple, as the following listing shows:

public interface Trigger {

	Instant nextExecution(TriggerContext triggerContext);
}

TriggerContext 是最重要的部分。它封装了所有相关数据,并且将来如果需要,可以进行扩展。TriggerContext 是一个接口(默认情况下使用 SimpleTriggerContext 实现)。以下清单显示了 Trigger 实现可用的方法。

The TriggerContext is the most important part. It encapsulates all of the relevant data and is open for extension in the future, if necessary. The TriggerContext is an interface (a SimpleTriggerContext implementation is used by default). The following listing shows the available methods for Trigger implementations.

public interface TriggerContext {

	Clock getClock();

	Instant lastScheduledExecution();

	Instant lastActualExecution();

	Instant lastCompletion();
}

Trigger Implementations

Spring 提供了 Trigger 接口的两种实现。最有趣的是 CronTrigger。它支持基于 cron expressions 的任务计划。例如,以下任务计划在每小时过去 15 分钟后运行,但仅限于工作日的 9 点至 5 点的“工作时间”:

Spring provides two implementations of the Trigger interface. The most interesting one is the CronTrigger. It enables the scheduling of tasks based on cron expressions. For example, the following task is scheduled to run 15 minutes past each hour but only during the 9-to-5 "business hours" on weekdays:

scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

另一个实现是接受固定周期、可选初始延迟值和一个布尔值来指示 period 应解释为固定速率还是固定延迟的 PeriodicTrigger。由于 TaskScheduler 接口已定义了用于以固定速率或以固定延迟计划任务的方法,因此应尽可能直接使用这些方法。PeriodicTrigger 实现的价值在于你可以在依赖 Trigger 抽象的组件中使用该实现。例如,允许交替使用周期性触发器、基于 Cron 的触发器,甚至是自定义触发器实现可能很方便。这样的组件可以利用依赖注入,以便你能够在外部配置此类 Triggers,从而轻松修改或扩展它们。

The other implementation is a PeriodicTrigger that accepts a fixed period, an optional initial delay value, and a boolean to indicate whether the period should be interpreted as a fixed-rate or a fixed-delay. Since the TaskScheduler interface already defines methods for scheduling tasks at a fixed rate or with a fixed delay, those methods should be used directly whenever possible. The value of the PeriodicTrigger implementation is that you can use it within components that rely on the Trigger abstraction. For example, it may be convenient to allow periodic triggers, cron-based triggers, and even custom trigger implementations to be used interchangeably. Such a component could take advantage of dependency injection so that you can configure such Triggers externally and, therefore, easily modify or extend them.

TaskScheduler implementations

与 Spring 的 TaskExecutor 抽象层一样,TaskScheduler 设置的主要优点在于将应用程序的调度需求与部署环境分离。在将应用程序部署到应用程序服务器的环境中(应用程序本身不应直接创建线程)时,此抽象级别尤为重要。对于此类方案,Spring 提供了一个 DefaultManagedTaskScheduler,该调度器在 Jakarta EE 环境中委派给 JSR-236 ManagedScheduledExecutorService

As with Spring’s TaskExecutor abstraction, the primary benefit of the TaskScheduler arrangement is that an application’s scheduling needs are decoupled from the deployment environment. This abstraction level is particularly relevant when deploying to an application server environment where threads should not be created directly by the application itself. For such scenarios, Spring provides a DefaultManagedTaskScheduler that delegates to a JSR-236 ManagedScheduledExecutorService in a Jakarta EE environment.

只要不需要外部线程管理,更简单的替代方案是应用程序中安装一个本地 ScheduledExecutorService,它可以通过 Spring 的 ConcurrentTaskScheduler 进行调整。为了方便起见,Spring 还提供了一个 ThreadPoolTaskScheduler,它在内部委派给 ScheduledExecutorService 来提供类似于 ThreadPoolTaskExecutor 的通用 Bean 样式配置。这些变体对于宽松的应用程序服务器环境中的本地嵌入式线程池安装同样非常有效,尤其是在 Tomcat 和 Jetty 上。

Whenever external thread management is not a requirement, a simpler alternative is a local ScheduledExecutorService setup within the application, which can be adapted through Spring’s ConcurrentTaskScheduler. As a convenience, Spring also provides a ThreadPoolTaskScheduler, which internally delegates to a ScheduledExecutorService to provide common bean-style configuration along the lines of ThreadPoolTaskExecutor. These variants work perfectly fine for locally embedded thread pool setups in lenient application server environments, as well — in particular on Tomcat and Jetty.

从 6.1 开始,ThreadPoolTaskScheduler 通过 Spring 的生命周期管理提供了暂停/恢复功能和优雅的关闭功能。还有一个称为 SimpleAsyncTaskScheduler 的新选项,该选项与 JDK 21 的 Virtual Threads 对齐,它使用单个调度程序线程,但为每个计划的任务执行启动一个新线程(除了固定延迟任务的所有任务都在单个调度程序线程上运行,因此对于此基于虚拟线程的选项,建议使用固定速率和 cron 触发器)。

As of 6.1, ThreadPoolTaskScheduler provides a pause/resume capability and graceful shutdown through Spring’s lifecycle management. There is also a new option called SimpleAsyncTaskScheduler which is aligned with JDK 21’s Virtual Threads, using a single scheduler thread but firing up a new thread for every scheduled task execution (except for fixed-delay tasks which all operate on a single scheduler thread, so for this virtual-thread-aligned option, fixed rates and cron triggers are recommended).

Annotation Support for Scheduling and Asynchronous Execution

Spring 为任务计划和异步方法执行提供注释支持。

Spring provides annotation support for both task scheduling and asynchronous method execution.

Enable Scheduling Annotations

要启用对 @Scheduled@Async 注释的支持,可以将 @EnableScheduling@EnableAsync 添加到您的一个 @Configuration 类,或 <task:annotation-driven> 元素,如下面的示例所示:

To enable support for @Scheduled and @Async annotations, you can add @EnableScheduling and @EnableAsync to one of your @Configuration classes, or <task:annotation-driven> element, as the following example shows:

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfiguration {
}
@Configuration
@EnableAsync
@EnableScheduling
class SchedulingConfiguration
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/task
	   https://www.springframework.org/schema/task/spring-task.xsd">

	<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
	<task:executor id="myExecutor" pool-size="5"/>
	<task:scheduler id="myScheduler" pool-size="10"/>
</beans>

你可以挑选并选择与你的应用程序关联的注释。例如,如果你仅需要 @Scheduled 的支持,那么你可以省略 @EnableAsync。为了进行更精细的控制,你还可以额外地实现 SchedulingConfigurer 接口、AsyncConfigurer 接口或两者。请参见 SchedulingConfigurerAsyncConfigurer javadoc 以获得详细信息。

You can pick and choose the relevant annotations for your application. For example, if you need only support for @Scheduled, you can omit @EnableAsync. For more fine-grained control, you can additionally implement the SchedulingConfigurer interface, the AsyncConfigurer interface, or both. See the SchedulingConfigurer and AsyncConfigurer javadoc for full details.

请注意,在之前的 XML 中,提供了一个执行程序引用来处理那些与带有 @Async 注释的方法相对应任务,而调度程序引用是用来管理那些用 @Scheduled 注释的方法的。

Note that, with the preceding XML, an executor reference is provided for handling those tasks that correspond to methods with the @Async annotation, and the scheduler reference is provided for managing those methods annotated with @Scheduled.

用于处理 @Async 注释的默认建议模式是 proxy,它仅允许通过代理拦截调用。此方式不能拦截同一类中本地调用。对于更高级的拦截模式,考虑结合编译时或加载时编织切换到 aspectj 模式。

The default advice mode for processing @Async annotations is proxy which allows for interception of calls through the proxy only. Local calls within the same class cannot get intercepted that way. For a more advanced mode of interception, consider switching to aspectj mode in combination with compile-time or load-time weaving.

The @Scheduled annotation

您可以将 @Scheduled 注释添加到方法中,以及触发器元数据。例如,以下方法每隔五秒(5000 毫秒)使用固定延迟触发一次,这意味着该周期从每次前一次调用的完成时间开始计算。

You can add the @Scheduled annotation to a method, along with trigger metadata. For example, the following method is invoked every five seconds (5000 milliseconds) with a fixed delay, meaning that the period is measured from the completion time of each preceding invocation.

@Scheduled(fixedDelay = 5000)
public void doSomething() {
	// something that should run periodically
}

默认情况下,毫秒将用作固定延迟、固定速率和初始延迟值的单位。如果您希望使用其他时间单位(如秒或分钟),可以通过 @Scheduled 中的 timeUnit 属性对其进行配置。

By default, milliseconds will be used as the time unit for fixed delay, fixed rate, and initial delay values. If you would like to use a different time unit such as seconds or minutes, you can configure this via the timeUnit attribute in @Scheduled.

例如,也可以将前一个示例写成如下形式。

For example, the previous example can also be written as follows.

@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
	// something that should run periodically
}

如果您需要固定速率执行,则可以在注释中使用 fixedRate 属性。以下方法每隔五秒(从每次调用的连续开始时间测量)触发一次:

If you need a fixed-rate execution, you can use the fixedRate attribute within the annotation. The following method is invoked every five seconds (measured between the successive start times of each invocation):

@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
	// something that should run periodically
}

对于固定延迟和固定速率任务,您可以通过指示在方法的首次执行前等待的时间量来指定初始延迟,如下面的 fixedRate 示例所示:

For fixed-delay and fixed-rate tasks, you can specify an initial delay by indicating the amount of time to wait before the first execution of the method, as the following fixedRate example shows:

@Scheduled(initialDelay = 1000, fixedRate = 5000)
public void doSomething() {
	// something that should run periodically
}

对于一次性任务,您可以仅仅通过指示在方法的预期执行前等待的时间量来指定初始延迟:

For one-time tasks, you can just specify an initial delay by indicating the amount of time to wait before the intended execution of the method:

@Scheduled(initialDelay = 1000)
public void doSomething() {
	// something that should run only once
}

如果简单的周期性计划不够明确,你可以提供一个 cron expression。以下示例仅在工作日运行:

If simple periodic scheduling is not expressive enough, you can provide a cron expression. The following example runs only on weekdays:

@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
	// something that should run on weekdays only
}

您还可以使用 zone 属性指定从中解析 cron 表达式的时区。

You can also use the zone attribute to specify the time zone in which the cron expression is resolved.

请注意,要调度的那些方法必须具有 void 返回值,并且不得接受任何参数。如果方法需要与应用程序上下文的其他对象进行交互,则通常会通过依赖项注入提供这些对象。

Notice that the methods to be scheduled must have void returns and must not accept any arguments. If the method needs to interact with other objects from the application context, those would typically have been provided through dependency injection.

@Scheduled 可用作可重复注释。如果在同一个方法上找到多个计划声明,则会独立处理它们中的每一个,并为它们中的每一个触发一个单独的触发器。因此,这样的并置计划可能会重叠,并行或立即连续执行多次。请确保您指定的 cron 表达式等不会意外重叠。

@Scheduled can be used as a repeatable annotation. If several scheduled declarations are found on the same method, each of them will be processed independently, with a separate trigger firing for each of them. As a consequence, such co-located schedules may overlap and execute multiple times in parallel or in immediate succession. Please make sure that your specified cron expressions etc do not accidentally overlap.

从 Spring Framework 4.3 开始,@Scheduled 方法受支持于任何范围的 bean。

As of Spring Framework 4.3, @Scheduled methods are supported on beans of any scope.

确保在运行时不初始化同一 @Scheduled 注解类的多个实例,除非希望计划对每个此类实例进行回调。与此相关,确保不对用 @Scheduled 注解并作为常规 Spring Bean 注册到容器的 Bean 类使用 @Configurable。否则,将进行双重初始化(一次通过容器,一次通过 @Configurable 方面),从而导致每个 @Scheduled 方法被调用两次。

Make sure that you are not initializing multiple instances of the same @Scheduled annotation class at runtime, unless you do want to schedule callbacks to each such instance. Related to this, make sure that you do not use @Configurable on bean classes that are annotated with @Scheduled and registered as regular Spring beans with the container. Otherwise, you would get double initialization (once through the container and once through the @Configurable aspect), with the consequence of each @Scheduled method being invoked twice.

The @Scheduled annotation on Reactive methods or Kotlin suspending functions

从 Spring Framework 6.1 开始,@Scheduled 方法还受支持于多种类型的响应方法:

As of Spring Framework 6.1, @Scheduled methods are also supported on several types of reactive methods:

  • methods with a Publisher return type (or any concrete implementation of Publisher) like in the following example:

@Scheduled(fixedDelay = 500)
public Publisher<Void> reactiveSomething() {
	// return an instance of Publisher
}
  • methods with a return type that can be adapted to Publisher via the shared instance of the ReactiveAdapterRegistry, provided the type supports deferred subscription like in the following example:

@Scheduled(fixedDelay = 500)
public Single<String> rxjavaNonPublisher() {
	return Single.just("example");
}

CompletableFuture 类是可以典型地适应到 Publisher 但不支持延迟订阅的类型的示例。它在注册表中的 ReactiveAdapter 通过带有 getDescriptor().isDeferred() 方法返回 false 表示这一点。

The CompletableFuture class is an example of a type that can typically be adapted to Publisher but doesn’t support deferred subscription. Its ReactiveAdapter in the registry denotes that by having the getDescriptor().isDeferred() method return false.

  • Kotlin suspending functions, like in the following example:

@Scheduled(fixedDelay = 500)
suspend fun something() {
	// do something asynchronous
}
  • methods that return a Kotlin Flow or Deferred instance, like in the following example:

@Scheduled(fixedDelay = 500)
fun something(): Flow<Void> {
	flow {
		// do something asynchronous
	}
}

所有这些类型的办法都必须在没有参数的情况下宣告。在 Kotlinsuspending 函数的情况下,kotlinx.coroutines.reactor 桥接器也必须存在,以允许框架调用一个 suspending 函数作为 Publisher

All these types of methods must be declared without any arguments. In the case of Kotlin suspending functions, the kotlinx.coroutines.reactor bridge must also be present to allow the framework to invoke a suspending function as a Publisher.

Spring 框架将为带注释的方法获取一个 Publisher 一次,并将会安排一个 Runnable,它将在其中订阅所述 Publisher。这些内部的规则订阅根据相应的 cron/fixedDelay/fixedRate 配置发生。

The Spring Framework will obtain a Publisher for the annotated method once and will schedule a Runnable in which it subscribes to said Publisher. These inner regular subscriptions occur according to the corresponding cron/fixedDelay/fixedRate configuration.

如果 Publisher 发射一个或多个 onNext 信号,它们将被忽略并丢弃(以与同步 @Scheduled 方法的返回值被忽略相同的方式)。

If the Publisher emits onNext signal(s), these are ignored and discarded (the same way return values from synchronous @Scheduled methods are ignored).

在下例中,Flux 每 5 秒发出 onNext("Hello")onNext("World"),但是这些值没有被使用:

In the following example, the Flux emits onNext("Hello"), onNext("World") every 5 seconds, but these values are unused:

@Scheduled(initialDelay = 5000, fixedRate = 5000)
public Flux<String> reactiveSomething() {
	return Flux.just("Hello", "World");
}

如果 Publisher 发射一个 onError 信号,它将在 WARN 级别被记录并恢复。由于 Publisher 实例的异步和惰性特性,异常不会从 Runnable 任务中被抛出:这意味着 ErrorHandler 合同不会涉及响应式方法。

If the Publisher emits an onError signal, it is logged at WARN level and recovered. Because of the asynchronous and lazy nature of Publisher instances, exceptions are not thrown from the Runnable task: this means that the ErrorHandler contract is not involved for reactive methods.

因此,尽管有错误,后续计划订阅仍会发生。

As a result, further scheduled subscription occurs despite the error.

在下例中,Mono 订阅在前 5 秒内失败了 2 次。然后订阅开始生效,每 5 秒向标准输出打印一条消息:

In the following example, the Mono subscription fails twice in the first five seconds. Then subscriptions start succeeding, printing a message to the standard output every five seconds:

@Scheduled(initialDelay = 0, fixedRate = 5000)
public Mono<Void> reactiveSomething() {
	AtomicInteger countdown = new AtomicInteger(2);

	return Mono.defer(() -> {
		if (countDown.get() == 0 || countDown.decrementAndGet() == 0) {
			return Mono.fromRunnable(() -> System.out.println("Message"));
		}
		return Mono.error(new IllegalStateException("Cannot deliver message"));
	})
}

当销毁带注释的 bean 或关闭应用程序上下文时,Spring 框架会取消计划任务,其中包括下一个计划的 Publisher 订阅,以及任何仍然处于活动状态的过去订阅(例如对于长期运行的发布者,甚至无限的发布者)。

When destroying the annotated bean or closing the application context, Spring Framework cancels scheduled tasks, which includes the next scheduled subscription to the Publisher as well as any past subscription that is still currently active (e.g. for long-running publishers or even infinite publishers).

The @Async annotation

您可以在方法上提供 @Async 注释,以便异步调用该方法。换句话说,在调用时,调用者会立即返回,而方法的实际执行发生在已提交到 Spring TaskExecutor 的任务中。在最简单的情况下,您可以将此注释应用到返回 void 的方法,如下例所示:

You can provide the @Async annotation on a method so that invocation of that method occurs asynchronously. In other words, the caller returns immediately upon invocation, while the actual execution of the method occurs in a task that has been submitted to a Spring TaskExecutor. In the simplest case, you can apply the annotation to a method that returns void, as the following example shows:

@Async
void doSomething() {
	// this will be run asynchronously
}

与使用 @Scheduled 注释注释的方法不同,这些方法可以期待参数,因为它们是由调用者在运行时以“正常”的方式调用的,而不是由容器管理的计划任务调用的。例如,以下代码是 @Async 注释的合法应用:

Unlike the methods annotated with the @Scheduled annotation, these methods can expect arguments, because they are invoked in the “normal” way by callers at runtime rather than from a scheduled task being managed by the container. For example, the following code is a legitimate application of the @Async annotation:

@Async
void doSomething(String s) {
	// this will be run asynchronously
}

即使是返回一个值的方法也可以异步调用。但是,这样的方法必须具有 Future 类型的返回值。这仍然提供了异步执行的好处,以便调用者可以在调用 Future 上的 get() 之前执行其他任务。以下示例展示了如何在返回一个值的方法上使用 @Async

Even methods that return a value can be invoked asynchronously. However, such methods are required to have a Future-typed return value. This still provides the benefit of asynchronous execution so that the caller can perform other tasks prior to calling get() on that Future. The following example shows how to use @Async on a method that returns a value:

@Async
Future<String> returnSomething(int i) {
	// this will be run asynchronously
}

@Async 方法不仅可以声明常规 java.util.concurrent.Future 返回类型,还可以声明 Spring’s org.springframework.util.concurrent.ListenableFuture 或 Spring 4.2 的 java.util.concurrent.CompletableFuture,以便与异步任务进行更丰富的交互,并立即组合进一步的处理步骤。

@Async methods may not only declare a regular java.util.concurrent.Future return type but also Spring’s org.springframework.util.concurrent.ListenableFuture or, as of Spring 4.2, JDK 8’s java.util.concurrent.CompletableFuture, for richer interaction with the asynchronous task and for immediate composition with further processing steps.

您不能将 @Async 与生命周期回调结合使用,例如 @PostConstruct。要异步初始化 Spring bean,您当前必须使用一个单独的初始化 Spring bean,然后在目标上调用 @Async 注释的方法,如下例所示:

You can not use @Async in conjunction with lifecycle callbacks such as @PostConstruct. To asynchronously initialize Spring beans, you currently have to use a separate initializing Spring bean that then invokes the @Async annotated method on the target, as the following example shows:

public class SampleBeanImpl implements SampleBean {

	@Async
	void doSomething() {
		// ...
	}

}

public class SampleBeanInitializer {

	private final SampleBean bean;

	public SampleBeanInitializer(SampleBean bean) {
		this.bean = bean;
	}

	@PostConstruct
	public void initialize() {
		bean.doSomething();
	}

}

@Async 没有直接对应的 XML 等效项,因为此类方法一开始就应设计为异步执行,而不是外部重新声明为异步。但是,您可以结合自定义切入点,使用 Spring AOP 手动设置 Spring’s AsyncExecutionInterceptor

There is no direct XML equivalent for @Async, since such methods should be designed for asynchronous execution in the first place, not externally re-declared to be asynchronous. However, you can manually set up Spring’s AsyncExecutionInterceptor with Spring AOP, in combination with a custom pointcut.

Executor Qualification with @Async

默认情况下,当在方法上指定 @Async 时,所使用的执行器是 configured when enabling async support, 即 “annotation-driven” 元素(如果你使用的是 XML)或你的 AsyncConfigurer 实现(如果有)。然而,当你需要表明在执行给定方法时需要使用一个不同于默认执行器的执行器时,可以使用 @Async 注解的 value 属性。以下示例展示了如何这样做:

By default, when specifying @Async on a method, the executor that is used is the one configured when enabling async support, i.e. the “annotation-driven” element if you are using XML or your AsyncConfigurer implementation, if any. However, you can use the value attribute of the @Async annotation when you need to indicate that an executor other than the default should be used when executing a given method. The following example shows how to do so:

@Async("otherExecutor")
void doSomething(String s) {
	// this will be run asynchronously by "otherExecutor"
}

在这种情况下,“otherExecutor”可以是 Spring 容器中任何 Executor bean 的名称,或者它可以是与任何 Executor 关联的限定符的名称(例如,使用 <qualifier> 元素或 Spring 的 @Qualifier 注释指定)。

In this case, "otherExecutor" can be the name of any Executor bean in the Spring container, or it may be the name of a qualifier associated with any Executor (for example, as specified with the <qualifier> element or Spring’s @Qualifier annotation).

Exception Management with @Async

当一个 @Async 方法具有 Future 类型的返回值时,很容易管理在方法执行期间抛出的异常,因为在调用 Future 结果上的 get 时会抛出此异常。但是,对于 void 返回类型,异常不会被捕获且无法被传输。您可以提供一个 AsyncUncaughtExceptionHandler 来处理此类异常。以下示例展示了如何做到这一点:

When an @Async method has a Future-typed return value, it is easy to manage an exception that was thrown during the method execution, as this exception is thrown when calling get on the Future result. With a void return type, however, the exception is uncaught and cannot be transmitted. You can provide an AsyncUncaughtExceptionHandler to handle such exceptions. The following example shows how to do so:

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

	@Override
	public void handleUncaughtException(Throwable ex, Method method, Object... params) {
		// handle exception
	}
}

默认情况下,异常只是被记录。您可以使用 AsyncConfigurer<task:annotation-driven/> XML 元素定义一个自定义 AsyncUncaughtExceptionHandler

By default, the exception is merely logged. You can define a custom AsyncUncaughtExceptionHandler by using AsyncConfigurer or the <task:annotation-driven/> XML element.

The task Namespace

从版本 3.0 开始,Spring 包含了一个 XML 命名空间,用于配置 TaskExecutorTaskScheduler 实例。它还提供了一种方便的方法来配置要使用触发器调度的任务。

As of version 3.0, Spring includes an XML namespace for configuring TaskExecutor and TaskScheduler instances. It also provides a convenient way to configure tasks to be scheduled with a trigger.

The 'scheduler' Element

以下元素使用指定的线程池大小创建 ThreadPoolTaskScheduler 实例:

The following element creates a ThreadPoolTaskScheduler instance with the specified thread pool size:

<task:scheduler id="scheduler" pool-size="10"/>

id 属性提供的值被用作池中线程名称的前缀。scheduler 元素相对简单。如果您没有提供 pool-size 属性,则默认线程池只有一个线程。调度器没有其他配置选项。

The value provided for the id attribute is used as the prefix for thread names within the pool. The scheduler element is relatively straightforward. If you do not provide a pool-size attribute, the default thread pool has only a single thread. There are no other configuration options for the scheduler.

The executor Element

以下内容创建一个 ThreadPoolTaskExecutor 实例:

The following creates a ThreadPoolTaskExecutor instance:

<task:executor id="executor" pool-size="10"/>

previous section 中所示的计划程序一样,为 id 属性提供的值被用作池内线程名称的前缀。就池大小而言,executor 元素支持比 scheduler 元素更多的配置选项。首先,对于 ThreadPoolTaskExecutor 的线程池本身更具可配置性。执行器的线程池不仅仅是单个大小,还可以为核心大小和最大大小设置不同的值。如果你提供一个单一值,则执行器具有固定大小的线程池(核心和最大大小相同)。然而,executor 元素的 pool-size 属性也接受范围的形式 min-max。以下示例设置了一个最小值 5 和一个最大值 25

As with the scheduler shown in the previous section, the value provided for the id attribute is used as the prefix for thread names within the pool. As far as the pool size is concerned, the executor element supports more configuration options than the scheduler element. For one thing, the thread pool for a ThreadPoolTaskExecutor is itself more configurable. Rather than only a single size, an executor’s thread pool can have different values for the core and the max size. If you provide a single value, the executor has a fixed-size thread pool (the core and max sizes are the same). However, the executor element’s pool-size attribute also accepts a range in the form of min-max. The following example sets a minimum value of 5 and a maximum value of 25:

<task:executor
		id="executorWithPoolSizeRange"
		pool-size="5-25"
		queue-capacity="100"/>

In the preceding configuration, a queue-capacity value has also been provided. The configuration of the thread pool should also be considered in light of the executor’s queue capacity. For the full description of the relationship between pool size and queue capacity, see the documentation for ThreadPoolExecutor. The main idea is that, when a task is submitted, the executor first tries to use a free thread if the number of active threads is currently less than the core size. If the core size has been reached, the task is added to the queue, as long as its capacity has not yet been reached. Only then, if the queue’s capacity has been reached, does the executor create a new thread beyond the core size. If the max size has also been reached, then the executor rejects the task.

默认情况下,队列是无界的,但这很少是期望的配置,因为如果所有池线程都处于繁忙状态时,向该队列添加足够的任务可能会导致 OutOfMemoryErrors。此外,如果队列是无界的,则最大值没有任何效果。由于执行器在创建超过核心大小的新线程之前总是先尝试队列,因此队列必须具有有限的容量,以便线程池增长超过核心大小(这就是固定大小池在使用无界队列时是唯一合理的场景的原因)。

By default, the queue is unbounded, but this is rarely the desired configuration, because it can lead to OutOfMemoryErrors if enough tasks are added to that queue while all pool threads are busy. Furthermore, if the queue is unbounded, the max size has no effect at all. Since the executor always tries the queue before creating a new thread beyond the core size, a queue must have a finite capacity for the thread pool to grow beyond the core size (this is why a fixed-size pool is the only sensible case when using an unbounded queue).

考虑上述所提到的场景,即当一个任务被拒绝时。默认情况下,当一个任务被拒绝时,线程池执行器会抛出一个 TaskRejectedException。然而,拒绝策略实际上是可配置的。当使用默认拒绝策略(即 AbortPolicy 实现)时,会抛出异常。对于重负荷下可以跳过某些任务的应用程序,你可以配置 DiscardPolicyDiscardOldestPolicy。另一种适用于需要在重负载下限制已提交的任务的应用程序的选项是 CallerRunsPolicy。该策略不会抛出异常或丢弃任务,而是强制调用提交方法的线程运行任务本身。其思想是,此类调用方在运行该任务时会处于繁忙状态,并且无法立即提交其他任务。因此,它提供了限制传入负载(同时保持线程池和队列的限制)的简单方法。通常情况下,这允许执行器“赶上”它正在处理的任务,从而释放队列、池或两者的部分容量。你可以从 executor 元素上 rejection-policy 属性的枚举值中选择任何这些选项。

Consider the case, as mentioned above, when a task is rejected. By default, when a task is rejected, a thread pool executor throws a TaskRejectedException. However, the rejection policy is actually configurable. The exception is thrown when using the default rejection policy, which is the AbortPolicy implementation. For applications where some tasks can be skipped under heavy load, you can instead configure either DiscardPolicy or DiscardOldestPolicy. Another option that works well for applications that need to throttle the submitted tasks under heavy load is the CallerRunsPolicy. Instead of throwing an exception or discarding tasks, that policy forces the thread that is calling the submit method to run the task itself. The idea is that such a caller is busy while running that task and not able to submit other tasks immediately. Therefore, it provides a simple way to throttle the incoming load while maintaining the limits of the thread pool and queue. Typically, this allows the executor to “catch up” on the tasks it is handling and thereby frees up some capacity on the queue, in the pool, or both. You can choose any of these options from an enumeration of values available for the rejection-policy attribute on the executor element.

以下示例显示了一个 executor 元素,该元素具有多个属性以指定各种行为:

The following example shows an executor element with a number of attributes to specify various behaviors:

<task:executor
		id="executorWithCallerRunsPolicy"
		pool-size="5-25"
		queue-capacity="100"
		rejection-policy="CALLER_RUNS"/>

最后,keep-alive 设置确定线程在停止之前可以保持空闲状态的时间限制(以秒为单位)。如果池中当前的线程数量超过核心数量,则在等待此段时间且没有处理任务后,将停止多余的线程。零值会造成多余的线程在执行任务且任务队列中没有后续工作后立即停止。以下示例将 keep-alive 值设置为两分钟:

Finally, the keep-alive setting determines the time limit (in seconds) for which threads may remain idle before being stopped. If there are more than the core number of threads currently in the pool, after waiting this amount of time without processing a task, excess threads get stopped. A time value of zero causes excess threads to stop immediately after executing a task without remaining follow-up work in the task queue. The following example sets the keep-alive value to two minutes:

<task:executor
		id="executorWithKeepAlive"
		pool-size="5-25"
		keep-alive="120"/>

The 'scheduled-tasks' Element

Spring 任务命名空间最强大的功能是对在 Spring Application Context 中计划的任务进行配置的支持。这种方式与 Spring 中其他“方法调用者”类似,例如 JMS 命名空间为配置消息驱动的 POJO 提供的方法调用者。基本上,ref 属性可以指向任何 Spring 管理的对象,而 method 属性提供要在该对象上调用的方法的名称。以下列表显示了一个简单的示例:

The most powerful feature of Spring’s task namespace is the support for configuring tasks to be scheduled within a Spring Application Context. This follows an approach similar to other “method-invokers” in Spring, such as that provided by the JMS namespace for configuring message-driven POJOs. Basically, a ref attribute can point to any Spring-managed object, and the method attribute provides the name of a method to be invoked on that object. The following listing shows a simple example:

<task:scheduled-tasks scheduler="myScheduler">
	<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>

<task:scheduler id="myScheduler" pool-size="10"/>

调度程序由外部元素引用,每个单独的任务都包含其触发器元数据配置。在前一个示例中,元数据定义了一个周期性触发器,其中固定延迟指定在每个任务执行完成后等待的毫秒数。另一种选择是 fixed-rate, 它指示该方法应运行的频率,而不考虑任何先前执行需要多长时间。此外,对于 fixed-delayfixed-rate 任务,你可以指定一个“初始延迟”参数,它指示在方法首次执行之前要等待的毫秒数。为了更好地控制,你可以提供一个 cron 属性来提供一个 cron expression。以下示例展示了这些其他选项:

The scheduler is referenced by the outer element, and each individual task includes the configuration of its trigger metadata. In the preceding example, that metadata defines a periodic trigger with a fixed delay indicating the number of milliseconds to wait after each task execution has completed. Another option is fixed-rate, indicating how often the method should be run regardless of how long any previous execution takes. Additionally, for both fixed-delay and fixed-rate tasks, you can specify an 'initial-delay' parameter, indicating the number of milliseconds to wait before the first execution of the method. For more control, you can instead provide a cron attribute to provide a cron expression. The following example shows these other options:

<task:scheduled-tasks scheduler="myScheduler">
	<task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
	<task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
	<task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>

<task:scheduler id="myScheduler" pool-size="10"/>

Cron Expressions

无论是在 @Scheduled annotations,task:scheduled-tasks elements, 还是在其他地方使用,所有的 Spring cron 表达式都必须符合相同的格式。有效的 cron 表达式(如 * * * * * *)包含六个以空格分隔的时间和日期字段,每个字段都有自己的有效值范围:

All Spring cron expressions have to conform to the same format, whether you are using them in @Scheduled annotations, task:scheduled-tasks elements, or someplace else. A well-formed cron expression, such as * * * * * *, consists of six space-separated time and date fields, each with its own range of valid values:

┌───────────── second (0-59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
│ │ │ │ │ ┌───────────── day of the week (0 - 7)
│ │ │ │ │ │          (0 or 7 is Sunday, or MON-SUN)
│ │ │ │ │ │
* * * * * *

有一些适用的规则:

There are some rules that apply:

  • A field may be an asterisk (*), which always stands for “first-last”. For the day-of-the-month or day-of-the-week fields, a question mark (?) may be used instead of an asterisk.

  • Commas (,) are used to separate items of a list.

  • Two numbers separated with a hyphen (-) express a range of numbers. The specified range is inclusive.

  • Following a range (or *) with / specifies the interval of the number’s value through the range.

  • English names can also be used for the month and day-of-week fields. Use the first three letters of the particular day or month (case does not matter).

  • The day-of-month and day-of-week fields can contain an L character, which has a different meaning.

    • In the day-of-month field, L stands for the last day of the month. If followed by a negative offset (that is, L-n), it means `n`th-to-last day of the month.

    • In the day-of-week field, L stands for the last day of the week. If prefixed by a number or three-letter name (dL or DDDL), it means the last day of week (d or DDD) in the month.

  • The day-of-month field can be nW, which stands for the nearest weekday to day of the month n`. If `n falls on Saturday, this yields the Friday before it. If n falls on Sunday, this yields the Monday after, which also happens if n is 1 and falls on a Saturday (that is: 1W stands for the first weekday of the month).

  • If the day-of-month field is LW, it means the last weekday of the month.

  • The day-of-week field can be d#n (or DDD#n), which stands for the n`th day of week `d (or DDD) in the month.

这里一些示例:

Here are some examples:

Cron Expression Meaning

0 0 * * * *

top of every hour of every day

*/10 * * * * *

every ten seconds

0 0 8-10 * * *

8, 9 and 10 o’clock of every day

0 0 6,19 * * *

6:00 AM and 7:00 PM every day

0 0/30 8-10 * * *

8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day

0 0 9-17 * * MON-FRI

on the hour nine-to-five weekdays

0 0 0 25 DEC ?

every Christmas Day at midnight

0 0 0 L * *

last day of the month at midnight

0 0 0 L-3 * *

third-to-last day of the month at midnight

0 0 0 * * 5L

last Friday of the month at midnight

0 0 0 * * THUL

last Thursday of the month at midnight

0 0 0 1W * *

first weekday of the month at midnight

0 0 0 LW * *

last weekday of the month at midnight

0 0 0 ? * 5#2

the second Friday in the month at midnight

0 0 0 ? * MON#1

the first Monday in the month at midnight

Macros

诸如 0 0 * * * * 的表达式对人类来说很难解析,因此在出现错误的情况下很难修复。为了提高可读性,Spring 支持以下宏,表示常用的序列。你可以使用这些宏代替六位数字的值,如下所示:@Scheduled(cron = "@hourly")

Expressions such as 0 0 * * * * are hard for humans to parse and are, therefore, hard to fix in case of bugs. To improve readability, Spring supports the following macros, which represent commonly used sequences. You can use these macros instead of the six-digit value, thus: @Scheduled(cron = "@hourly").

Macro Meaning

@yearly (or @annually)

once a year (0 0 0 1 1 *)

@monthly

once a month (0 0 0 1 * *)

@weekly

once a week (0 0 0 * * 0)

@daily (or @midnight)

once a day (0 0 0 * * *), or

@hourly

once an hour, (0 0 * * * *)

Using the Quartz Scheduler

Quartz 使用 Trigger, Job, 和 JobDetail 对象来实现各种作业的调度。有关 Quartz 背后的基本概念,请参阅 Quartz Web site。为了方便,Spring 提供了几种类,简化了在基于 Spring 的应用程序中使用 Quartz。

Quartz uses Trigger, Job, and JobDetail objects to realize scheduling of all kinds of jobs. For the basic concepts behind Quartz, see the Quartz Web site. For convenience purposes, Spring offers a couple of classes that simplify using Quartz within Spring-based applications.

Using the JobDetailFactoryBean

Quartz JobDetail 对象包含运行作业所需的所有信息。Spring 提供了一个 JobDetailFactoryBean,它为 XML 配置目的提供了 bean 样式的属性。考虑以下示例:

Quartz JobDetail objects contain all the information needed to run a job. Spring provides a JobDetailFactoryBean, which provides bean-style properties for XML configuration purposes. Consider the following example:

<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
	<property name="jobClass" value="example.ExampleJob"/>
	<property name="jobDataAsMap">
		<map>
			<entry key="timeout" value="5"/>
		</map>
	</property>
</bean>

作业详细配置具有运行作业(ExampleJob)所需的所有信息。超时在作业数据映射中指定。作业数据映射可通过 JobExecutionContext(在执行时传递给你)获得,但 JobDetail 也从映射到作业实例属性的作业数据获取其属性。因此,在以下示例中,ExampleJob 包含一个名为 timeout 的 bean 属性,JobDetail 会自动应用该属性:

The job detail configuration has all the information it needs to run the job (ExampleJob). The timeout is specified in the job data map. The job data map is available through the JobExecutionContext (passed to you at execution time), but the JobDetail also gets its properties from the job data mapped to properties of the job instance. So, in the following example, the ExampleJob contains a bean property named timeout, and the JobDetail has it applied automatically:

public class ExampleJob extends QuartzJobBean {

	private int timeout;

	/**
	 * Setter called after the ExampleJob is instantiated
	 * with the value from the JobDetailFactoryBean.
	 */
	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
		// do the actual work
	}
}

作业数据映射中的所有其他属性也对你可用。

All additional properties from the job data map are available to you as well.

通过使用 namegroup 属性,您可以分别修改名称和作业组。默认情况下,作业名称与 JobDetailFactoryBean 的 Bean 名称相匹配(在上面的前一个示例中为 exampleJob)。

By using the name and group properties, you can modify the name and the group of the job, respectively. By default, the name of the job matches the bean name of the JobDetailFactoryBean (exampleJob in the preceding example above).

Using the MethodInvokingJobDetailFactoryBean

通常,你只需要在特定对象上调用一个方法。通过使用 MethodInvokingJobDetailFactoryBean,你可以执行此操作,如下面的示例所示:

Often you merely need to invoke a method on a specific object. By using the MethodInvokingJobDetailFactoryBean, you can do exactly this, as the following example shows:

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
	<property name="targetObject" ref="exampleBusinessObject"/>
	<property name="targetMethod" value="doIt"/>
</bean>

上一个示例导致在 exampleBusinessObject 方法上调用 doIt 方法,如下一个示例所示:

The preceding example results in the doIt method being called on the exampleBusinessObject method, as the following example shows:

public class ExampleBusinessObject {

	// properties and collaborators

	public void doIt() {
		// do the actual work
	}
}
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>

通过使用 MethodInvokingJobDetailFactoryBean,无需创建仅调用方法的单行作业。只需创建实际业务对象并连接详细信息对象即可。

By using the MethodInvokingJobDetailFactoryBean, you need not create one-line jobs that merely invoke a method. You need only create the actual business object and wire up the detail object.

默认情况下,Quartz 作业是无状态的,这会导致作业相互干扰的可能性。如果针对同一个 JobDetail 指定两个触发器,第二个触发器有可能在第一个作业完成之前启动。如果 JobDetail 类实现了 Stateful 接口,这种情况不会发生:第二个作业不会在第一个作业完成之前启动。

By default, Quartz Jobs are stateless, resulting in the possibility of jobs interfering with each other. If you specify two triggers for the same JobDetail, it is possible that the second one starts before the first job has finished. If JobDetail classes implement the Stateful interface, this does not happen: the second job does not start before the first one has finished.

若要使 MethodInvokingJobDetailFactoryBean 产生的作业变为非并发,请将 concurrent 标志设置为 false,如下例所示:

To make jobs resulting from the MethodInvokingJobDetailFactoryBean be non-concurrent, set the concurrent flag to false, as the following example shows:

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
	<property name="targetObject" ref="exampleBusinessObject"/>
	<property name="targetMethod" value="doIt"/>
	<property name="concurrent" value="false"/>
</bean>

默认情况下,作业将以并发的方式运行。

By default, jobs will run in a concurrent fashion.

Wiring up Jobs by Using Triggers and SchedulerFactoryBean

我们已经创建了作业详细信息和作业。我们还查看了让你在一个特定对象中调用方法的便捷 Bean。当然,我们仍然需要安排作业本身。这是通过使用触发器和 SchedulerFactoryBean 来完成的。在 Quartz 中有几种触发器可用,而 Spring 提供了两个 Quartz FactoryBean 实现,带有便捷的默认值:CronTriggerFactoryBeanSimpleTriggerFactoryBean

We have created job details and jobs. We have also reviewed the convenience bean that lets you invoke a method on a specific object. Of course, we still need to schedule the jobs themselves. This is done by using triggers and a SchedulerFactoryBean. Several triggers are available within Quartz, and Spring offers two Quartz FactoryBean implementations with convenient defaults: CronTriggerFactoryBean and SimpleTriggerFactoryBean.

需要安排触发器。Spring 提供了一个 SchedulerFactoryBean,用于将触发器公开为属性设置。SchedulerFactoryBean 使用这些触发器安排实际作业。

Triggers need to be scheduled. Spring offers a SchedulerFactoryBean that exposes triggers to be set as properties. SchedulerFactoryBean schedules the actual jobs with those triggers.

以下清单同时使用了 SimpleTriggerFactoryBeanCronTriggerFactoryBean

The following listing uses both a SimpleTriggerFactoryBean and a CronTriggerFactoryBean:

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
	<!-- see the example of method invoking job above -->
	<property name="jobDetail" ref="jobDetail"/>
	<!-- 10 seconds -->
	<property name="startDelay" value="10000"/>
	<!-- repeat every 50 seconds -->
	<property name="repeatInterval" value="50000"/>
</bean>

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
	<property name="jobDetail" ref="exampleJob"/>
	<!-- run every morning at 6 AM -->
	<property name="cronExpression" value="0 0 6 * * ?"/>
</bean>

前一个示例设置了两个触发器,一个每 50 秒运行一次,启动延迟 10 秒,另一个每天上午 6 点运行一次。为最终确定一切,我们需要设置 SchedulerFactoryBean,如下例所示:

The preceding example sets up two triggers, one running every 50 seconds with a starting delay of 10 seconds and one running every morning at 6 AM. To finalize everything, we need to set up the SchedulerFactoryBean, as the following example shows:

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
	<property name="triggers">
		<list>
			<ref bean="cronTrigger"/>
			<ref bean="simpleTrigger"/>
		</list>
	</property>
</bean>

SchedulerFactoryBean 有更多属性可用,例如任务详细信息使用的日历、用于自定义 Quartz 的属性以及 Spring 提供的 JDBC DataSource。请参见 SchedulerFactoryBean javadoc 以获取更多信息。

More properties are available for the SchedulerFactoryBean, such as the calendars used by the job details, properties to customize Quartz with, and a Spring-provided JDBC DataSource. See the SchedulerFactoryBean javadoc for more information.

SchedulerFactoryBean 也识别一个 quartz.properties 文件中的类路径,基于 Quartz 属性键,就像使用常规 Quartz 配置一样。请注意,许多 SchedulerFactoryBean 设置会与属性文件中常见的 Quartz 设置交互;因此,不建议在两个级别上都指定值。例如,如果您打算依赖 Spring 提供的数据源,不要设置一个“org.quartz.jobStore.class”属性,或者指定一个 org.springframework.scheduling.quartz.LocalDataSourceJobStore 变种,它将完全替代标准 org.quartz.impl.jdbcjobstore.JobStoreTX

SchedulerFactoryBean also recognizes a quartz.properties file in the classpath, based on Quartz property keys, as with regular Quartz configuration. Please note that many SchedulerFactoryBean settings interact with common Quartz settings in the properties file; it is therefore not recommended to specify values at both levels. For example, do not set an "org.quartz.jobStore.class" property if you mean to rely on a Spring-provided DataSource, or specify an org.springframework.scheduling.quartz.LocalDataSourceJobStore variant which is a full-fledged replacement for the standard org.quartz.impl.jdbcjobstore.JobStoreTX.