此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

任务执行和调度

Spring Framework 为异步执行和调度提供了抽象 任务与TaskExecutorTaskScheduler接口。Spring也 功能 支持线程池或委托给 CommonJ 在应用程序服务器环境中。最终,这些 通用接口背后的实现抽象化了 Java SE 和 Jakarta EE 环境。spring-doc.cadn.net.cn

Spring 还具有集成类,以支持使用 Quartz Scheduler 进行调度。spring-doc.cadn.net.cn

SpringTaskExecutor抽象化

执行器是线程池概念的 JDK 名称。“执行者”命名是 由于无法保证底层实现是 实际上是一个游泳池。执行器可以是单线程的,甚至可以是同步的。Spring的 抽象隐藏了 Java SE 和 Jakarta EE 环境之间的实现细节。spring-doc.cadn.net.cn

Spring的TaskExecutor接口与java.util.concurrent.Executor接口。事实上,最初,它存在的主要原因是抽象化 使用线程池时需要 Java 5。该接口具有单一方法 (execute(Runnable task))接受任务执行,基于语义 以及线程池的配置。spring-doc.cadn.net.cn

TaskExecutor最初是为了给其他 Spring 组件一个抽象而创建的 用于在需要时进行线程池。组件,例如ApplicationEventMulticaster, JMS 的AbstractMessageListenerContainer,和 Quartz 集成都使用TaskExecutor抽象到池线程。但是,如果您的 Bean 需要线程池 行为,你也可以根据自己的需要使用这个抽象。spring-doc.cadn.net.cn

TaskExecutor类型

Spring 包括许多预构建的TaskExecutor. 很可能,您永远不需要实现自己的。 Spring提供的变体如下:spring-doc.cadn.net.cn

  • SyncTaskExecutor: 此实现不会异步运行调用。相反,每个 调用发生在调用线程中。它主要用于以下情况 不需要多线程的地方,例如在简单的测试用例中。spring-doc.cadn.net.cn

  • SimpleAsyncTaskExecutor: 此实现不会重用任何线程。相反,它启动了一个新线程 对于每个调用。但是,它确实支持阻止的并发限制 在释放槽位之前超过限制的任何调用。如果你 正在寻找真正的池化,请参阅ThreadPoolTaskExecutor,在此列表的后面。 这将使用 JDK 21 的虚拟线程,当“virtualThreads” 选项已启用。此实现还支持通过 Spring 的生命周期管理。spring-doc.cadn.net.cn

  • ConcurrentTaskExecutor: 此实现是java.util.concurrent.Executor实例。 还有另一种选择(ThreadPoolTaskExecutor)公开Executor配置参数作为 bean 属性。很少需要使用ConcurrentTaskExecutor径直。但是,如果ThreadPoolTaskExecutor莫 足够灵活,满足您的需求,ConcurrentTaskExecutor是一种替代方案。spring-doc.cadn.net.cn

  • ThreadPoolTaskExecutor: 这种实现是最常用的。它公开了用于配置的 bean 属性 一个java.util.concurrent.ThreadPoolExecutor并将其包装在TaskExecutor. 如果您需要适应不同类型的java.util.concurrent.Executor, 我们建议您使用ConcurrentTaskExecutor相反。 它还提供暂停/恢复功能和正常关闭 Spring 的生命周期管理。spring-doc.cadn.net.cn

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

使用TaskExecutor

Spring的TaskExecutor实现通常与依赖注入一起使用。 在下面的示例中,我们定义了一个使用ThreadPoolTaskExecutor异步打印一组消息:spring-doc.cadn.net.cn

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使用其内部规则来 决定何时运行任务。spring-doc.cadn.net.cn

要配置规则,请TaskExecutoruses,我们公开了简单的 bean 属性:spring-doc.cadn.net.cn

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

TaskExecutor实现提供了一种自动包装提交的任务的方法 使用TaskDecorator.装饰器应该委托给它正在包装的任务,可能是 在执行任务之前/之后实现自定义行为。spring-doc.cadn.net.cn

让我们考虑一个简单的实现,它将在执行前后记录消息 或我们的任务:spring-doc.cadn.net.cn

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.core.task.TaskDecorator;

public class LoggingTaskDecorator implements TaskDecorator {

	private static final Log logger = LogFactory.getLog(LoggingTaskDecorator.class);

