Spring Framework 为异步执行和调度提供了抽象 分别与 和 接口的任务。春天也 功能支持线程池或委派给的接口的实现 应用程序服务器环境中的 CommonJ。归根结底,使用这些 通用接口背后的实现抽象了 Java SE 和 Jakarta EE 环境。TaskExecutorTaskScheduler

Spring 还具有集成类,以支持使用 Quartz Scheduler 进行调度。

春天的抽象TaskExecutor

执行程序是线程池概念的 JDK 名称。“执行人”的命名是 由于无法保证底层实现是 其实是一个游泳池。执行程序可以是单线程的,甚至可以是同步的。春天的 抽象隐藏了 Java SE 和 Jakarta EE 环境之间的实现细节。

Spring 的接口与接口相同。事实上,最初,它存在的主要原因是抽象化 使用线程池时需要 Java 5。该接口只有一个方法 () 接受基于语义执行的任务 以及线程池的配置。TaskExecutorjava.util.concurrent.Executorexecute(Runnable task)

最初创建是为了给其他 Spring 组件一个抽象 在需要时进行线程池。诸如 、 JMS 和 Quartz 集成都使用抽象来池化线程。但是,如果您的 Bean 需要线程池 行为,你也可以根据自己的需要使用这个抽象。TaskExecutorApplicationEventMulticasterAbstractMessageListenerContainerTaskExecutor

TaskExecutor类型

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

  • SyncTaskExecutor: 此实现不会异步运行调用。取而代之的是,每个 调用发生在调用线程中。它主要用于各种情况 不需要多线程,例如在简单的测试用例中。

  • SimpleAsyncTaskExecutor: 此实现不会重用任何线程。相反,它会启动一个新线程 对于每个调用。但是,它确实支持阻止的并发限制 在释放插槽之前超出限制的任何调用。如果你 正在寻找真正的池化,请参阅此列表后面的 。ThreadPoolTaskExecutor

  • ConcurrentTaskExecutor: 此实现是实例的适配器。 有一个替代 () 将配置参数公开为 Bean 属性。很少需要直接使用。但是,如果不是 足够灵活地满足您的需求,是一种选择。java.util.concurrent.ExecutorThreadPoolTaskExecutorExecutorConcurrentTaskExecutorThreadPoolTaskExecutorConcurrentTaskExecutor

  • ThreadPoolTaskExecutor: 此实现是最常用的。它公开用于配置的 Bean 属性 a 并将其包装在 . 如果你需要适应不同的种类, 我们建议您改用 A。java.util.concurrent.ThreadPoolExecutorTaskExecutorjava.util.concurrent.ExecutorConcurrentTaskExecutor

  • DefaultManagedTaskExecutor: 此实现使用在 JSR-236 中获取的 JNDI 兼容的运行时环境(例如 Jakarta EE 应用程序服务器), 为此目的替换 CommonJ WorkManager。ManagedExecutorService

从 6.1 开始,提供暂停/恢复功能和优雅 通过 Spring 的生命周期管理关闭。还有一个新的“virtualThreads” 与 JDK 21 的虚拟线程一致的选项, 以及优雅的关机功能。ThreadPoolTaskExecutorSimpleAsyncTaskExecutorSimpleAsyncTaskExecutor

使用TaskExecutor

Spring 的实现通常与依赖注入一起使用。 在下面的示例中,我们定义了一个 bean,它使用 异步打印出一组消息:TaskExecutorThreadPoolTaskExecutor

import org.springframework.core.task.TaskExecutor;

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

正如你所看到的,而不是从池中检索一个线程并自己执行它, 将您的添加到队列中。然后使用其内部规则来 确定任务的运行时间。RunnableTaskExecutor

为了配置它们使用的规则,我们公开了简单的 Bean 属性: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>

春天的抽象TaskScheduler

除了抽象之外,Spring 还有一个 SPI 和 用于安排任务在将来的某个时间点运行的各种方法。以下 列表显示了接口定义:TaskExecutorTaskSchedulerTaskScheduler

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

最简单的方法是仅接受 a 和 an 的命名方法。 这会导致任务在指定时间后运行一次。所有其他方法 能够安排任务重复运行。固定速率和固定延迟 方法用于简单的定期执行,但接受 更加灵活。scheduleRunnableInstantTrigger

Trigger接口

