Quartz框架是一个极其成功的开源任务调度框架,目前很多公司在实现任务调度上都直接使用或在借鉴的基础上实现了自己的任务调度框架,Quartz使用Trigger,Job和JobDetail对象实现调度各种各样的任务,为了更加便捷地在基于Spring应用中使用该框架,Spring提供了大量的类来简化Quartz的使用步骤;
本篇文章将通过介绍Spring中如何对应Quartz的Trigger,Job,JobDetail方式对整合进行介绍;
一、Quartz的JobDetail对象包含了一个任务运行时需要的各种必要信息,在Spring中,提供了一个JobDetailFactoryBean用来通过XML配置的方式配置Bean属性,首先来看一个例子:
<!-- Spring配置Job --> <bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <!-- 设置任务类 --> <property name="jobClass" value="org.spring.scheduling.quartz.job.ExampleJob" /> <!-- 创建JobDetail的描述 --> <property name="description" value="job1" /> <property name="jobDataAsMap"> <map> <entry key="timeout" value="5" /> <entry key="count" value="10"/> </map> </property> </bean>
可以发现在Spring中通过配置JobDetailFactoryBean来对应Quartz框架中的Job和JobDetail,需要注意的是timeout和count指定在JobDataMap中,该JobDataMap对象可以在运行时通过JobExecutionContext传递给你,对于JobDetail对象而言,它也可以从该JobDataMap中获取它的属性值,如上,我们在jobDataAsMap属性中,配置了timeout和count,那么如果我们的ExampleJob中也有对应属性的话,Spring也会自动赋值,ExampleJob代码如下所示:
package org.spring.scheduling.quartz.job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; public class ExampleJob extends QuartzJobBean { private int timeout; public ExampleJob() { // TODO Auto-generated constructor stub } @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { System.out.println(context.getJobDetail().getJobDataMap().get("timeout")); System.out.println(context.getJobDetail().getDescription() + "任务在" + context.getNextFireTime().toLocaleString() + "执行!"); } public void setTimeout(int timeout) { // System.out.println("setTimeout得到自动调用,timeout的值为" ); this.timeout = timeout; } }
在上述方法executeInternal方法中,编写要执行的定时任务内容;有的时候你需要指定job的name和group,查看JobDetailFactoryBean会发现里面含有好多属性,如下:
因此可以利用Spring进行属性注入,在此就不演示了;
很多情况下,我们只是想调用某一个对象的某个方法,这个时候就没有必要去让对象所在的类去某个类,只需要利用MethodInvokingJobDetailFactoryBean类就可以实现针对特定某个方法的调用,如下:
package org.spring.scheduling.quartz.service; /* * 该类不需要继承或实现任何类和接口 */ public class ExampleServiceObject { // 任务调度将要调用执行的方法 public void doIt() { // 该方法内部可以编写实际要操作的内容,同样可以给该方法加事务等 } }
Spring配置如下:
<!-- 将Bean对象纳入Spring的管理范围内 --> <bean id="service" class="org.spring.scheduling.quartz.service.ExampleServiceObject" /> <!-- 配置工厂bean --> <bean id="jobdetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <!-- 执行调用方法所在的对象 --> <property name="targetObject" ref="service"/> <!-- 指定将要执行的方法名称 --> <property name="targetMethod" value="doIt"/> </bean>
好的,下面接着考虑并发问题,一般来说Quartz的Jobs是无状态的,这就导致了很可能它们彼此之间会产生相互影响,比如有两个触发器同时执行同一个JobDetail类对象,那么就有可能在第一个任务没有执行完之前,第二个任务开始执行,那么如何实现后面的定时任务必须等待前一个定时任务执行完毕再执行呢?问题回到了如何不允许JobDetail并发执行,一般来说,如下两种方式:
1
package org.spring.scheduling.quartz.job; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.StatefulJob; import org.springframework.scheduling.quartz.QuartzJobBean; // 通过添加下述注解或实现StatefulJob接口,任意一种方式都可以使Job不能并发执行 @DisallowConcurrentExecution public class ExampleJob extends QuartzJobBean implements StatefulJob { public ExampleJob() { // TODO Auto-generated constructor stub } @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { // 任务代码 } }
2
那么如果我们使用了MethodInvokingJobDetailFactoryBean该如何禁止方法并发执行呢,这个更简单,只需要加一行配置就可以了,如下:
<!-- 配置工厂bean --> <bean id="jobdetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <!-- 执行调用方法所在的对象 --> <property name="targetObject" ref="service"/> <!-- 指定将要执行的方法名称 --> <property name="targetMethod" value="doIt"/> <!-- 禁用并发执行 --> <property name="concurrent" value="false"/> </bean>
二、通过上述内容,讲解了如何创建Job和JobDetail对象以及并发相关问题,下面简单讲解如何使用Spring的触发器和SchedulerFactoryBean来调度Job和JobDetail对象,在Quartz中提供了若干种triggers,其中最常用的是SimpleTrigger和CronTrigger,针对于此,Spring提供了SimpleTriggerFactoryBean和CronTriggerFactoryBean来提供对Quartz的支持,配置及其简单,如下:
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> <!-- 配置触发器的执行任务 --> <property name="jobDetail" ref="jobdetail" /> <!-- 10s后执行 --> <property name="startDelay" value="10" /> <!-- 每个5秒钟出发一次 --> <property name="repeatInterval" value="5000" /> </bean> <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="exampleJob" /> <!-- 在每天早上六点得到执行 --> <property name="cronExpression" value="0 0 6 * * ?" /> </bean>
三、在二中,讲述了如何创建各种类型的触发器,触发器需要调度,Spring提供了SchedulerFactoryBean类用来通过调度触发器来执行任务,需要将triggers当作属性设置给SchedulerFactoryBean,配置如下:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> <ref bean="simpleTrigger" /> </list> </property> </bean>