	@Override
	public Runnable decorate(Runnable runnable) {
		return () -> {
			logger.debug("Before execution of " + runnable);
			runnable.run();
			logger.debug("After execution of " + runnable);
		};
	}
}
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.springframework.core.task.TaskDecorator

class LoggingTaskDecorator : TaskDecorator {

	override fun decorate(runnable: Runnable): Runnable {
		return Runnable {
			logger.debug("Before execution of $runnable")
			runnable.run()
			logger.debug("After execution of $runnable")
		}
	}

	companion object {
		private val logger: Log = LogFactory.getLog(
			LoggingTaskDecorator::class.java
		)
	}
}

然后我们可以在TaskExecutor实例:spring-doc.cadn.net.cn

@Bean
ThreadPoolTaskExecutor decoratedTaskExecutor() {
	ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
	taskExecutor.setTaskDecorator(new LoggingTaskDecorator());
	return taskExecutor;
}
@Bean
fun decoratedTaskExecutor() = ThreadPoolTaskExecutor().apply {
	setTaskDecorator(LoggingTaskDecorator())
}
<bean id="decoratedTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
	<property name="taskDecorator" ref="loggingTaskDecorator"/>
</bean>

如果需要多个装饰器,则org.springframework.core.task.support.CompositeTaskDecorator可用于按顺序执行多个装饰器。spring-doc.cadn.net.cn

SpringTaskScheduler抽象化

除了TaskExecutor抽象,Spring 有一个TaskSchedulerSPI 与 用于调度任务在未来某个时间点运行的各种方法。以下内容 列表显示TaskScheduler接口定义:spring-doc.cadn.net.cn

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只需要一个RunnableInstant. 这会导致任务在指定时间后运行一次。所有其他方法 能够安排任务重复运行。固定速率和固定延迟 方法用于简单的定期执行,但接受Trigger是 更加灵活。spring-doc.cadn.net.cn

Trigger接口

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

public interface Trigger {

	Instant nextExecution(TriggerContext triggerContext);
}

TriggerContext是最重要的部分。它封装了所有相关数据,并在必要时在未来开放扩展。 这TriggerContext是一个接口(一个SimpleTriggerContext实现由默认使用)。以下列表显示了Trigger实现。spring-doc.cadn.net.cn

public interface TriggerContext {

	Clock getClock();

	Instant lastScheduledExecution();

	Instant lastActualExecution();

	Instant lastCompletion();
}

Trigger实现

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

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

另一个实现是PeriodicTrigger接受固定的 period 、可选的初始延迟值和布尔值,用于指示 period 应解释为固定速率或固定延迟。由于TaskScheduler接口已经定义了以固定速率或 固定延迟,应尽可能直接使用这些方法。的值PeriodicTrigger实现是你可以在依赖于 这Trigger抽象化。例如,允许定期触发器可能很方便, 基于 CRON 的触发器,甚至可以互换使用的自定义触发器实现。 这样的组件可以利用依赖注入,以便您可以配置 这样Triggers外部,因此可以轻松修改或扩展它们。spring-doc.cadn.net.cn

TaskScheduler实现

与 Spring 的TaskExecutor抽象,的主要好处是TaskScheduler安排是应用程序的调度需求与部署分离 环境。当部署到 应用服务器环境,其中线程不应由 应用程序本身。对于此类场景,Spring 提供了一个DefaultManagedTaskScheduler委托给 JSR-236ManagedScheduledExecutorService在雅加达 EE 环境中。spring-doc.cadn.net.cn

每当不需要外部线程管理时,更简单的替代方法是 一个本地人ScheduledExecutorService应用程序内的设置,可以进行调整 通过 Spring 的ConcurrentTaskScheduler.为了方便起见,Spring 还提供了一个ThreadPoolTaskScheduler,它在内部委托给ScheduledExecutorService提供通用的 bean 样式配置,沿着ThreadPoolTaskExecutor. 这些变体非常适合宽松的本地嵌入线程池设置 应用程序服务器环境也是如此——特别是在 Tomcat 和 Jetty 上。spring-doc.cadn.net.cn

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

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

Spring 为任务调度和异步方法提供注释支持 执行。spring-doc.cadn.net.cn

启用调度注释

启用对@Scheduled@Async注释,您可以添加@EnableScheduling@EnableAsync给你的一个@Configuration类,或<task:annotation-driven>元素 如以下示例所示:spring-doc.cadn.net.cn