该界面本质上是受 JSR-236 的启发。其基本思想是,可以根据过去的执行结果或 甚至是任意条件。如果这些决定考虑到 在执行之前,该信息在 .界面本身非常简单,如以下列表所示:TriggerTriggerTriggerContextTrigger

public interface Trigger {

	Instant nextExecution(TriggerContext triggerContext);
}

这是最重要的部分。它封装了所有 相关数据,并在必要时开放,以便将来扩展。是一个接口(一个实现由 默认)。以下列表显示了可用于实现的方法。TriggerContextTriggerContextSimpleTriggerContextTrigger

public interface TriggerContext {

	Clock getClock();

	Instant lastScheduledExecution();

	Instant lastActualExecution();

	Instant lastCompletion();
}

Trigger实现

Spring 提供了两种接口实现。最有趣的一个 是 .它支持基于 cron 表达式的任务调度。 例如,以下任务计划为每小时运行 15 分钟,但仅 在工作日的朝九晚五的“工作时间”内:TriggerCronTrigger

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

另一个实现是接受固定的 period,一个可选的初始延迟值,以及一个布尔值,用于指示周期是否 应解释为固定速率或固定延迟。由于该接口已经定义了以固定速率或使用 固定延迟,应尽可能直接使用这些方法。实现的价值在于,您可以在依赖 抽象。例如,允许周期性触发可能很方便, 基于 cron 的触发器,甚至可以互换使用的自定义触发器实现。 此类组件可以利用依赖项注入,以便您可以配置 因此,可以很容易地修改或扩展它们。PeriodicTriggerTaskSchedulerPeriodicTriggerTriggerTriggers

TaskScheduler实现

与 Spring 的抽象一样,这种安排的主要好处是应用程序的调度需求与部署分离 环境。此抽象级别在部署到 应用程序服务器环境中,线程不应由 应用程序本身。对于这种情况,Spring 在 Jakarta EE 环境中为 JSR-236 提供了一个委托。TaskExecutorTaskSchedulerDefaultManagedTaskSchedulerManagedScheduledExecutorService

每当不需要外部线程管理时,更简单的替代方案是 应用程序中的本地设置,可以进行调整 通过春天的.为了方便起见,Spring 还提供了一个 ,它在内部委托给 a 以提供通用的 bean 样式配置。 这些变体非常适合本地嵌入的线程池设置 应用程序服务器环境也是如此,尤其是在 Tomcat 和 Jetty 上。ScheduledExecutorServiceConcurrentTaskSchedulerThreadPoolTaskSchedulerScheduledExecutorServiceThreadPoolTaskExecutor

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

对调度和异步执行的注释支持

Spring 为任务调度和异步方法提供注解支持 执行。

启用计划注释

若要启用对 和 批注的支持,可以将 和 添加到其中一个类中,如以下示例所示:@Scheduled@Async@EnableScheduling@EnableAsync@Configuration

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

您可以为您的应用程序选择相关的注释。例如 如果只需要支持 ,则可以省略 。查看更多 细粒度控制,您可以另外实现接口和/或接口。有关完整的详细信息,请参阅 SchedulingConfigurerAsyncConfigurer javadoc。@Scheduled@EnableAsyncSchedulingConfigurerAsyncConfigurer

如果您更喜欢 XML 配置,则可以使用以下元素: 如以下示例所示:<task:annotation-driven>

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

请注意,在前面的 XML 中,提供了用于处理这些 与具有注释的方法和调度程序相对应的任务 为管理那些用 .@Async@Scheduled

处理注释的默认建议模式是允许 仅用于通过代理拦截呼叫。同一类中的本地呼叫 不能以这种方式被拦截。对于更高级的拦截模式,请考虑 切换到与编译时或加载时编织相结合的模式。@Asyncproxyaspectj

注释@Scheduled

您可以将批注与触发器元数据一起添加到方法中。为 例如,每 5 秒(5000 毫秒)调用一次以下方法,其中 固定延迟,这意味着该周期是从每个的完成时间开始计算的 在调用之前。@Scheduled

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

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

例如,前面的示例也可以写成如下。

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

如果需要固定速率执行,可以使用 注解。以下方法每 5 秒调用一次(在 每次调用的连续开始时间):fixedRate

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

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

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

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

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

如果简单的定期调度不够富有表现力,则可以提供 cron 表达式。 以下示例仅在工作日运行:

