概念
Spring 3.0引入了一个TaskScheduler,它有多种方法来调度未来某个时候运行的任务。
Scheduler
- 名为“schedule”的方法,它只接受可运行的日期。这将导致任务在指定时间后运行一次。
- 所有其他方法都能够安排任务重复运行。固定速率和固定延迟方法用于简单的、周期性的执行,但是使用 Trigger 的方法要灵活得多。
Trigger
触发器的基本思想是,执行时间可以根据过去的执行结果甚至任意条件来确定。如果这些决定确实考虑了前面执行的结果,那么该信息在TriggerContext中是可用的。
public interface Trigger { Date nextExecutionTime(TriggerContext triggerContext); }
TriggerContext是最重要的部分。它封装了所有相关的数据,如果有必要,将来还可以进行扩展。TriggerContext是一个接口(默认情况下使用SimpleTriggerContext实现)。
Trigger 实现
Spring提供了触发器接口的两个实现。最有趣的是CronTrigger。它支持基于cron表达式的任务调度。
另一个开箱即用的实现是一个周期性Trigger ,它接受一个固定的周期、一个可选的初始延迟值,以及一个布尔值,用来指示周期应该解释为固定速率还是固定延迟。由于TaskScheduler接口已经定义了以固定速率或固定延迟调度任务的方法,因此应该尽可能直接使用这些方法。PeriodicTrigger实现的价值在于它可以在依赖于触发器抽象的组件中使用。例如,允许周期性Trigger 、基于cro的Trigger ,甚至自定义Trigger 实现可以互换使用,这可能很方便。这样的组件可以利用依赖注入,这样就可以在外部配置这样的Trigger 。
scheduled-tasks元素
<task:scheduled-tasks scheduler="myScheduler">
<task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/><!--ref
指向容器管理的对象,method是要执行的方法名-->
</task:scheduled-tasks>
<task:scheduler id="myScheduler" pool-size="10"/>
<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"/>
Quartz Scheduler
Quartz使用触发器、作业和作业细节对象来实现各种作业的调度。有关Quartz背后的基本概念,请参阅http://quartz-scheduling er.org。为了方便起见,Spring提供了两个类,它们简化了基于Spring的应用程序中Quartz的使用。
使用JobDetailBean
JobDetail对象包含运行作业所需的所有信息。Spring框架提供了JobDetailBean,它使JobDetail更接近于具有合理默认值的实际JavaBean。
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.spring5.scheduler.ExampleJob"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5"/>
</map>
</property>
</bean>
job detail bean 具有运行作业(ExampleJob)所需的所有信息。timeout在job data map指定。job data map可以通过JobExecutionContext(在执行时传递给您)获得,但是JobDetailBean还将 job data map 中的属性映射到实际job的属性。因此,在本例中,如果ExampleJob包含一个名为timeout的属性,JobDetailBean将自动应用它。
package com.spring5.scheduler;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
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 {
System.out.println("使用JobDetailFactoryBean: do something");
}
}
使用MethodInvokingJobDetailFactoryBean
<!-- Using the MethodInvokingJobDetailFactoryBean-->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject"/>
<property name="targetMethod" value="doIt"/>
</bean>
<bean id="exampleBusinessObject" class="com.spring5.scheduler.ExampleBusinessObject"/>
使用上面的配置将会导致ExampleBusinessObject .doIt()方法被调用。
package com.spring5.scheduler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ExampleBusinessObject {
// properties and collaborators
public void doIt() {
// do the actual work
System.out.println("ExampleBusinessObject:do the actual work");
}
public static void main(String[] args) {
System.out.println("ExampleBusinessObject:start");
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring.xml"});
((ClassPathXmlApplicationContext) context).start();
}
}
使用MethodInvokingJobDetailFactoryBean,不再需要创建只调用一个方法的一行作业,只需要创建实际的业务对象并连接到它。
默认情况下,Quartz作业是无状态的,导致作业相互干扰的可能性。如果为相同的JobDetail指定两个触发器,那么可能在第一个作业完成之前,第二个作业就会开始。如果JobDetail类实现有状态接口,则不会发生这种情况。在第一项工作完成之前,第二项工作不会开始。要使方法调用jobdetailfactorybean产生的作业非并发,请将并发标志设置为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" /><!--并发执行false-->
</bean>
6.3 作业调度
尽管我们能使用MethodInvokingJobDetailFactoryBean调用特定对象上的方法,但是我们还是需要调度作业 。这需要使用触发器和scheduler erfactorybean完成。Quartz 提供了多种触发器
Spring提供了两种Quartz 工厂对象:
- CronTriggerFactoryBean
- SimpleTriggerFactoryBean
两种触发器的示例
<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="1000"/>
<!-- repeat every 50 seconds -->
<property name="repeatInterval" value="5000"/>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="exampleJob"/>
<property name="cronExpression" value="10 * * * * ?"/>
</bean>
关于SchedulerFactoryBean的等多属性设置 ,参考SchedulerFactoryBean javadoc