@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接口,或两者兼而有之。请参阅SchedulingConfigurerAsyncConfigurerjavadoc 获取完整详细信息。spring-doc.cadn.net.cn

请注意,在前面的 XML 中,提供了一个执行器引用来处理这些 与具有@Async注释和调度程序 提供了用于管理那些用@Scheduled.spring-doc.cadn.net.cn

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

@Scheduled注解

您可以添加@Scheduled注释到方法,以及触发器元数据。为 例如,以下方法每五秒(5000 毫秒)调用一次,并使用 固定延迟,这意味着该周期是从每个 前面的调用。spring-doc.cadn.net.cn

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

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

例如,前面的例子也可以写成这样。spring-doc.cadn.net.cn

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

如果您需要固定速率执行,可以使用fixedRate属性 注解。以下方法每五秒调用一次(在 每次调用的连续开始时间):spring-doc.cadn.net.cn

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

对于固定延迟和固定速率任务,您可以通过指示 在首次执行方法之前等待的时间量,如下所示fixedRate示例显示:spring-doc.cadn.net.cn

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

对于一次性任务,只需指定初始延迟即可指定数量 在预期执行方法之前等待的时间:spring-doc.cadn.net.cn

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

如果简单的周期性调度不够表达,你可以提供一个 cron 表达式。 以下示例仅在工作日运行:spring-doc.cadn.net.cn

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

请注意,要调度的方法必须具有 void 返回,并且不得接受任何 参数。如果该方法需要与应用程序中的其他对象交互 上下文,这些通常是通过依赖注入提供的。spring-doc.cadn.net.cn

@Scheduled可以用作可重复的注释。如果多个计划声明 在同一方法上找到,它们中的每一个都将被独立处理,并带有 他们每个人的单独扳机发射。因此,这种同地时间表 可以重叠并并行或立即连续执行多次。 请确保您指定的 cron 表达式等不会意外重叠。spring-doc.cadn.net.cn

从 Spring Framework 4.3 开始,@Scheduled任何范围的 bean 都支持方法。spring-doc.cadn.net.cn

确保您没有初始化同一实例的多个实例@Scheduledannotation 类,除非你确实想调度对每个此类类的回调 实例。与此相关,请确保您不要使用@Configurable在豆上 用@Scheduled并注册为普通春豆 与容器。否则,您将获得双重初始化(一次通过 容器,并通过@Configurable方面),以及每个@Scheduled方法被调用两次。spring-doc.cadn.net.cn

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

从 Spring Framework 6.1 开始,@Scheduled多种类型也支持方法 反应方法:spring-doc.cadn.net.cn

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

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

CompletableFutureclass 是通常可以调整的类型的示例 自Publisher但不支持延迟订阅。其ReactiveAdapter在 registry 表示通过将getDescriptor().isDeferred()方法返回false.spring-doc.cadn.net.cn

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

所有这些类型的方法都必须声明,不带任何参数。以 Kotlin 为例 suspending 函数,则kotlinx.coroutines.reactor桥也必须存在才能允许 将挂起函数调用为的框架Publisher.spring-doc.cadn.net.cn

Spring Framework 将获得一个Publisher对于带注释的方法一次,将 附表ARunnable其中它订阅了所述Publisher.这些内在常规 订阅根据相应的cron/fixedDelay/fixedRate配置。spring-doc.cadn.net.cn

如果Publisher发出onNext信号,这些信号将被忽略和丢弃(以相同的方式 同步返回值@Scheduled方法被忽略)。spring-doc.cadn.net.cn

在以下示例中,Flux发出onNext("Hello"),onNext("World")每 5 次 秒,但这些值未使用:spring-doc.cadn.net.cn

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

如果Publisher发出onError信号,则记录在WARN水平并恢复。 由于Publisher实例,例外是 不是从Runnabletask:这意味着ErrorHandler合同不是 涉及反应性方法。spring-doc.cadn.net.cn

因此,尽管出现错误,但仍会发生进一步的计划订阅。spring-doc.cadn.net.cn

在以下示例中,Mono订阅在前五秒内失败两次。 然后订阅开始成功,每五个将一条消息打印到标准输出 秒:spring-doc.cadn.net.cn