@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
	// something that should run on weekdays only
}
您还可以使用该属性来指定 cron 所在的时区 表达式已解析。zone

请注意,要计划的方法必须具有无效返回,并且不得接受任何返回 参数。如果该方法需要与应用程序中的其他对象进行交互 上下文中,这些通常是通过依赖注入提供的。

@Scheduled可以用作可重复的注释。如果多个预定声明 在相同的方法上找到,它们中的每一个都将独立处理,带有 每个都单独触发。因此,这种同地办公的时间表 可以重叠并并行或立即连续执行多次。 请确保您指定的 cron 表达式等不会意外重叠。

从 Spring Framework 4.3 开始,任何范围的 Bean 都支持方法。@Scheduled

请确保您没有在运行时初始化同一注释类的多个实例,除非您确实希望计划对每个此类实例的回调 实例。与此相关,请确保您不要在 bean 上使用 使用常规 Spring Bean 进行注释并注册为常规 Spring Bean 的类 与容器。否则,您将获得双重初始化(一次通过 容器和一次通过方面),结果是每个方法被调用两次。@Scheduled@Configurable@Scheduled@Configurable@Scheduled

对反应式方法或 Kotlin 挂起函数的注释@Scheduled

从 Spring Framework 6.1 开始,方法也支持多种类型 反应方法:@Scheduled

  • 具有返回类型(或任何具体实现)的方法 如以下示例所示:PublisherPublisher

@Scheduled(fixedDelay = 500)
public Publisher<Void> reactiveSomething() {
	// return an instance of Publisher
}
  • 具有可通过共享实例适应的返回类型的方法 的 ,前提是类型支持延迟订阅,例如 在以下示例中:PublisherReactiveAdapterRegistry

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

该类是通常可以调整的类型的一个示例 ,但不支持延迟订阅。它在 registry 表示通过让方法返回 。CompletableFuturePublisherReactiveAdaptergetDescriptor().isDeferred()false

  • Kotlin 挂起函数,如以下示例所示:

@Scheduled(fixedDelay = 500)
suspend fun something() {
	// do something asynchronous
}
  • 返回 Kotlin 或实例的方法,如以下示例所示:FlowDeferred

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

所有这些类型的方法都必须声明不带任何参数。在 Kotlin 的情况下 悬挂功能,桥接器也必须存在以允许 将挂起函数调用为 .kotlinx.coroutines.reactorPublisher

Spring 框架将获取一次注释方法,并且将 它订阅的附表 A 说 .这些内在的规则 根据相应的 // 配置进行订阅。PublisherRunnablePublishercronfixedDelayfixedRate

如果发出信号,则忽略并丢弃这些信号(以同样的方式) 将忽略同步方法的返回值)。PublisheronNext@Scheduled

在以下示例中,每 5 发出 秒,但未使用以下值:FluxonNext("Hello")onNext("World")

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

如果发出信号,则将其记录在水平并恢复。 由于实例的异步和惰性,例外情况包括 not thrown from the task:这意味着合约不是 涉及反应性方法。PublisheronErrorWARNPublisherRunnableErrorHandler

因此,尽管存在错误,但仍会发生进一步的计划订阅。

在以下示例中,订阅在前 5 秒内失败两次。 然后订阅开始成功,每 5 次将消息打印到标准输出 秒:Mono

@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 Framework 会取消 计划任务,其中还包括对 与当前仍处于活动状态的任何过去订阅一样(例如,对于长期运行的发布者 甚至无限的出版商)。Publisher

注释@Async

您可以提供方法的注释,以便调用该方法 异步发生。换言之,呼叫者在 调用,而该方法的实际执行发生在已执行的任务中 提交给一个春天.在最简单的情况下,您可以应用注释 到返回 的方法,如以下示例所示:@AsyncTaskExecutorvoid

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

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

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

甚至可以异步调用返回值的方法。但是,这些方法 必须具有 -type 返回值。这仍然提供了以下好处 异步执行,以便调用方可以在调用该 .下面的示例演示如何在方法上使用 返回一个值:Futureget()Future@Async

@Async
Future<String> returnSomething(int i) {
	// this will be run asynchronously
}
@Async方法不仅可以申报定期退货 类型,但也有 Spring 的或,截至 Spring 4.2,JDK 8,用于更丰富的交互 异步任务,并立即通过进一步的处理步骤进行组合。java.util.concurrent.Futureorg.springframework.util.concurrent.ListenableFuturejava.util.concurrent.CompletableFuture

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

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

}
没有直接的 XML 等效项,因为应该设计这样的方法 首先用于异步执行,而不是在外部重新声明为异步执行。 但是,您可以使用 Spring AOP 手动设置 Spring, 与自定义切口结合使用。@AsyncAsyncExecutionInterceptor

