Spring提供了支持时序调度(译者注:Scheduling,下同)的整合类.现在, Spring支持内置于1.3版本以来的JDK中的Timer和Quartz Scheduler(http://www.quartzscheduler.org)。 两个时序调度器通过FactoryBean建立,保持着可选的对Timers或者Triggers的引用。更进一步的, 对于Quartz Scheduler和Timer两者存在一个方便的类允许你调用目标对象(类似于通常的MethodInvokingFactoryBeans)上的某个方法
Quartz使用Triggers,Jobs和JobDetail来实现时序调度中的各种工作。 为了了解Quartz背后的种种基本观点,你可以移步至http://www.opensymphony.com/quartz。 为了方便的使用,Spring提供了几个类在基于Spring的应用中来简化对Quartz的使用。
JobDetail 对象包括了运行一个job所需要的所有信息。 于是Spring提供了一个所谓的JobDetailBean使得JobDetail拥有了一个真实的,有意义的默认值。让我们来看个例子:
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass"> <value>example.ExampleJob</value> </property> <property name="jobDataAsMap"> <map> <entry key="timeout"><value>5</value></entry> </map> </property> </bean>
Job detail bean拥有所有运行job(ExampleJob)的必要信息。通过job的data map来制定timeout。 Job的data map可以通过JobExecutionContext(在运行时刻传递给你)来得到, 但是JobDetailBean也把从job的data map中得到的属性映射到实际job中的属性中去。 所以,如果ExampleJob中包含一个名为timeout的属性,JobDetailBean将自动为它赋值:
package example; public class ExampleJob extends QuartzJobBean { private int timeout; /** * Setter called after the ExampleJob is instantiated * with the value from the JobDetailBean (5) */ public void setTimeout(int timeout) { this.timeout = timeout; } protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException { // do the actual work } }
所有Job detail bean中的一些其他的设定对你来说也是可以同样设置的.
注意:使用name和group属性,你可以修改job在哪一个组下运行和使用什么名称。 默认情况下,job的名称等于job detai bean的名称(在上面的例子中为exampleJob)。
通常情况下,你只需要调用特定对象上的一个方法。你可以使用MethodInvokingJobDetailFactoryBean准确的做到这一点:
<bean id="methodInvokingJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject"><ref bean="exampleBusinessObject"/></property> <property name="targetMethod"><value>doIt</value></property> </bean>
上面例子将导致exampleBusinessObject中的doIt方法被调用(如下):
public class BusinessObject { // properties and collaborators public void doIt() { // do the actual work } }
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>
使用MethodInvokingJobDetailFactoryBean你不需要创建只有一行代码且只调用一个方法的job, 你只需要创建真实的业务对象来包装具体的细节的对象。
默认情况下,Quartz Jobs是无状态的,可能导致jobs之间互相的影响。如果你为相同的JobDetail指定两个触发器, 很可能当第一个job完成之前,第二个job就开始了。如果JobDetail对象实现了Stateful接口,就不会发生这样的事情。 第二个job将不会在第一个job完成之前开始。为了使得jobs不并发运行,设置MethodInvokingJobDetailFactoryBean中的concurrent标记为false。
<bean id="methodInvokingJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject"><ref bean="exampleBusinessObject"/></property> <property name="targetMethod"><value>doIt</value></property> </bean>
注意:默认情况下,jobs在并行的方式下运行。
我们已经创建了job details,jobs。我们回顾了允许你调用特定对象上某一个方法的便捷的bean。 当然我们仍需要调度这些jobs。这需要使用triggers和SchedulerFactoryBean来完成。 Quartz自带一些可供使用的triggers。Spring提供两个子类triggers,分别为CronTriggerBean和SimpleTriggerBean。
Triggers也需要被调度。Spring提供SchedulerFactoryBean来暴露一些属性来设置triggers。SchedulerFactoryBean负责调度那些实际的triggers。
两个例子:
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail"> <!-- see the example of method invoking job above --> <ref bean="methodInvokingJobDetail"/> </property> <property name="startDelay"> <!-- 10 seconds --> <value>10000</value> </property> <property name="repeatInterval"> <!-- repeat every 50 seconds --> <value>50000</value> </property> </bean> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail"> <ref bean="exampleJob"/> </property> <property name="cronExpression"> <!-- run every morning at 6 am --> <value>0 6 * * 1</value> </property> </bean>
现在我们创建了两个triggers,其中一个开始延迟10秒以后每50秒运行一次,另一个每天早上6点钟运行。 我们需要创建一个SchedulerFactoryBean来最终实现上述的一切:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref local="cronTrigger"/> <ref local="simpleTrigger"/> </list> </property> </bean>
更多的一些属性你可以通过SchedulerFactoryBean来设置,例如job details使用的Calendars,用来订制Quartz的一些属性以及其它。 你可以看相应的JavaDOC(http://www.springframework.org/docs/api/org/springframework/scheduling/quartz/SchedulerFactoryBean.html)来了解进一步的信息。
另外一个调度任务的途径是使用JDK Timer对象。更多的关于Timers的信息可以在这里http://java.sun.com/docs/books/tutorial/essential/threads/timer.html找到。 上面讨论的概念仍可以应用于Timer的支持。你可以创建定制的timer或者调用某些方法的timer。 包装timers的工作由TimerFactoryBean完成。
你可以使用TimerTask创建定制的timer tasks,类似于Quartz中的jobs:
public class CheckEmailAddresses extends TimerTask { private List emailAddresses; public void setEmailAddresses(List emailAddresses) { this.emailAddresses = emailAddresses; } public void run() { // iterate over all email addresses and archive them } }
包装它是简单的:
<bean id="checkEmail" class="examples.CheckEmailAddress"> <property name="emailAddresses"> <list> <value>test@springframework.org</value> <value>foo@bar.com</value> <value>john@doe.net</value> </list> </property> </bean> <bean id="scheduledTask" class="org.springframework.scheduling.timer.ScheduledTimerTask"> <!-- wait 10 seconds before starting repeated execution --> <property name="delay"> <value>10000</value> </property> <!-- run every 50 seconds --> <property name="period"> <value>50000</value> </property> <property name="timerTask"> <ref local="checkEmail"/> </property> </bean>
就像Quartz的支持一样,Timer的支持也有一个组件允许你周期性地调用一个方法:
<bean id="methodInvokingTask" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean"> <property name="targetObject"><ref bean="exampleBusinessObject"/></property> <property name="targetMethod"><value>doIt</value></property> </bean>
上面的例子将会导致exampleBusinessObject上的doIt方法被调用(如下):
public class BusinessObject { // properties and collaborators public void doIt() { // do the actual work } }
把上面例子中提到ScheduledTimerTask的引用改为methodInvokingTask将导致该task被执行。
TimerFactoryBean类似于QuartzSchedulerFactoryBean,都是服务于一个目的:建立起实际的时序调度。 TimerFactoryBean建立一个实际的Timer来调度它引用的那些tasks。你可以指定它是否使用一个守护线程。
<bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean"> <property name="scheduledTimerTasks"> <list> <!-- see the example above --> <ref local="scheduledTask"/> </list> </property> </bean>