@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也 作为当前仍处于活动状态的任何过去订阅(例如,对于长时间运行的发布者 甚至无限的出版商)。spring-doc.cadn.net.cn

@Async注解

您可以提供@Async注释,以便调用该方法 异步发生。换句话说,调用方在 调用,而该方法的实际执行发生在已 提交给弹簧TaskExecutor.在最简单的情况下,您可以应用注释 到返回void,如以下示例所示:spring-doc.cadn.net.cn

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

与用@Scheduled注释,这些方法可以期待 参数,因为它们是由调用者在运行时以“正常”方式调用的,而不是 而不是从容器管理的计划任务。例如,以下内容 代码是@Async注解:spring-doc.cadn.net.cn

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

即使是返回值的方法也可以异步调用。但是,此类方法需要具有Future-typed 返回值。这仍然提供了异步执行,以便调用者可以在调用之前执行其他任务get()关于这一点Future. 以下示例演示如何使用@Async在方法上返回一个值:spring-doc.cadn.net.cn

@Async
Future<String> returnSomething(int i) {
	// this will be run asynchronously
}
@Async方法不仅可以声明一个常规的java.util.concurrent.Future返回 类型,还有 Spring 的org.springframework.util.concurrent.ListenableFuture或者,截至 Spring 4.2,JDK 8 的java.util.concurrent.CompletableFuture,以便与 异步任务和通过进一步处理步骤立即组合。

你不能使用@Async结合生命周期回调,例如@PostConstruct. 要异步初始化 Spring bean,您目前必须使用单独的 初始化 Spring bean,然后调用@Async标注的方法, 如以下示例所示:spring-doc.cadn.net.cn

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 等效项@Async,因为应该设计这样的方法 首先用于异步执行,而不是在外部重新声明为异步。 但是,您可以手动设置 Spring 的AsyncExecutionInterceptor与 Spring AOP, 与自定义切入点相结合。

执行人资格@Async

默认情况下,当指定@Async在方法上,使用的执行器是启用异步支持时配置的执行器,即“注释驱动”元素,如果您使用的是 XML 或AsyncConfigurer实现(如果有)。但是,您可以使用value属性的@Async注释,当您需要指示默认执行器以外的执行器时,应为执行给定方法时使用。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

在这种情况下,"otherExecutor"可以是任何Executorbean 在 Spring 中容器,或者它可以是与任何关联的限定符的名称Executor(例如,如<qualifier>元素或 Spring 的@Qualifier注释)。spring-doc.cadn.net.cn

异常管理@Async

@Async方法有一个Future-typed 返回值,易于管理在方法执行过程中抛出的异常,因为此异常是调用时抛出getFuture结果。 使用void返回类型,但是,异常未捕获且无法传输。您可以提供AsyncUncaughtExceptionHandler来处理此类异常。以下示例显示了如何做到这一点:spring-doc.cadn.net.cn

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

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

默认情况下,仅记录异常。您可以定义自定义AsyncUncaughtExceptionHandler通过使用AsyncConfigurer<task:annotation-driven/>XML 元素。spring-doc.cadn.net.cn

taskNamespace

从 3.0 版开始,Spring 包括一个用于配置 XML 命名空间TaskExecutorTaskScheduler实例。 它还提供了一种将任务配置为的便捷方法使用触发器调度。spring-doc.cadn.net.cn

scheduler元素

以下元素创建了一个ThreadPoolTaskScheduler实例,具有指定的线程池大小:spring-doc.cadn.net.cn

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

id属性用作线程名称的前缀在池中。 这scheduler元素相对简单。如果您不这样做 提供一个pool-size属性,则默认线程池只有一个线程。 调度程序没有其他配置选项。spring-doc.cadn.net.cn

executor元素

下面创建一个ThreadPoolTaskExecutor实例:spring-doc.cadn.net.cn

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

上一节中显示的调度程序一样, 为id属性用作线程名称的前缀 游泳池。就池大小而言,executor元素支持更多 配置选项而不是scheduler元素。一方面,线程池 一个ThreadPoolTaskExecutor本身更具可配置性。不仅仅是单一尺寸, 执行器的线程池可以有不同的核心值和最大大小。 如果您提供单个值,则执行器具有一个固定大小的线程池(核心和 最大尺寸相同)。但是,executor元素的pool-size属性也 接受以下形式的范围min-max.以下示例将最小值设置为5最大值25:spring-doc.cadn.net.cn

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

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

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

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