遗嘱执行人资格@Async

默认情况下,在方法上指定时,使用的执行程序是 一个在启用异步支持时配置的, 即“注解驱动”元素,如果您使用的是 XML 或您的实现(如果有)。但是,当需要指示除默认值之外的执行程序应为 在执行给定方法时使用。以下示例演示如何执行此操作:@AsyncAsyncConfigurervalue@Async

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

在这种情况下,可以是 Spring 中任何 bean 的名称 container,或者它可以是与任何 (例如, 由元素或 Spring 的注释指定)。"otherExecutor"ExecutorExecutor<qualifier>@Qualifier

异常管理@Async

当方法具有 -type 返回值时,它很容易管理 在方法执行期间引发的异常,因为此异常是 调用结果时抛出。对于返回类型, 但是,异常未捕获,无法传输。您可以提供一个来处理此类异常。以下示例显示 如何做到这一点:@AsyncFuturegetFuturevoidAsyncUncaughtExceptionHandler

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

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

默认情况下,仅记录异常。可以使用 XML 元素定义自定义项。AsyncUncaughtExceptionHandlerAsyncConfigurer<task:annotation-driven/>

命名空间task

从 3.0 版开始,Spring 包含用于配置和实例的 XML 命名空间。它还提供了一种方便的方式来配置任务 使用触发器进行计划。TaskExecutorTaskScheduler

“scheduler”元素

以下元素创建一个实例,其中包含 指定的线程池大小:ThreadPoolTaskScheduler

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

为属性提供的值用作线程名称的前缀 在游泳池内。该元素相对简单。如果你不这样做 提供一个属性,默认线程池只有一个线程。 调度程序没有其他配置选项。idschedulerpool-size

元素executor

以下创建一个实例:ThreadPoolTaskExecutor

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

上一节中所示的调度程序一样, 为该属性提供的值用作 游泳池。就池大小而言,该元素支持更多 配置选项。首先,线程池 a 本身更具可配置性。而不是只有单一的尺寸, 执行程序的线程池可以具有不同的核心值和最大大小值。 如果提供单个值,则执行程序具有固定大小的线程池(核心和 最大大小相同)。但是,元素的属性也 接受 形式的范围。下面的示例设置最小值和最大值:idexecutorschedulerThreadPoolTaskExecutorexecutorpool-sizemin-max525

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

在前面的配置中,还提供了一个值。 线程池的配置也应根据 执行程序的队列容量。有关池之间关系的完整描述 大小和队列容量,请参阅 ThreadPoolExecutor 的文档。 主要思想是,当提交任务时,执行者首先尝试使用 如果活动线程数当前小于核心大小,则为可用线程。 如果已达到核心大小,则任务将添加到队列中,只要其 尚未达到容量。只有这样,如果队列的容量已经 到达时,执行程序是否创建超出核心大小的新线程。如果最大大小 也已到达,则执行者拒绝该任务。queue-capacity

默认情况下,队列是无界的,但这很少是所需的配置, 因为它可能会导致将足够的任务添加到该队列中,而 所有池线程都处于繁忙状态。此外,如果队列是无界的,则最大大小为 完全没有效果。由于执行程序总是在创建新的队列之前尝试队列 线程超出核心大小,队列必须具有有限的容量,线程池才能 超出核心大小(这就是为什么固定大小的池是唯一明智的情况 使用无界队列时)。OutOfMemoryErrors

如上所述,考虑任务被拒绝的情况。默认情况下,当 任务被拒绝,线程池执行程序抛出 .然而 拒绝策略实际上是可配置的。使用 默认拒绝策略,即实现。 对于在重负载下可以跳过某些任务的应用程序,您可以改为 配置 或 .另一个有效的选项 对于需要在重负载下限制提交任务的应用程序来说,是 这。与其抛出异常或丢弃任务, 该策略强制调用 submit 方法的线程本身运行任务。 这个想法是这样的调用者在运行该任务时很忙并且无法提交 其他任务立即。因此,它提供了一种限制传入的简单方法 加载,同时保持线程池和队列的限制。通常,这允许 执行者“赶上”它正在处理的任务,从而释放一些 队列中的容量和/或池中的容量。您可以从以下选项中选择任何一个 枚举可用于元素上属性的值。TaskRejectedExceptionAbortPolicyDiscardPolicyDiscardOldestPolicyCallerRunsPolicyrejection-policyexecutor

