徒手翻譯spring framework 4.2.3官方文檔的第33章,若有翻譯不當之處請指正。
定時任務的執行和調度
1. 簡介
spring framework 為任務的異步執行和調度提供了抽象接口分別是:TaskExecutor 和 TaskScheduler,spring 對這些接口的進一步實現支持線程池或者將該功能交給應用服務器的commonJ。最后,在java5、java6和java EE環境下這些公共接口的實現的使用方法是不同的。
spring為了支持任務的調度,利用Timer(始于jdk1.3)和Quartz調度器實現了一些集成類,這兩種調度器的建立都是利用FactoryBean,可分別參考Timer或者Trigger的示例。此外,還有一個Quartz調度器和Timer都可用的類,這個類很方便,它允許你調用現存對象的一個方法(類似于常見的 MethodInvokingFactoryBean)。
2. Spring TaskExecutor
spring 2.0 引入一個抽象類來處理executors,Executors是java 5 為線程池命的名。executor的命名不一定要實現作一個線程池,但實際上就是一個池子,一個executor可能是單線程的或者同步的,spring的抽象類隱藏java SE 1.4、java SE 1.5和java EE中的實現細節。
Spring 的 TaskExecutor
和 java.util.concurrent.Executor
接口是一樣的,實際上它存在的主要原因是和java 5在線程池的使用上的不同,這個接口僅有一個方法execute(Runnable task),這個任務的執行是基于線程池的原理和配置的。
起初,TaskExcecutor
是為其他的需要線程池支持的spring組件設計的,這些組件如:ApplicationEventMulticaster
、JMS中的 AbstractMessageListenerContainer
和Quartz集成時用到的線程池。可是,如果你的beans需要線程池,也可以根據自己的需要來使用這個抽象類。
TaskExecutor 的實現類
在spring發布版本中,有很多TaskExecutor內部實現類,大多數情況下,你不需要自己實現。
-
SimpleAsyncTaskExecutor
這是一個實現類,它不會多次使用任何線程,而是對每次調用啟動一個新的線程。但是它確實支持并發限制,當線程數超過限制時,阻止任何調用,直到有一個空位。如果想要一個真實線程池,參看下面討論的SimpleThreadPoolTaskExecutor
和ThreadPoolTaskExecutor
。
SyncTaskExecutor
這是一個實現類,它不會異步執行方法,而是每個任務調用都會占用正在運行的線程。主要是應用在沒必要使用多線程的情景下,如簡單的測試方法。ConcurrentTaskExecutor
這是一個實現類,它是java.util.concurrent.Executor
的一個適配器,這個和ThreadPoolTaskExecutor
是二選一的,它可以將Executor的配置參數做成properties bean。需要用這個類的地方很少,但是如果你覺得ThreadPoolTaskExecutor
不夠靈活,ConcurrentTaskExecutor
是一個選擇。SimpleThreadPoolTaskExecutor
這個實現類實際上是Quartz中SampleThreadPool
的一個子類,它監聽了spring生命周期中的所有回調。它的典型用法是,當你有一個線程池需要Quartz和non-Quartz的組件共享時,可使用該類。ThreadPoolTaskExecutor
這是最常用的一個實現,它使得java.util.concurrent.ThreadPoolExecutor
的參數配置可以在properties bean中,并且把它封裝在了TaskExecutor
,如果你需要適配不同種類的java.util.concurrent.Executor
,推薦你使用ConcurrentTaskExecutor
。WorkManagerTaskExecutor
這個類實現了CommonJ中的WorkManager
接口,可以很方便的在spring的容器中啟動一個CommonJ中的WorkManager
,和SimpleThreadPoolTaskExecutor
類很相似,這個類實現了WorkManager
接口,因此也可以直接用作 WorkManager。
TaskExecutor示例
spring的TaskExecutor
可以用一些簡單的JavaBean實現,我們舉個用 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 properties配置:
<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>
3. Spring TaskScheduler
除了TaskExecutor的抽象之外,spring 3.0 還引入了 TaskScheduler,其中包含大量的方法,使得可以指定在將來的某個時候運行任務。
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Date startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}
其中,最簡單的方法是一個叫做schedule
的方法,僅需要一個Ruannable任務和一個時間,它會使任務在到達指定的時間之后運行一次。所有其它的方法都是可以使任務重復執行的,fixed-rate和fixed-delay方法是一個簡化的操作,任務周期性的執行,但是傳入參數Trigger的方法是更靈活的。
Trigger接口
Trigger接口的靈感本質上是來自于JSR-236,到spring 3.0 實現為止,還沒有官方的實現。Trigger的基本想法是控制任務的執行時間,根據過去任務的執行結果或者是任意條件執行任務。如果這些決定確實要考慮之前的執行結果,在TriggerContext中的信息是有用的。Trigger接口本身是很簡單的:
public interface Trigger {
Date nextExecutionTime(TriggerContext triggerContext);
}
正如你所看到的,TriggerContext
是很重要的一部分,它封裝了所有的相關數據,并且如果在將來有必要的話是可以擴展的。TriggerContext
是一個接口(SimpleTriggerContext
是默認使用的實現類),下面代碼中你可以看到TriggerContext
接口中哪些方法是可以使用的。
public interface TriggerContext {
Date lastScheduledExecutionTime();
Date lastActualExecutionTime();
Date lastCompletionTime();
}
Trigger的實現類
Spring為Trigger接口提供了兩個實現類,你可能最感興趣的一個是CronTrigger
,它能夠按照cron表達式來調度任務。例如,下面的任務執行時間規則,在工作日(周一至周五)的9點到17點期間的每個小時整點過15分鐘執行一次。
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
另一個實現類是拆箱可用的 PeriodicTrigger
,它接受一個固定的周期,一個可選的初始的延遲時間,還有一個Boolean值指出將固定的周期解釋作fixed-rate還是fixed-delay。因為在TaskScheduler接口中已經利用fixed-rate和fixed-delay定義了調度任務的幾個方法,這些方法在任何時候都能直接使用。PeriodicTrigger
實現類的價值在于,它可以用在依賴Trigger的組件內部使用。例如,它可以很方便的允許周期性觸發器或者基于cron表達式的觸發器,甚至是自定義實現的觸發器去執行任務,這樣的一個組件可以利用依賴注入的特性,以便于這些觸發器可以外部配置,因此更容易修改和擴展。
TaskScheduler的實現類
正如TaskExecutor
的抽象類一樣,TaskScheduler的最主要的意義在于,依賴調度行為的代碼不再需要關注特定調度方法的實現。在一些不應該應用本身創建線程的應用服務器中,這些調度方法能夠很靈活的實現特定的任務調度功能。在這樣的情況下,spring提供了一個類 TimerManagerTaskScheduler
,它是CommonJ中類 TimerManager
的一個實例,典型的配置是JNDI-lookup。
當在外部的線程管理不是必須的時候,一個更簡單的選擇 ThreadPoolTaskScheduler
可以被使用。在內部,它代表 ScheduledExecutorService
的一個實例。ThreadPoolTaskScheduler
實際上也實現了TaskExecutor
的接口,所以一個單獨的實例會被盡快的異步執行,按照預定的時間執行,可能會再次執行或者執行完成。
4. 任務的調度和異步執行的注解支持
spring 對任務調度和異步方法調用都提供了注解支持。
啟動任務調度注解
為了啟用注解@Scheduled
和@Async
,需要再你的一個@Configuration
類上添加注解@EnableScheduling
和@EnableAsync
。
@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}
你可以任意為你的應用選擇相關的注解使用,例如,如果你僅需要支持@Scheduled
,就可以省略@EnableAsync
。如果你需要更加細粒度的控制任務,你可以額外實現SchedulingConfigurer
或者AsyncConfigurer
接口。全部細節詳見javadocs。
如果你更喜歡使用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配置信息,其中處理任務的executor引用配置,相當于
@Async
注解;管理任務的Scheduler相當于@Scheduled
注解。
@Scheduled注解
這個注解可以添加在一個方法上,注解里可以配置一些觸發器的參數。例如下面的一個方法,每次被調用都有5秒鐘的延遲(即下一次執行在上一次執行完成之后,延遲5秒鐘再執行),這個周期的衡量是每次調用完成的時間。
@Scheduled(fixedDelay=5000)
public void doSomething() {
// something that should execute periodically
}
如果你急需一個固定頻率的執行任務方法,只需要簡單的改變注解中屬性名稱,下面的方法就是每5秒鐘執行一次,這個周期的衡量是每次成功調用開始的時間。
@Scheduled(fixedRate=5000)
public void doSomething() {
// something that should execute periodically
}
對于一些固定時延和固定頻率執行的任務,指定一個初始延遲時間,表明這個方法的第一次執行需要等待這個毫秒數。例子如下:
@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
// something that should execute periodically
}
如果簡單的周期性調度不能滿足你的任務的表達,可以使用提供的cron表達式。例如下面的例子,這個僅在工作日時間執行。
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should execute on weekdays only
}
- 注意,此外你還可以使用zone屬性來指定cron表達式中的時區問題。
- 注意,這些被調度的任務方法不能有任何返回值,都是void方法,如果這個方法需要和應用上下文中其他對象有交互,這將需要通過依賴注入來實現。
- 注意,確定你在運行時將注解了
@Scheduled
的類不要被初始化為一個實例,除非你確實想調度每個實例的回調函數。與這個相關,確保不要在使用@Scheduled
注解的類上使用@Configurable
,要表現為spring 容器的一個普通 bean,否則這個類會被初始化兩次,一次是通過容器初始化的,一次是通過@Configurable
切面初始化的,這樣導致的結果就是,每個注解@Scheduled
的方法被執行兩次。
@Async注解
注解@Async
在方法上使用,這樣,這個方法就可以被異步調用。換句話說就是,調用者將及時的得到上一次調用的響應,方法的實際執行將在一個任務中,這個任務已經被提交到Spring TaskExecutor中,最簡單的例子是,注解被應用在一個void返回值的方法。
@Async
void doSomething() {
// this will be executed asynchronously
}
和加了@scheduled
注解的方法不同,這些方法希望有一些參數,因為它們是調用者在運行時,以一種普通的方式調用的,而不是一個被容器管理的調度任務中調用的,例如,下面是@Async
使用合法的一個方法。
@Async
void doSomething(String s) {
// this will be executed asynchronously
}
甚至,帶有返回值的方法也能異步調用,但是這些方法的返回值類型必須是Future
,這給異步執行提供了便利,調用者可以在調用Future
中get( )
方法之前調用其他任務。
@Async
Future<String> returnSomething(int i) {
// this will be executed asynchronously
}
@Async
注解不能和 spring bean 生命周期的回調函數相關注解一起使用,例如@PostConstruct
。如果想異步初始化 spring bean 中方法,必須在一個分離的初始化 spring bean 中調用帶有@Async
注解的方法。
public class SampleBeanImpl implements SampleBean {
@Async
void doSomething() {
// ...
}
}
public class SampleBeanInititalizer {
private final SampleBean bean;
public SampleBeanInitializer(SampleBean bean) {
this.bean = bean;
}
@PostConstruct
public void initialize() {
bean.doSomething();
}
}
注意,在同一個類中的方法調用,添加
@Async
注解實現異步調用是無效的。
@Async中指定執行時的Executor
添加了@Async
注解的方法默認情況下使用的Executor是上面提到的一種方式,即通過Annotation-driven
元素提供的Executor。然而,在@Async
注解的一個屬性中可以指定這個Executor,而不是使用上面默認提供的Executor。例子如下:
@Async("otherExecutor")
void doSomething(String s) {
// this will be executed asynchronously by "otherExecutor"
}
在這種情況下,otherExecutor
可能是在spring容器中任何Executor的名字,也可能是和任何Executor有聯系的qualifier的名字,如指定<qualifier>
元素或者spring的@Qualifier
注解。
@Async的異常管理
當一個注有@Async
注解的方法有一個Future類型的返回值時,異常是很容易管理的,即當這個方法執行過程中拋出異常時,這個異常同樣會在調用Future中get方法時拋出。然而一個void返回值類型的函數呢,這個異常將不會被捕獲也不會被傳遞,就這種情況下,AsyncUncaughtExceptionHandler
是用來處理這種異常的。
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
默認情況下,這種異常僅僅被簡單記入日志,還可以通過接口
AsyncConfigurer
或者XML標簽task:annotation-driven
自定義一個AsyncUncaughtExceptionHandler
。
5. Task的命名空間
從spring 3.0 開始,為了配置TaskExecutor
和TaskScheduler
實例新添加了XML命名空間,這也為使用觸發器配置任務調度提供了方便。
Scheduler元素
下面的元素將要使用指定的線程池大小創建一個TreadPoolTaskScheduler
實例:
<task:scheduler id="scheduler" pool-size="10"/>
上面提供的id
屬性的值將會在線程池中被用作線程名字的前綴,Scheduler
元素是相對來說比較簡單的。如果你不提供pool-size
屬性的值,默認的線程池大小就是1,Scheduler
元素沒有其它的配置屬性了。
Executor元素
下面將創建一個ThreadPoolTaskExecutor
的實例:
<task:executor id="executor" pool-size="10"/>
和上面的scheduler
元素一樣,id
屬性的值將會被用作在線程池中線程名稱的前綴,就線程池大小而言,executor
元素比scheduler
元素支持更多的配置項。首先,ThreadPoolTaskExecutor
的線程池本身是有很多配置的,不僅僅是一個線程池大小的配置,一個executor
的線程池的核心值和最大值有很多不同的值,如果是簡單的一個值,executor
將有一個固定大小的線程池(核心和最大值是相同的),然而實際上,executor
元素的pool-size
屬性已接受這樣的值min-max
。下面是個例子:
<task:executor id= "executorWithPoolSizeRange"
pool-size="5-25"
queue-capacity="100" />
正如你在配置中看到的一樣,還提供了一個屬性queue-capacity
。線程池的配置應該考慮一下executor的隊列容量,對于線程池大小和隊列容量之間關系的詳細描述,參見TreadPoolExecutor
的文檔。主要的意思是這樣的,當我們提交一個任務的時候,首先,如果當前活動的線程數量小于核心大小,則executor使用一個空閑的線程來執行這個任務;如果當前線程數量已達到核心大小,任務將會被添加到一個隊列中,直到達到隊列的最大容量;如果隊列的容量也達到了,executor將會在核心大小之外創建新的線程來執行這個任務;如果線程數量也達到了線程池的最大值,executor將拒絕執行這個任務。
默認情況下,隊列的大小是無限的(Integer.MAX_VALUE
),但是這個值很少人有人設置,這樣如果任務添加過多,而線程池的線程又是一直工作的,就會導致內存溢出的錯誤(OutOfMemoryErrors
)。進一步來說,如果隊列的大小是無限的,設置的線程池的最大值就沒有作用了。因為當線程池大小超過核心大小時,創建一個新的線程之前,executor總是試圖將任務添加到隊列中,所以隊列必須設置一個有限的容量,來接收超出線程池核心大小的任務。使用無限隊列大小的唯一明智的情況是,在使用固定大小線程池的時候。
現在我們需要回顧一下keep-alive
設置的效果,這是配置了線程池大小之后,有一個需要考慮的因素。首先,讓我們考慮一下這種情況,如上所述,一個任務可能會被拒絕。默認情況下,當一個任務被拒絕的時候,executor會拋出一個異常(TaskRejectedException
),實際上拒絕任務的策略是可配置的,拋出異常的那個默認的拒絕任務的策略是AbortPolicy
實現類。在一些應用中,重負載下的一些任務是可以跳過的,這時可配置的拒絕策略是DiscardPolicy
或者DiscardOldestPolicy
。另一個可選的拒絕任務的策略是CallerRunsPolicy
,它在某些應用(在重負載下的需要節流已經提交的任務)中表現良好。與拋出異常和丟棄任務相比,這個策略是簡單的強制正在調用提交方法的線程運行當前這個任務。主要的思想是讓任務的調用者是繁忙的,而不能立即提交其他的任務。因此這樣就提供了一種簡單的控制任務提交的方式,在達到線程池和隊列的限制時。這樣executor就會在完成一些任務后,在隊列和線程池中騰出一些空間。上面列舉的這些選項都是可以作為executor元素中的rejection-policy
屬性的值。下面是個例子:
<task:executor id= "executorWithCallerRunsPolicy"
pool-size="5-25"
queue-capacity="100"
rejection-policy="CALLER_RUNS" />
最后,keep-alive
設置決定了閑置的線程在被終止前保留的時間(以秒為單位),意思是,如果在當前線程池中有超過核心線程數的線程,在等待這個時間沒有處理一個任務之后,超出的線程將會終止。這個時間設置為0,導致超出的線程在執行完任務之后就會被立即結束,不會處理任務隊列中的后續工作。下面是一個例子:
<task:executor id= "executorWithKeepAlive"
pool-size="5-25"
keep-alive="120" />
scheduled-tasks元素
spring的task的命名空間最強大的一個特性是,支持在spring的應用上下文中配置任務和調度。下面是一種方法和在spring中其他的method-invokers
一樣,例如在JMS的命名空間中配置Message-driven
的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"/>
正如你所看到的,scheduler是被其他外部元素引用的,每一單獨的任務包括觸發器的相關配置。在前面的例子中,是一個有固定延遲的周期性觸發器,延遲的指定數字的毫秒數,也就是每個任務執行完成之后,要等待這個時間值之后再啟動下一次任務。另一個選項是fixed-rate
,它的意思是任務多長時間執行一次,不管前面的任務是否執行完成。此外,fixed-delay
和fixed-rate
都有一個initial-delay
參數,可以指定任務第一次執行時延遲的時間,需要更多的控制的話,可以使用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" />
6. Quartz Scheduler的用法
Quartz使用Trigger
、Job
和JobDetail
對象來實現各種任務的調度。對于Quartz背后的基本概念,可以查看http://quartz-scheduler.org。為了方便,spring提供了兩個類,簡化在基于spring的應用中使用Quartz的過程。
JobDetailFactoryBean的用法
Quartz的JobDetail對象包括運行一個Job所需的全部信息。spring提供了一個JobDetailFactoryBean
,實現xml配置的目的。下面是一個例子:
<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>
Job detail的配置包含運行一個任務(ExampleJob)所需的全部信息。timeout在任務的數據映射中被指定,任務的數據映射是通過JobExecutionContext
獲取的,而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 (5)
*/
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>
上面的例子將會使名為 exampleBusinessObject
的 bean 中的doIt
方法被調用。bean 的定義如下:
public class ExampleBusinessObject {
// properties and collaborators
public void doIt() {
// do the actual work
}
}
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
使用MethodInvokingJobDetailFactoryBean
的時候,不需要創建一行有關任務相關的代碼,只需要調用一個方法,你只需要創建一個實際的業務對象,然后和detail
對象連接起來就行了。
默認情況下,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>
注意,默認情況下任務是并行的模式。
使用Triggers和SchedulerFactoryBean連接任務
我們已經創建了JobDetail
和Job
,我們也回顧一下上面方便我們直接調用一個對象的指定方法的bean,當然,仍然需要我們自己調度任務,這需要使用Trigger
和一個SchedulerFactoryBean
。在Quartz中的Trigger
是可以用,在spring中提供了Quartz 中 FactoryBean
的兩個實現類:CronTriggerFactoryBean
和 SimpleTriggerFactoryBean
。
Trigger是調度需要的,spring提供了一個SchedulerFactoryBean
,Trigger作為它的一個屬性。SchedulerFactoryBean
根據這些Trigger實際調度這些任務。下面是一對例子:
<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>
現在我們啟動了兩個Trigger,一個是每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
中有很多屬性可以設置,例如在JobDetail
中的日歷,支持Quartz中自定義屬性等,詳細信息查閱SchedulerFactoryBean
的javadoc文檔。
最完整的一個主題的翻譯,下一篇預告:分布式定時任務的完美實施