以下示例显示了executor元素,其中包含要指定的多个属性 各种行为:spring-doc.cadn.net.cn

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

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

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

scheduled-tasks元素

Spring 的任务命名空间最强大的特性是支持配置 在 Spring Application Context 中调度的任务。这遵循一种方法 类似于 Spring 中的其他“方法调用器”,例如 JMS 命名空间提供的 用于配置消息驱动的 POJO。基本上,一个ref属性可以指向任何Spring 管理的对象,并且method属性提供要调用的方法的名称在该对象上调用。以下列表显示了一个简单的示例:spring-doc.cadn.net.cn

<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-ratetasks,您可以指定一个 'initial-delay' 参数,指示 在首次执行该方法之前等待毫秒。为了获得更多控制, 您可以改为提供cron属性来提供 cron 表达式。 以下示例显示了这些其他选项:spring-doc.cadn.net.cn

<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 个组成 空格分隔的时间和日期字段,每个字段都有自己的有效值范围:* * * * * *spring-doc.cadn.net.cn

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

有一些规则适用:spring-doc.cadn.net.cn

  • 字段可以是星号 (),它始终代表“从头到尾”。 对于“月份中的某一天”或“星期几”字段,问号 (*?) 可以代替 星号。spring-doc.cadn.net.cn

  • 逗号 (,) 用于分隔列表中的项目。spring-doc.cadn.net.cn

  • 用连字符 () 分隔的两个数字表示数字范围。 指定的范围是包含的。-spring-doc.cadn.net.cn

  • 在范围(或 )后面指定数字值在该范围内的间隔。*/spring-doc.cadn.net.cn

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

  • 月中日和星期几字段可以包含L字符,具有不同的含义。spring-doc.cadn.net.cn

    • 在“月日”字段中,L代表当月的最后一天。 如果后跟负偏移量(即L-n),这意味着n每月的最后一天.spring-doc.cadn.net.cn

    • 在星期几字段中,L代表一周的最后一天。 如果以数字或三个字母的名称 (dLDDDL),这意味着一周的最后一天 (dDDD) 在当月.spring-doc.cadn.net.cn

  • day-of-month 字段可以是nW,代表每月中最近的工作日和某一天n. 如果n落在星期六,这得出了它之前的星期五。 如果n落在周日,这会产生之后的星期一,如果n1并落在 星期六(即:1W代表当月的第一个工作日)。spring-doc.cadn.net.cn

  • 如果 day-of-month 字段为LW,表示当月的最后一个工作日spring-doc.cadn.net.cn

  • 星期几字段可以是d#n(或DDD#n),代表n一周中的第 1 天d(或DDD) 在当月.spring-doc.cadn.net.cn

这里有些例子:spring-doc.cadn.net.cn

Cron 表达式 意义

0 0 * * * *spring-doc.cadn.net.cn

每天每小时的顶部spring-doc.cadn.net.cn

*/10 * * * * *spring-doc.cadn.net.cn

每十秒spring-doc.cadn.net.cn

0 0 8-10 * * *spring-doc.cadn.net.cn

每天8点、9点、10点spring-doc.cadn.net.cn

0 0 6,19 * * *spring-doc.cadn.net.cn

每天早上 6:00 和晚上 7:00spring-doc.cadn.net.cn

0 0/30 8-10 * * *spring-doc.cadn.net.cn

每天 8:00、8:30、9:00、9:30、10:00 和 10:30spring-doc.cadn.net.cn

0 0 9-17 * * MON-FRIspring-doc.cadn.net.cn

工作日朝九晚五的小时spring-doc.cadn.net.cn

0 0 0 25 DEC ?spring-doc.cadn.net.cn

每年圣诞节午夜spring-doc.cadn.net.cn

0 0 0 L * *spring-doc.cadn.net.cn

每月最后一天午夜spring-doc.cadn.net.cn

0 0 0 L-3 * *spring-doc.cadn.net.cn

每月倒数第三天午夜spring-doc.cadn.net.cn

0 0 0 * * 5Lspring-doc.cadn.net.cn

每月最后一个星期五午夜spring-doc.cadn.net.cn

0 0 0 * * THULspring-doc.cadn.net.cn

每月最后一个星期四午夜spring-doc.cadn.net.cn

0 0 0 1W * *spring-doc.cadn.net.cn