下面的示例演示一个元素,其中包含要指定的许多属性 各种行为:executor

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

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

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

“scheduled-tasks”元素

Spring 的任务命名空间最强大的功能是支持配置 要在 Spring Application Context 中安排的任务。这遵循一种方法 类似于 Spring 中的其他“方法调用器”,例如 JMS 命名空间提供的调用器 用于配置消息驱动的 POJO。基本上,一个属性可以指向任何 Spring 管理的对象,该属性提供了要 在该对象上调用。以下列表显示了一个简单的示例:refmethod

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

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

调度程序由外部元素引用,并且每个元素都引用 任务包括其触发器元数据的配置。在前面的示例中, 该元数据定义了一个周期性触发器,该触发器具有固定的延迟,指示 每个任务执行完成后等待的毫秒数。另一个选项是 ,指示方法应运行多长时间 任何先前的执行都需要。此外,对于这两个任务,您可以指定一个“初始延迟”参数,指示 毫秒等待方法首次执行之前。为了更好地控制, 您可以改为提供属性来提供 cron 表达式。 以下示例显示了以下其他选项:fixed-ratefixed-delayfixed-ratecron

<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 表达式

所有 Spring cron 表达式都必须符合相同的格式,无论您是在@Scheduled解、task:scheduled-tasks 元素、 或其他地方。格式良好的 cron 表达式(如 )由 6 个组成 以空格分隔的时间和日期字段,每个字段都有自己的有效值范围:* * * * * *

 ┌───────────── 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)
 │ │ │ │ │ │
 * * * * * *

有一些规则适用:

  • 字段可以是星号 (),它始终代表“第一个-最后一个”。 对于“月中日”或“星期几”字段,可以使用问号 () 代替 星号。*?

  • 逗号 () 用于分隔列表的项目。,

  • 用连字符 () 分隔的两个数字表示一系列数字。 指定的范围是包括在内的。-

  • 在范围(或)后面指定数字值在该范围内的间隔。*/

  • 英文名称也可用于月份和星期几字段。 使用特定日期或月份的前三个字母(大小写无关紧要)。

  • “月中日”和“星期几”字段可以包含具有不同含义的字符。L

    • 在“月中日”字段中,代表该月的最后一天。 如果后跟负偏移量(即 ),则表示该月的第 n天到最后一天LL-n

    • 在星期几字段中,代表一周的最后一天。 如果以数字或三个字母的名称 ( 或 ) 为前缀,则表示该月一周的最后一天(dDDD)。LdLDDDL

  • 月份中的某一天字段可以是 ,它表示最接近的工作日到月份 n 中的某一天。 如果落在星期六,则产生前一个星期五。 如果落在星期日,这会产生下一个星期一,如果 is 和 落在 星期六(即:代表每月的第一个工作日)。nWnnn11W

  • 如果月份中的某一天字段为 ,则表示该月的最后一个工作日LW

  • 星期几字段可以是 (或),它代表当月第 d 周的第 n天(或 DDD)。d#nDDD#n

以下是一些示例:

Cron 表达式 意义

0 0 * * * *

每天每个小时都处于领先地位

*/10 * * * * *

每十秒

0 0 8-10 * * *

每天8点、9点和10点

0 0 6,19 * * *

每天上午 6:00 和晚上 7:00

0 0/30 8-10 * * *

每天8:00、8:30、9:00、9:30、10:00和10:30

0 0 9-17 * * MON-FRI

工作日朝九晚五整点

0 0 0 25 DEC ?

每年圣诞节午夜

0 0 0 L * *

每月的最后一天午夜

0 0 0 L-3 * *

每月倒数第三天午夜

0 0 0 * * 5L

每月最后一个星期五午夜

0 0 0 * * THUL

每月的最后一个星期四午夜

0 0 0 1W * *

每月第一个工作日的午夜

0 0 0 LW * *

每月最后一个工作日的午夜

0 0 0 ? * 5#2

每月第二个星期五午夜

0 0 0 ? * MON#1

