|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
任务执行与调度
Spring 框架通过 TaskExecutor 和 TaskScheduler 接口分别为任务的异步执行和调度提供了抽象。Spring 还提供了这些接口的实现,支持线程池或在应用服务器环境中委托给 CommonJ。最终,在通用接口背后使用这些实现可以屏蔽 Java SE 和 Jakarta EE 环境之间的差异。
Spring 还提供了集成类以支持与 Quartz 调度器 一起进行任务调度。
Spring TaskExecutor 抽象
执行器(Executors)是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。 -
ConcurrentTaskExecutor: 此实现是java.util.concurrent.Executor实例的适配器。 有一个替代方案(ThreadPoolTaskExecutor),它将Executor配置参数作为bean属性公开。很少需要直接使用ConcurrentTaskExecutor。但是,如果ThreadPoolTaskExecutor对您的需求不够灵活,ConcurrentTaskExecutor是一个替代方案。 -
ThreadPoolTaskExecutor: 此实现最常被使用。它公开 bean 属性以配置一个java.util.concurrent.ThreadPoolExecutor并将其包装在一个TaskExecutor中。 如果您需要适应不同类型的java.util.concurrent.Executor,我们建议您改用ConcurrentTaskExecutor。 -
DefaultManagedTaskExecutor: 此实现在符合 JSR-236 的运行时环境(例如 Jakarta EE 应用服务器)中使用通过 JNDI 获取的ManagedExecutorService,以替代 CommonJ WorkManager 实现该目的。
使用 TaskExecutor
Spring的TaskExecutor实现通常与依赖注入一起使用。
在以下示例中,我们定义一个使用ThreadPoolTaskExecutor异步打印一组消息的Bean:
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));
}
}
}
如你所见,而不是从线程池中获取一个线程并自己执行它,
你将你的 Runnable 添加到队列中。然后 TaskExecutor 会根据其内部规则决定何时运行该任务。
要配置TaskExecutor使用的规则,我们提供了简单的Bean属性:
<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>
Spring TaskScheduler 抽象
除了 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分钟执行,但仅限于工作日的上午9点到下午5点“工作时间”内:
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
另一种实现是PeriodicTrigger,它接受一个固定周期、一个可选的初始延迟值以及一个布尔值,以指示该周期应被解释为固定频率还是固定延迟。由于TaskScheduler接口已经定义了以固定频率或固定延迟安排任务的方法,因此在可能的情况下应直接使用这些方法。 PeriodicTrigger实现的价值在于,您可以在依赖Trigger抽象的组件中使用它。例如,允许定期触发器、基于cron的触发器甚至自定义触发器实现互换使用可能是方便的。这样的组件可以利用依赖注入,以便您可以外部配置这些Triggers,从而轻松修改或扩展它们。
TaskScheduler 个实现
与 Spring 的 TaskExecutor 抽象一样,TaskScheduler 的主要好处是将应用程序的调度需求与部署环境解耦。这一抽象级别在部署到应用服务器环境时尤为重要,因为在这样的环境中,应用程序本身不应直接创建线程。对于此类场景,Spring 提供了一个 DefaultManagedTaskScheduler,在 Jakarta EE 环境中将其委托给 JSR-236 ManagedScheduledExecutorService。
当不需要外部线程管理时,一个更简单的替代方案是在应用程序内部使用本地ScheduledExecutorService配置,该配置可以通过Spring的ConcurrentTaskScheduler进行调整。作为便利,Spring还提供了一个ThreadPoolTaskScheduler,它在内部委托给一个ScheduledExecutorService,以提供类似于ThreadPoolTaskExecutor的常见Bean样式配置。这些变体在宽松的应用服务器环境中用于本地嵌入式线程池设置时也非常适用,尤其是在Tomcat和Jetty上。
对调度和异步执行的注解支持
Spring 为任务调度和异步方法执行提供了注解支持。
启用调度注解
要支持 @Scheduled 和 @Async 注释,可以将 @EnableScheduling 和
@EnableAsync 添加到你的某个 @Configuration 类中,如下例所示:
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
您可以为您的应用程序选择合适的注解。例如,如果您只需要支持 @Scheduled,可以省略 @EnableAsync。如需更精细的控制,您可以另外实现 SchedulingConfigurer
接口、AsyncConfigurer 接口或两者都实现。有关详细信息,请参阅
SchedulingConfigurer
和 AsyncConfigurer
的 javadoc。
如果您更喜欢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注解的方法。
默认的处理 @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
}
如果简单的周期调度不够灵活,您可以提供一个 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 开始,支持任何作用域的 bean 上的 确保在运行时没有初始化同一注解类的多个实例,除非您确实希望为每个此类实例安排回调。与此相关的是,确保不要在使用 |
注解 @Async
您可以将 @Async 注解添加到方法上,以便该方法的调用异步进行。换句话说,调用者在调用时会立即返回,而该方法的实际执行会在一个已提交到 Spring TaskExecutor 的任务中进行。在最简单的情况下,您可以将该注解应用于返回 void 的方法,如下例所示:
@Async
void doSomething() {
// this will be run asynchronously
}
与使用@Scheduled注解的方法不同,这些方法可以接受参数,因为它们在运行时是通过调用者以“正常”方式调用的,而不是由容器管理的计划任务调用的。例如,以下代码是@Async注解的合法应用:
@Async
void doSomething(String s) {
// this will be run asynchronously
}
即使返回值的方法也可以异步调用。但是,这样的方法必须具有Future类型的返回值。这仍然提供了异步执行的好处,这样调用者可以在调用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,然后该 Bean 会在目标对象上调用 @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等价物用于@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"可以是Spring容器中任何Executor bean的名称,或者是与任何Executor相关联的限定符的名称(例如,如<qualifier>元素或Spring的@Qualifier注解所指定的)。
使用 @Async 进行异常管理
当一个 @Async 方法具有 Future 类型的返回值时,很容易管理在方法执行期间抛出的异常,因为此异常是在对 get 的 Future 结果进行调用时抛出的。但是,使用 void 返回类型时,异常未被捕获,无法传递。您可以提供一个 AsyncUncaughtExceptionHandler 来处理此类异常。下面的示例显示了如何操作:
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
默认情况下,异常仅被记录。您可以使用 AsyncUncaughtExceptionHandler 或 AsyncConfigurer XML 元素定义自定义 <task:annotation-driven/>。
命名空间 task
从版本3.0开始,Spring包含了一个用于配置TaskExecutor和TaskScheduler实例的XML命名空间。它还提供了一种方便的方式来配置使用触发器安排的任务。
'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的文档。
其主要思想是,当提交一个任务时,如果当前活动线程数少于核心大小,执行器会首先使用空闲线程。
如果已达到核心大小,只要队列的容量尚未达到,任务就会被添加到队列中。
只有在队列的容量已满时,执行器才会创建超出核心大小的新线程。如果最大大小也已达到,那么执行器将拒绝该任务。
默认情况下,队列是无界的,但这很少是理想的配置,因为当所有线程都在忙碌时,向该队列添加足够多的任务可能导致 OutOfMemoryErrors。此外,如果队列是无界的,那么最大大小将完全不起作用。由于执行器总是先尝试队列,然后再创建超过核心大小的新线程,因此为了使线程池超出核心大小,队列必须具有有限的容量(这就是为什么使用无界队列时,固定大小的池才是唯一合理的案例)。
考虑上面提到的情况,当一个任务被拒绝时。默认情况下,当一个任务被拒绝时,线程池执行器会抛出一个TaskRejectedException。然而,拒绝策略实际上是可配置的。当使用默认的拒绝策略时,即AbortPolicy实现时,会抛出异常。对于在高负载下某些任务可以被跳过的应用程序,您可以配置DiscardPolicy或DiscardOldestPolicy。对于需要在高负载下限制提交任务的应用程序,另一个有效的选择是CallerRunsPolicy。该策略不会抛出异常或丢弃任务,而是强制调用submit方法的线程自己运行该任务。其思路是,这样的调用者在运行该任务时很忙,无法立即提交其他任务。因此,它提供了一种简单的方法来限制传入的负载,同时保持线程池和队列的限制。通常,这允许执行器“赶上”它正在处理的任务,从而释放队列、线程池或两者的部分容量。您可以从executor元素上可用的rejection-policy属性的枚举值中选择任何一种选项。
以下示例显示了一个带有多个属性以指定各种行为的 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应用上下文中配置计划任务。这种方法与其他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任务,您可以指定一个“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 表达式,例如 * * * * * *,由六个以空格分隔的时间和日期字段组成,每个字段都有自己的有效值范围:
┌───────────── 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)。
-
-
日期字段可以是
nW,表示最接近月份第n日的星期几。 如果n是星期六,这将返回之前的星期五。 如果n是星期日,这将返回之后的星期一,当n是1且是星期六时也会发生这种情况(即:1W表示月份的第一个星期几)。 -
如果日期字段为
LW,则表示 该月的最后一个工作日。 -
星期几字段可以是
d#n(或DDD#n),表示 该月的第n天星期d(或DDD)。
这里是一些示例:
| cron 表达式 | 含义 |
|---|---|
|
每小时的开始 |
|
每十秒 |
|
8、9 和 10 点钟每天 |
|
早上6点和下午7点每天 |
|
8:00,8:30,9:00,9:30,10:00 和 10:30 每天 |
|
每小时工作日的九点到五点 |
|
每个圣诞节的午夜 |
|
当月最后一天的午夜 |
|
本月最后一天前的第三天午夜 |
|
每月最后一个星期五午夜 |
|
每月最后一个星期四的午夜 |
|
每月第一个星期一的午夜 |
|
当月最后一个工作日的午夜 |
|
每月的第二个星期五午夜 |
|
每月第一个星期一的午夜 |
使用 Quartz 调度器
Quartz 使用 Trigger、Job 和 JobDetail 对象来实现各种任务的调度。
有关 Quartz 的基本概念,请参见
Quartz 官方网站。为了方便起见,Spring 提供了一些类,以简化在基于 Spring 的应用程序中使用 Quartz。
使用 JobDetailFactoryBean
Quartz JobDetail 对象包含运行作业所需的所有信息。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 的 bean 属性,并且 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 属性,您可以分别修改作业的名称和组。默认情况下,作业的名称与 JobDetailFactoryBean 的 bean 名称匹配(在上面的示例中为 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 标志设置为 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 来连接作业
我们已经创建了作业详细信息和作业。我们还审查了方便的bean,它允许您在特定对象上调用方法。当然,我们仍然需要安排作业本身。这是通过使用触发器和一个SchedulerFactoryBean来完成的。Quartz中提供了几个触发器,Spring为Quartz提供了两个带有便捷默认值的FactoryBean实现: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 设置相互作用;
因此不建议在两个级别上同时指定值。例如,如果您希望依赖 Spring 提供的 DataSource,
则不要设置 "org.quartz.jobStore.class" 属性,
或指定一个 org.springframework.scheduling.quartz.LocalDataSourceJobStore 变体,
它是对标准 org.quartz.impl.jdbcjobstore.JobStoreTX 的完整替代。 |