每月第一个工作日午夜spring-doc.cadn.net.cn

0 0 0 LW * *spring-doc.cadn.net.cn

每月最后一个工作日午夜spring-doc.cadn.net.cn

0 0 0 ? * 5#2spring-doc.cadn.net.cn

每月第二个星期五午夜spring-doc.cadn.net.cn

0 0 0 ? * MON#1spring-doc.cadn.net.cn

每月第一个星期一午夜spring-doc.cadn.net.cn

诸如0 0 * * * *人类很难解析,因此, 如果出现错误,很难修复。为了提高可读性,Spring 支持以下内容 宏,表示常用序列。您可以改用这些宏 六位数值,因此:@Scheduled(cron = "@hourly").spring-doc.cadn.net.cn

意义

@yearly(或@annually)spring-doc.cadn.net.cn

每年一次(0 0 0 1 1 *)spring-doc.cadn.net.cn

@monthlyspring-doc.cadn.net.cn

每月一次(0 0 0 1 * *)spring-doc.cadn.net.cn

@weeklyspring-doc.cadn.net.cn

每周一次(0 0 0 * * 0)spring-doc.cadn.net.cn

@daily(或@midnight)spring-doc.cadn.net.cn

每天一次(0 0 0 * * *),或spring-doc.cadn.net.cn

@hourlyspring-doc.cadn.net.cn

每小时一次,(0 0 * * * *)spring-doc.cadn.net.cn

使用 Quartz 调度程序

Quartz用途Trigger,JobJobDetail对象实现所有 各种工作。有关 Quartz 背后的基本概念,请参阅 Quartz 网站。为方便起见,Spring 提供了几个类,可以简化在基于 Spring 的应用程序中使用 Quartz。spring-doc.cadn.net.cn

使用JobDetailFactoryBean

QuartzJobDetail对象包含运行作业所需的所有信息。Spring 提供一个JobDetailFactoryBean,它为 XML 提供 bean 样式属性 配置目的。请考虑以下示例:spring-doc.cadn.net.cn

<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包含名为timeoutJobDetail是否自动应用:spring-doc.cadn.net.cn

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

作业数据映射中的所有其他属性也可供您使用。spring-doc.cadn.net.cn

通过使用namegroup属性,您可以修改名称和组 分别是工作的。缺省情况下,作业的名称与 Bean 名称匹配 的JobDetailFactoryBean (exampleJob在上面的示例中)。

使用MethodInvokingJobDetailFactoryBean

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

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

前面的示例导致doItexampleBusinessObject方法,如以下示例所示:spring-doc.cadn.net.cn

public class ExampleBusinessObject {

	// properties and collaborators

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

通过使用MethodInvokingJobDetailFactoryBean,则无需创建单行作业 这只是调用一种方法。您只需创建实际业务对象和 连接细节对象。spring-doc.cadn.net.cn

默认情况下,Quartz 作业是无状态的,导致作业可能会干扰 彼此之间。如果为同一触发器指定两个触发器JobDetail,这是可能的 第二个作业在第一个作业完成之前开始。如果JobDetail类 实现Stateful接口,不会发生这种情况:第二个作业没有启动 在第一个完成之前。spring-doc.cadn.net.cn

要使作业由MethodInvokingJobDetailFactoryBean非并发, 将concurrentflag 到false,如以下示例所示:spring-doc.cadn.net.cn

<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

我们创建了工作详细信息和工作。我们还审查了便利豆 允许您在特定对象上调用方法。当然,我们还需要调度 工作本身。这是通过使用触发器和SchedulerFactoryBean.几个 触发器在 Quartz 中可用,Spring 提供两个 QuartzFactoryBean具有方便默认值的实现:CronTriggerFactoryBeanSimpleTriggerFactoryBean.spring-doc.cadn.net.cn

需要安排触发器。Spring 提供了一个SchedulerFactoryBean这暴露了 触发器设置为属性。SchedulerFactoryBean使用 那些触发因素。spring-doc.cadn.net.cn

以下列表同时使用SimpleTriggerFactoryBeanCronTriggerFactoryBean:spring-doc.cadn.net.cn

<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,如以下示例所示:spring-doc.cadn.net.cn

<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。看 这SchedulerFactoryBeanjavadoc 了解更多信息。spring-doc.cadn.net.cn

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