每月第一个星期一午夜

诸如此类的表达式对于人类来说很难解析,因此, 如果出现错误,很难修复。为了提高可读性,Spring 支持以下功能 宏,表示常用序列。您可以改用这些宏 的六位数值,因此:.0 0 * * * *@Scheduled(cron = "@hourly")

宏观 意义

@yearly(或@annually)

每年一次(0 0 0 1 1 *)

@monthly

每月一次 (0 0 0 1 * *)

@weekly

每周一次 (0 0 0 * * 0)

@daily(或@midnight)

每天一次 (),或0 0 0 * * *

@hourly

每小时一次,(0 0 * * * *)

使用 Quartz 调度程序

Quartz 使用 、 和 对象来实现所有 各种工作。有关 Quartz 背后的基本概念,请参阅 Quartz 网站。为方便起见,Spring 提供了几个类,这些类简化了在基于 Spring 的应用程序中使用 Quartz。TriggerJobJobDetail

使用JobDetailFactoryBean

Quartz 对象包含运行作业所需的所有信息。春天 提供了一个 ,它为 XML 提供了 Bean 样式的属性 配置目的。请看以下示例:JobDetailJobDetailFactoryBean

<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>

作业详细信息配置包含运行作业所需的所有信息 ()。 超时在作业数据映射中指定。作业数据映射可通过(在执行时传递给您)获得,但也得到 其从作业数据映射到作业实例属性的属性。因此,在 下面的示例包含一个名为 的 Bean 属性,并且 已自动应用该属性:ExampleJobJobExecutionContextJobDetailExampleJobtimeoutJobDetail

package example;

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

作业数据映射中的所有其他属性也可供您使用。

通过使用 和 属性,可以修改名称和组 的工作。缺省情况下,作业的名称与 Bean 名称匹配 的(在上面的前面的例子中)。namegroupJobDetailFactoryBeanexampleJob

使用MethodInvokingJobDetailFactoryBean

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

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

前面的示例导致在方法上调用该方法,如以下示例所示:doItexampleBusinessObject

public class ExampleBusinessObject {

	// properties and collaborators

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

通过使用 ,您不需要创建单行作业 只是调用一个方法。您只需要创建实际的业务对象,然后 连接细节对象。MethodInvokingJobDetailFactoryBean

默认情况下,Quartz 作业是无状态的,因此作业可能会受到干扰 彼此之间。如果您为同一个 指定两个触发器,则可以 第二个在第一个作业完成之前开始。如果类 实现接口,这不会发生:第二个作业没有启动 在第一个完成之前。JobDetailJobDetailStateful

要使由非并发产生的作业, 将标志设置为 ,如以下示例所示:MethodInvokingJobDetailFactoryBeanconcurrentfalse

<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>
默认情况下,作业将以并发方式运行。

使用触发器连接作业和SchedulerFactoryBean

我们创建了工作详细信息和工作。我们还回顾了方便豆 允许您在特定对象上调用方法。当然,我们仍然需要安排 工作本身。这是通过使用触发器和 .几个 触发器在 Quartz 中可用,Spring 提供了两种具有方便默认值的 Quartz 实现:和 .SchedulerFactoryBeanFactoryBeanCronTriggerFactoryBeanSimpleTriggerFactoryBean

需要安排触发器。Spring 提供了一个暴露 要设置为属性的触发器。 使用 那些触发器。SchedulerFactoryBeanSchedulerFactoryBean

以下列表同时使用 a 和 a:SimpleTriggerFactoryBeanCronTriggerFactoryBean

<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

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

更多属性可用于 ,例如 作业详细信息、用于自定义 Quartz 的属性以及 Spring 提供的 JDBC DataSource。看 SchedulerFactoryBean javadoc 了解更多信息。SchedulerFactoryBean

SchedulerFactoryBean还可以识别类路径中的文件, 基于 Quartz 属性键,与常规 Quartz 配置一样。请注意,许多设置与属性文件中的常见 Quartz 设置交互; 因此,不建议在两个级别上指定值。例如,不要设置 “org.quartz.jobStore.class”属性,如果你打算依赖 Spring 提供的 DataSource, 或指定一个变体,该变体 是标准的完全替代品。quartz.propertiesSchedulerFactoryBeanorg.springframework.scheduling.quartz.LocalDataSourceJobStoreorg.quartz.impl.jdbcjobstore.JobStoreTX