徒手翻译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文档。
最完整的一个主题的翻译,下一篇预告:分布式定时任务的完美实施
网友评论