此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
任务执行和调度
Spring Framework 为异步执行和调度提供了抽象tasks 与TaskExecutor
和TaskScheduler
接口。Spring 还功能支持线程池或委托给CommonJ 的接口的实现。最终,使用这些通用接口背后的实现抽象化了Java SE 和 Jakarta EE 环境之间的差异。
Spring 还具有集成类,以支持使用 Quartz Scheduler 进行调度。
SpringTaskExecutor
抽象化
执行器是线程池概念的 JDK 名称。“执行器”的命名是因为不能保证底层实现是实际上是一个池。执行器可以是单线程的,甚至可以是同步的。Spring 的抽象隐藏了 Java SE 和 Jakarta EE 环境之间的实现细节。
Spring的TaskExecutor
接口与java.util.concurrent.Executor
接口。 事实上,最初,它存在的主要原因是抽象化使用线程池时对 Java 5 的需求。该接口只有一个方法 (execute(Runnable task)
)接受基于语义执行的任务和线程池的配置。
这TaskExecutor
最初是为了给其他 Spring 组件一个抽象用于线程池。组件,例如ApplicationEventMulticaster
, JMS 的AbstractMessageListenerContainer
,和 Quartz 集成都使用TaskExecutor
抽象来池线程。但是,如果您的 Bean 需要线程池行为,您也可以根据自己的需要使用此抽象。
TaskExecutor
类型
Spring 包括许多预构建的TaskExecutor
. 很可能,您永远不需要实现自己的。Spring 提供的变体如下:
-
SyncTaskExecutor
: 此实现不会异步运行调用。相反,每个调用都发生在调用线程中。它主要用于以下情况不需要多线程,例如在简单的测试用例中。 -
SimpleAsyncTaskExecutor
: 此实现不会重用任何线程。相反,它启动一个新线程对于每个调用。但是,它确实支持阻止任何超过限制的调用,直到插槽被释放。如果你正在寻找真正的池化,请参阅ThreadPoolTaskExecutor
,在此列表后面。这将使用 JDK 21 的虚拟线程,当启用“virtualThreads”选项时选项。此实现还支持通过Spring 的生命周期管理进行正常关闭。 -
ConcurrentTaskExecutor
: 此实现是java.util.concurrent.Executor
实例。 还有另一种选择(ThreadPoolTaskExecutor
)公开Executor
配置参数作为 bean 属性。很少需要使用ConcurrentTaskExecutor
径直。 但是,如果ThreadPoolTaskExecutor
莫 足够灵活,满足您的需求,ConcurrentTaskExecutor
是一种替代方案。 -
ThreadPoolTaskExecutor
: 这种实现是最常用的。它公开了用于配置的 bean 属性 一个java.util.concurrent.ThreadPoolExecutor
并将其包装在TaskExecutor
. 如果您需要适应不同类型的java.util.concurrent.Executor
, 我们建议您使用ConcurrentTaskExecutor
相反。 它还通过Spring 的生命周期管理提供暂停/恢复功能和正常关闭。 -
DefaultManagedTaskExecutor
: 此实现使用 JNDI 获取的ManagedExecutorService
在 JSR-236 中 兼容的运行时环境(例如 Jakarta EE 应用程序服务器), 为此目的替换 CommonJ WorkManager。
使用TaskExecutor
Spring的TaskExecutor
实现通常与依赖注入一起使用。
在下面的示例中,我们定义了一个使用ThreadPoolTaskExecutor
异步打印一组消息:
-
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
使用其内部规则来
决定何时运行任务。
要配置规则,请TaskExecutor
uses,我们公开了简单的 bean 属性:
-
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>
最TaskExecutor
实现提供了一种自动包装提交的任务的方法
使用TaskDecorator
.装饰器应该委托给它正在包装的任务,可能是
在执行任务之前/之后实现自定义行为。
让我们考虑一个简单的实现,它将在执行前后记录消息 或我们的任务:
-
Java
-
Kotlin
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
实例:
-
Java
-
Kotlin
-
Xml
@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
可用于按顺序执行多个装饰器。
SpringTaskScheduler
抽象化
除了TaskExecutor
抽象,Spring 有一个TaskScheduler
SPI 与
用于调度任务在未来某个时间点运行的各种方法。以下内容
列表显示TaskScheduler
接口定义:
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
是
更加灵活。
Trigger
接口
这Trigger
界面本质上是受到 JSR-236 的启发。的基本思想Trigger
是执行时间可以根据过去的执行结果或
甚至是任意条件。如果这些决定考虑了
在执行之前,该信息在TriggerContext
.这Trigger
界面本身非常简单,如以下列表所示:
public interface Trigger {
Instant nextExecution(TriggerContext triggerContext);
}
这TriggerContext
是最重要的部分。它封装了所有
相关数据,并在必要时在未来开放扩展。这TriggerContext
是一个接口(一个SimpleTriggerContext
实现由
默认)。以下列表显示了以下可用方法Trigger
实现。
public interface TriggerContext {
Clock getClock();
Instant lastScheduledExecution();
Instant lastActualExecution();
Instant lastCompletion();
}
Trigger
实现
Spring 提供了两个Trigger
接口。最有趣的一个
是CronTrigger
.它支持基于 cron 表达式的任务调度。
例如,以下任务计划在每小时运行 15 分钟后运行,但仅
在工作日朝九晚五的“工作时间”内:
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
另一个实现是PeriodicTrigger
接受固定的
period 、可选的初始延迟值和布尔值,用于指示 period
应解释为固定速率或固定延迟。由于TaskScheduler
接口已经定义了以固定速率或
固定延迟,应尽可能直接使用这些方法。的值PeriodicTrigger
实现是你可以在依赖于
这Trigger
抽象化。例如,允许定期触发器可能很方便,
基于 CRON 的触发器,甚至可以互换使用的自定义触发器实现。
这样的组件可以利用依赖注入,以便您可以配置
这样Triggers
外部,因此可以轻松修改或扩展它们。
TaskScheduler
实现
与 Spring 的TaskExecutor
抽象,的主要好处是TaskScheduler
安排是应用程序的调度需求与部署分离 环境。 当部署到应用程序服务器环境时,这种抽象级别尤其重要,其中线程不应直接由应用程序本身创建。对于这种情况,Spring 提供了一个DefaultManagedTaskScheduler
委托给 JSR-236ManagedScheduledExecutorService
在雅加达 EE 环境中。
每当不需要外部线程管理时,一个更简单的替代方法是本地ScheduledExecutorService
应用程序中的设置,可以调整通过 Spring 的ConcurrentTaskScheduler
. 为了方便起见,Spring 还提供了一个ThreadPoolTaskScheduler
,它在内部委托给ScheduledExecutorService
提供通用的 bean 样式配置,沿着ThreadPoolTaskExecutor
. 这些变体非常适合宽松的本地嵌入线程池设置应用程序服务器环境,尤其是在 Tomcat 和 Jetty 上。
从 6.1 开始,ThreadPoolTaskScheduler
提供暂停/恢复功能和优雅的关闭,通过 Spring 的生命周期管理。还有一个新选项,称为SimpleAsyncTaskScheduler
它与 JDK 21 的虚拟线程保持一致,使用单个调度程序线程,但为每个计划任务执行启动一个新线程(除了所有在单个调度程序线程上运行的固定延迟任务,因此对于建议使用此虚拟线程对齐选项,建议使用固定速率和 cron 触发器)。
对调度和异步执行的注释支持
Spring 为任务调度和异步方法提供注释支持 执行。
启用调度注释
启用对@Scheduled
和@Async
注释,您可以添加@EnableScheduling
和@EnableAsync
给你的一个@Configuration
类,或<task:annotation-driven>
元素 如以下示例所示:
-
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
接口,或两者。请参阅SchedulingConfigurer
和AsyncConfigurer
javadoc 获取完整详细信息。
请注意,在前面的 XML 中,提供了一个执行器引用来处理那些任务,这些任务对应于具有@Async
注释,调度程序提供了用于管理那些用@Scheduled
.
处理的默认通知方式@Async 注释是proxy 这允许仅用于通过代理拦截呼叫。同一类中的本地调用不会以这种方式被拦截。对于更高级的拦截模式,请考虑切换到aspectj 模式与编译时或加载时编织相结合。 |
这@Scheduled
注解
您可以添加@Scheduled
注释到方法,以及触发器元数据。为
例如,以下方法每五秒(5000 毫秒)调用一次,并使用
固定延迟,这意味着该周期是从每个
前面的调用。
@Scheduled(fixedDelay = 5000)
public void doSomething() {
// something that should run periodically
}
默认情况下,毫秒将用作固定延迟、固定速率和
初始延迟值。如果您想使用不同的时间单位,例如秒或
分钟,您可以通过 例如,前面的例子也可以写成这样。
|
如果您需要固定速率执行,可以使用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
}
您还可以使用zone 属性来指定 cron 所在的时区
表达式已解析。 |
请注意,要调度的方法必须具有 void 返回,并且不得接受任何 参数。如果该方法需要与应用程序中的其他对象交互 上下文,这些通常是通过依赖注入提供的。
@Scheduled
可以用作可重复的注释。如果多个计划声明
在同一方法上找到,它们中的每一个都将被独立处理,并带有
他们每个人的单独扳机发射。因此,这种同地时间表
可以重叠并并行或立即连续执行多次。
请确保您指定的 cron 表达式等不会意外重叠。
从 Spring Framework 4.3 开始, 确保您没有初始化同一实例的多个实例 |
这@Scheduled
响应式方法或 Kotlin 挂起函数的注释
从 Spring Framework 6.1 开始,@Scheduled
几种类型也支持方法反应方法:
-
方法
Publisher
返回类型(或Publisher
) 如以下示例所示:
@Scheduled(fixedDelay = 500)
public Publisher<Void> reactiveSomething() {
// return an instance of Publisher
}
-
方法具有可适应的返回类型
Publisher
通过共享实例的ReactiveAdapterRegistry
,前提是该类型支持延迟订阅,例如在以下示例中:
@Scheduled(fixedDelay = 500)
public Single<String> rxjavaNonPublisher() {
return Single.just("example");
}
这 |
-
Kotlin 挂起函数,如以下示例所示:
@Scheduled(fixedDelay = 500)
suspend fun something() {
// do something asynchronous
}
-
返回 Kotlin 的方法
Flow
或Deferred
实例,如以下示例所示:
@Scheduled(fixedDelay = 500)
fun something(): Flow<Void> {
flow {
// do something asynchronous
}
}
所有这些类型的方法都必须声明,不带任何参数。以 Kotlin 为例
suspending 函数,则kotlinx.coroutines.reactor
桥也必须存在才能允许
将挂起函数调用为的框架Publisher
.
Spring Framework 将获得一个Publisher
对于带注释的方法一次,将
附表ARunnable
其中它订阅了所述Publisher
. 这些内部常规订阅根据相应的cron
/fixedDelay
/fixedRate
配置。
如果Publisher
发出onNext
信号,这些信号将被忽略和丢弃(以相同的方式从同步返回值@Scheduled
方法被忽略)。
在以下示例中,Flux
发出onNext("Hello")
,onNext("World")
每 5秒,但这些值未使用:
@Scheduled(initialDelay = 5000, fixedRate = 5000)
public Flux<String> reactiveSomething() {
return Flux.just("Hello", "World");
}
如果Publisher
发出onError
信号,则记录在WARN
级别并恢复。由于Publisher
实例中,异常是not 从中抛出Runnable
task:这意味着ErrorHandler
合同不是涉及反应方法。
因此,尽管出现错误,但仍会发生进一步的计划订阅。
在以下示例中,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 会取消计划任务,其中包括对 |
这@Async
注解
您可以提供@Async
注释,以便该方法的调用异步发生。换句话说,调用者在调用时立即返回调用,而该方法的实际执行发生在已提交给 SpringTaskExecutor
. 在最简单的情况下,您可以将注释应用于返回void
,如以下示例所示:
@Async
void doSomething() {
// this will be run asynchronously
}
与用@Scheduled
注释,这些方法可以期待参数,因为它们是由调用者在运行时以“正常”方式调用的,而不是而不是从容器管理的计划任务中调用。例如,以下code 是@Async
注解:
@Async
void doSomething(String s) {
// this will be run asynchronously
}
即使是返回值的方法也可以异步调用。但是,此类方法需要具有Future
-typed 返回值。这仍然提供了异步执行,以便调用者可以在调用之前执行其他任务get()
关于这一点Future
. 以下示例演示如何使用@Async
在方法上返回一个值:
@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
annotated 方法,如以下示例所示:
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
注释,当您需要指示默认执行器以外的执行器时,应为执行给定方法时使用。以下示例显示了如何执行此作:
@Async("otherExecutor")
void doSomething(String s) {
// this will be run asynchronously by "otherExecutor"
}
在这种情况下,"otherExecutor"
可以是任何Executor
Spring的豆子
容器,或者它可以是与任何Executor
(例如,
如<qualifier>
元素或 Spring 的@Qualifier
注释)。
异常管理@Async
当@Async
方法有一个Future
-typed 返回值,易于管理
在方法执行期间抛出的异常,因为此异常是
调用时抛出get
在Future
结果。使用void
返回类型,
但是,异常未捕获且无法传输。您可以提供AsyncUncaughtExceptionHandler
来处理此类异常。以下示例显示
如何做到这一点:
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
默认情况下,仅记录异常。您可以定义自定义AsyncUncaughtExceptionHandler
通过使用AsyncConfigurer
或<task:annotation-driven/>
XML 元素。
这task
Namespace
从 3.0 版开始,Spring 包括一个用于配置 XML 命名空间TaskExecutor
和TaskScheduler
实例。它还提供了一种将任务配置为
使用触发器调度。
这scheduler
元素
以下元素创建了一个ThreadPoolTaskScheduler
实例与
指定的线程池大小:
<task:scheduler id="scheduler" pool-size="10"/>
为id
属性用作线程名称的前缀
在游泳池内。这scheduler
元素相对简单。如果您不这样做
提供一个pool-size
属性,则默认线程池只有一个线程。
调度程序没有其他配置选项。
这executor
元素
下面创建一个ThreadPoolTaskExecutor
实例:
<task:executor id="executor" pool-size="10"/>
与上一节中显示的调度程序一样,
为id
属性用作线程名称的前缀
游泳池。就池大小而言,executor
元素支持更多
配置选项而不是scheduler
元素。一方面,线程池
一个ThreadPoolTaskExecutor
本身更具可配置性。不仅仅是单一尺寸,
执行器的线程池可以有不同的核心值和最大大小。
如果您提供单个值,则执行器具有一个固定大小的线程池(核心和
最大尺寸相同)。但是,executor
元素的pool-size
属性也
接受以下形式的范围min-max
.以下示例将最小值设置为5
最大值25
:
<task:executor
id="executorWithPoolSizeRange"
pool-size="5-25"
queue-capacity="100"/>
在前面的配置中,queue-capacity
还提供了价值。
线程池的配置还应根据
执行器的队列容量。有关池之间关系的完整描述
大小和队列容量,请参阅文档ThreadPoolExecutor
. 主要思想是,当任务提交时,执行器首先尝试使用free 线程,如果活动线程数当前小于核心大小。如果已达到核心大小,则任务将添加到队列中,只要其容量尚未达到。只有这样,如果队列的容量已达到,执行器才会创建一个超出核心大小的新线程。如果最大大小也已达到,则执行器将拒绝该任务。
默认情况下,队列是无界的,但这很少是所需的配置,因为它可能导致OutOfMemoryError
如果将足够多的任务添加到该队列中,而所有池线程都很忙。此外,如果队列是无界的,则最大大小具有完全没有影响。由于执行器总是在创建新的队列之前尝试队列线程超过核心大小,队列必须具有有限的容量,线程池才能增长超过核心大小(这就是为什么固定大小的池是唯一合理的情况使用无界队列时)。
考虑如上所述,当任务被拒绝时。默认情况下,当任务被拒绝时,线程池执行器会抛出一个TaskRejectedException
. 然而 拒绝策略实际上是可配置的。使用默认拒绝策略,即AbortPolicy
实现。 对于在重负载下可以跳过某些任务的应用程序,您可以改为配置DiscardPolicy
或DiscardOldestPolicy
. 另一个有效的选项适用于需要在重负载下限制提交任务的应用程序是 这CallerRunsPolicy
. 不是抛出异常或丢弃任务,该策略强制调用 submit 方法的线程运行任务本身。这个想法是这样的调用者在运行该任务时很忙,无法提交其他任务立即。因此,它提供了一种简单的方法来限制传入的加载,同时保持线程池和队列的限制。通常,这允许执行器“赶上”它正在处理的任务,从而释放一些队列、池中的容量或两者兼而有之。您可以从可用于rejection-policy
属性executor
元素。
以下示例显示了executor
元素,其中包含要指定的多个属性
各种行为:
<task:executor
id="executorWithCallerRunsPolicy"
pool-size="5-25"
queue-capacity="100"
rejection-policy="CALLER_RUNS"/>
最后,keep-alive
设置确定线程的时间限制(以秒为单位)
在停止之前可能会保持空闲状态。如果线程数超过核心数
当前在池中,在等待此时间而不处理任务后,超出
线程被停止。时间值为零会导致多余的线程停止
在执行任务后立即执行任务,任务队列中没有剩余的后续工作。
以下示例将keep-alive
值设置为两分钟:
<task:executor
id="executorWithKeepAlive"
pool-size="5-25"
keep-alive="120"/>
这scheduled-tasks
元素
Spring 的任务命名空间最强大的特性是支持配置
在 Spring Application Context 中调度的任务。这遵循一种方法
类似于 Spring 中的其他“方法调用器”,例如 JMS 命名空间提供的
用于配置消息驱动的 POJO。基本上,一个ref
属性可以指向任何
Spring 管理的对象,以及method
属性提供要成为的方法的名称
调用。以下列表显示了一个简单的示例:
<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-delay
和fixed-rate
tasks,您可以指定一个 'initial-delay' 参数,指示
在首次执行该方法之前等待毫秒。为了获得更多控制,
您可以改为提供cron
属性来提供 cron 表达式。
以下示例显示了这些其他选项:
<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
字符,具有不同的含义。-
在“月日”字段中,
L
代表当月的最后一天。 如果后跟负偏移量(即L-n
),这意味着n
每月的最后一天. -
在星期几字段中,
L
代表一周的最后一天。 如果以数字或三个字母的名称 (dL
或DDDL
),这意味着一周的最后一天 (d
或DDD
) 在当月.
-
-
day-of-month 字段可以是
nW
,代表每月中最近的工作日和某一天n
. 如果n
落在星期六,这得出了它之前的星期五。 如果n
落在周日,这会产生之后的星期一,如果n
是1
并落在星期六(即:1W
代表当月的第一个工作日)。 -
如果 day-of-month 字段为
LW
,表示当月的最后一个工作日。 -
星期几字段可以是
d#n
(或DDD#n
),代表这n
一周中的第 1 天d
(或DDD
) 在当月.
这里有些例子:
Cron 表达式 | 意义 |
---|---|
|
每天每小时的顶部 |
|
每十秒 |
|
每天8点、9点、10点 |
|
每天早上 6:00 和晚上 7:00 |
|
每天 8:00、8:30、9:00、9:30、10:00 和 10:30 |
|
工作日朝九晚五的小时 |
|
每年圣诞节午夜 |
|
每月最后一天午夜 |
|
每月倒数第三天午夜 |
|
每月最后一个星期五午夜 |
|
每月最后一个星期四午夜 |
|
每月第一个工作日午夜 |
|
每月最后一个工作日午夜 |
|
每月第二个星期五午夜 |
|
每月第一个星期一午夜 |
使用 Quartz 调度程序
Quartz用途Trigger
,Job
和JobDetail
对象实现所有
各种工作。有关 Quartz 背后的基本概念,请参阅 Quartz 网站。为方便起见,Spring
提供了几个类,可以简化在基于 Spring 的应用程序中使用 Quartz。
使用JobDetailFactoryBean
QuartzJobDetail
对象包含运行作业所需的所有信息。Spring
提供一个JobDetailFactoryBean
,它为 XML 提供 bean 样式属性
配置目的。请考虑以下示例:
<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
和JobDetail
是否自动应用:
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
}
}
作业数据映射中的所有其他属性也可供您使用。
通过使用name 和group 属性,您可以修改名称和组
分别是工作的。缺省情况下,作业的名称与 Bean 名称匹配
的JobDetailFactoryBean (exampleJob 在上面的示例中)。 |
使用MethodInvokingJobDetailFactoryBean
通常,您只需要在特定对象上调用一个方法。通过使用MethodInvokingJobDetailFactoryBean
,您可以完全执行此作,如以下示例所示:
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
</bean>
前面的示例导致doIt
在exampleBusinessObject
方法,如以下示例所示:
public class ExampleBusinessObject {
// properties and collaborators
public void doIt() {
// do the actual work
}
}
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
通过使用MethodInvokingJobDetailFactoryBean
,则无需创建单行作业
这只是调用一种方法。您只需创建实际业务对象和
连接细节对象。
默认情况下,Quartz 作业是无状态的,导致作业可能会干扰
彼此之间。如果为同一触发器指定两个触发器JobDetail
,这是可能的
第二个作业在第一个作业完成之前开始。如果JobDetail
类
实现Stateful
接口,不会发生这种情况:第二个作业没有启动
在第一个完成之前。
要使作业由MethodInvokingJobDetailFactoryBean
非并发,
将concurrent
flag 到false
,如以下示例所示:
<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
具有方便默认值的实现:CronTriggerFactoryBean
和SimpleTriggerFactoryBean
.
需要安排触发器。Spring 提供了一个SchedulerFactoryBean
这暴露了
触发器设置为属性。SchedulerFactoryBean
使用
那些触发因素。
以下列表同时使用SimpleTriggerFactoryBean
和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
,如以下示例所示:
<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 了解更多信息。
SchedulerFactoryBean 还识别quartz.properties 类路径中的文件,基于 Quartz 属性键,与常规 Quartz 配置一样。请注意,许多SchedulerFactoryBean 设置与属性文件中的常见 Quartz 设置交互;因此,不建议在这两个级别指定值。例如,不要设置“org.quartz.jobStore.class”属性,如果您打算依赖 Spring 提供的 DataSource,或指定org.springframework.scheduling.quartz.LocalDataSourceJobStore 变体,其中是标准的全面替代品org.quartz.impl.jdbcjobstore.JobStoreTX . |