在我们的另一个专栏《深入浅出Quartz任务调度》详细的讲解了使用Quartz适用于从普通门户至网站企业级系统的任务调度实现方法。在下面我们结合实例来完整spring和quartz的整合工作,将我们对quartz的配置统一交给spring容器进行管理。quartz1与quartz2两个版本的差别较大,他们的具体差别可参考我的另一篇文章Quartz任务调度(1)概念例析快速入门 。鉴于我们的实际项目中很多依旧使用着quartz1版本,下面我们会针对quartz1和quartz2的配置分别进行分析。但是:
下面我们会先讲解spring与Quartz1.8.6的整合,不过讲完之后,我们会发现,Quartz1.8.6与Quartz2.2.2的配置区别是很小,因为Quartz2中将原来1.+中的JobDetail和Trigger都变成了接口。但只需将下面的XXXBean换成XXXFactoryBean,即可完美兼容1.+版本配置。
JobDetailBean
扩展自Quartz的JobDetail,当使用Bean声明JobDetail时,Bean的name/id极为任务名字,如果没有指定所属组,就使用默认组,它的常见属性有:
1. jobClass
指定我们自定义的Job接口实现类
2. name
默认为Bean的id名,通过此属性显示指定任务名
3. jobDataAsMap
类型为Map,通过设置此Map的值,对应设置到JobDetail的jobDataMap中
4. applicationContextJobDataKey
通过指定一个key,然后可以通过任务的JobDataMap来访问ApplicationContext,如果不设置此key,则JobDetailbean不会将ApplicationContext放入JobDataMap中
5. jobListenerNames:类型为String[],指定注册在Scheduler中的Joblistener名字,以便这些监听器对本任务进行监听。
6. group:设定组名
下面是一个配置实例:
<bean id="forumMaintainJob" class="tool.job.ForumMaintainJob" />
<bean class="org.springframework.scheduling.quartz.JobDetailBean" id="myJobDetail">
<property name="applicationContextJobDataKey" value="acKey" />
<property name="jobClass" value="tool.job.ForumMaintainJob" />
<property name="description" value="I'm desc" />
<property name="group" value="group1" />
<property name="name" value="testJob2" />
<property name="jobDataAsMap">
<map>
<entry key="key1" value="value1" />
<entry key="key2" value="value2" />
</map>
</property>
<property name="jobListenerNames">
<list>
<value>listener1</value>
<value>listener2</value>
</list>
</property>
</bean>
下面是我们的测试类
public class Test1 {
@Test
public void test1(){
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/spring-task.xml");
JobDetail jobDetail = (JobDetail) ac.getBean("myJobDetail");
System.out.println(jobDetail.getDescription());
System.out.println(jobDetail.getFullName());
System.out.println(jobDetail.getGroup());
System.out.println(jobDetail.getName());
System.out.println(jobDetail.getJobClass());
String[] listenerNames = jobDetail.getJobListenerNames();
for(String name : listenerNames){
System.out.println(name);
}
Map map = jobDetail.getJobDataMap();
for(Iterator it = map.keySet().iterator(); it.hasNext();){
Object key = it.next();
System.out.println( key + "——" + map.get(key));
}
}
}
运行方法后,打印信息:
I’m desc
group1.testJob2
group1
testJob2
class tool.job.ForumMaintainJob
listener1
listener2
acKey——org.springframework.context.support.ClassPathXmlApplicationContext@4d1d54a5: startup date [Sun Mar 27 00:09:10 CST 2016]; root of context hierarchy
key2——value2
key1——value1
MethodInvokingJobDetailFactoryBean
有的时候,我们的任务需要对操作数据库,完成特定的业务处理,如在Service定义了一个备份文章表的方法,要求在每天特定时刻将文章内容复制到一份备份表中,如果使用JobDetailBean,我们可能需要单独配置一个实现类,再完成相应的依赖配置工作来实现调用,但如果使用MethodInvokingJobDetailFactoryBean,我们可以把特定对象的某个方法封装成一个Job,就能直接在我们的Service类中完成任务调度了。
下面是我们的配置实例:
<bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean" id="myJobFactoryBean">
<property name="targetClass" value="com.yc.service.ArticleServiceImpl" /><!-- 指定目标类 -->
<property name="targetMethod" value="copyArticle" /><!-- 指定目标类中的方法 -->
<!-- <property name="staticMethod" value="com.yc.service.ArticleServiceImpl.copyArticle" /> 以上两句等同与这一句 -->
<property name="concurrent" value="false" /><!-- 指定最终封装出的任务类是否无状态,默认为true,表示无状态 -->
<property name="name" value="job1" />
<property name="group" value="group1" />
<property name="jobListenerNames">
<list>
<value>listener1</value>
<value>listener2</value>
</list>
</property>
</bean>
下面是我们的测试方法:
@Test
public void test2(){
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/spring-task.xml");
JobDetail jobDetail = (JobDetail) ac.getBean("myJobFactoryBean");
System.out.println(jobDetail.getDescription());
System.out.println(jobDetail.getFullName());
System.out.println(jobDetail.getGroup());
System.out.println(jobDetail.getName());
System.out.println(jobDetail.getJobClass());
String[] listenerNames = jobDetail.getJobListenerNames();
for(String name : listenerNames){
System.out.println(name);
}
Map map = jobDetail.getJobDataMap();
for(Iterator it = map.keySet().iterator(); it.hasNext();){
Object key = it.next();
System.out.println( key + "——" + map.get(key));
}
}
打印信息
null
group1.job1
group1
job1
class org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBeanStatefulMethodInvokingJoblistener1listener2methodInvoker——org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean@3d50475b与之前对比,我们的getJobClass变成了:MethodInvokingJobDetailFactoryBeanStatefulMethodInvokingJob
这里是有状态的,如果在我们配置中将concurrent设为true或不设置,上述类就会变成:
MethodInvokingJobDetailFactoryBean$MethodInvokingJob
SimpleTriggerBean
直接上配置实例:
<bean class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean" id="mySimpleTrigger" >
<property name="jobDetail" ref="myJobDetail" />
<property name="name" value="trigger1" />
<property name="description" value="I'm desc of trigger" />
<property name="group" value="tgroup1" />
<property name="repeatCount" value="10" /><!-- 设置重复次数-->
<property name="repeatInterval" value="1000" /><!-- 设置调用间隔 -->
<property name="startDelay" value="1000" /><!-- 设置延迟执行时间,单位为毫秒 -->
<property name="startTime">
<bean class="java.util.Date" />
</property>
</bean>
测试方法
@Test
public void test3(){
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/spring-task.xml");
SimpleTrigger simpleTrigger= (SimpleTrigger) ac.getBean("mySimpleTrigger");
System.out.println("name = " + simpleTrigger.getName());
System.out.println("group = " + simpleTrigger.getGroup());
System.out.println("jobGroup = " + simpleTrigger.getJobGroup());
System.out.println("jobName = " + simpleTrigger.getJobName());
System.out.println("jobDataMap = " + simpleTrigger.getJobDataMap());
System.out.println("fullName = " + simpleTrigger.getFullName());
System.out.println("fullJobName = " + simpleTrigger.getFullJobName());
System.out.println("repearCount = " + simpleTrigger.getRepeatCount());
System.out.println("repeatInterval = " + simpleTrigger.getRepeatInterval());
System.out.println("TimesTriggered = " + simpleTrigger.getTimesTriggered());//被触发次数
System.out.println("nextFireTime = " + simpleTrigger.getNextFireTime());//下次触发时间,如果不会再触发,则为null
System.out.println("previousFireTime = " + simpleTrigger.getPreviousFireTime());//上次触发时间,如果还没开始触发,则为null
System.out.println("startTime = " + simpleTrigger.getStartTime());//开始时间
System.out.println("finalFireTime = " + simpleTrigger.getFinalFireTime());//最后触发时间
}
打印结果:
name = trigger1
group = tgroup1
jobGroup = group1
jobName = testJob2
jobDataMap = org.quartz.JobDataMap@d9ffa6cd
fullName = tgroup1.trigger1
fullJobName = group1.testJob2
repearCount = 10
repeatInterval = 1000
TimesTriggered = 0
nextFireTime = null
previousFireTime = null
startTime = Sun Mar 27 00:35:12 CST 2016
finalFireTime = Sun Mar 27 00:35:22 CST 2016
注意到,这里的finalFireTime是根据startTime和重复次数、间隔时间来算的。而即时我们一次都没调用,但触发器处于不会触发状态,nextFireTime也为null。
CronTriggerBean
实例配置如下:
<bean class="org.springframework.scheduling.quartz.CronTriggerBean" id="myCronTriggerBean">
<property name="cronExpression" value="0/5 * * * * ?" />
<property name="jobDetail" ref="myJobFactoryBean" />
</bean>
其中涉及到的其他属性和SimpleTrigger大同小异,这里就不再提及,关于Cron表达式的使用方法可参考我前面的一篇文章
SchedulerFactoryBean
SchedulerFactoryBean能感知Spring容器的生命周期,在Spring容器开启后,Scheduler自动开始工作,而在Spring容器关闭后,自动关闭Scheduler.
下面是我们的配置实例:
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean" id="mySecheduler">
<property name="autoStartup" value="true" /><!-- 设置是否为初始化后自动开启,默认为true,如果需要手动开启,则将此属性设为false -->
<!-- <property name="configLocation" value="classpath:quartz.properties" /> --><!-- 读取我们的quartz属性文件资源 -->
<property name="jobDetails" >
<list >
<ref local="myJobDetail"/>
<ref local="myJobFactoryBean"/>
</list>
</property>
<property name="triggers">
<list>
<ref local="mySimpleTrigger"/>
<ref local="myCronTriggerBean"/>
</list>
</property>
<property name="startupDelay" value="2" /><!-- 以秒为单位,设置延迟开始时间 -->
</bean>
这里,我们集成了前面所有的配置进行测试。
注释一些冲突的属性,完整的xml配置如下所示:
<bean id="forumMaintainJob" class="tool.job.ForumMaintainJob" />
<bean class="org.springframework.scheduling.quartz.JobDetailBean" id="myJobDetail">
<property name="applicationContextJobDataKey" value="acKey" />
<property name="jobClass" value="tool.job.ForumMaintainJob" />
<property name="description" value="I'm desc" />
<property name="group" value="group1" />
<property name="name" value="testJob1" />
<property name="jobDataAsMap">
<map>
<entry key="key1" value="value1" />
<entry key="key2" value="value2" />
</map>
</property>
<!-- <property name="jobListenerNames">
<list>
<value>listener1</value>
<value>listener2</value>
</list>
</property> -->
</bean>
<bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean" id="myJobFactoryBean">
<property name="targetClass" value="com.yc.service.ArticleServiceImpl" /><!-- 指定目标类 -->
<property name="targetMethod" value="copyArticle" /><!-- 指定目标类中的方法 -->
<!-- <property name="staticMethod" value="com.yc.service.ArticleServiceImpl.copyArticle" /> 以上两句等同与这一句 -->
<!-- <property name="concurrent" value="true" /> --><!-- 指定最终封装出的任务类是否有状态 -->
<property name="name" value="job2" />
<property name="group" value="group2" />
<!-- <property name="jobListenerNames">
<list>
<value>listener1</value>
<value>listener2</value>
</list>
</property> -->
</bean>
<bean class="org.springframework.scheduling.quartz.SimpleTriggerBean" id="mySimpleTrigger" >
<property name="jobDetail" ref="myJobDetail" />
<property name="name" value="trigger1" />
<property name="description" value="I'm desc of trigger" />
<property name="group" value="tgroup1" />
<property name="repeatCount" value="10" /><!-- 设置重复次数-->
<property name="repeatInterval" value="1000" /><!-- 设置调用间隔 -->
<property name="startDelay" value="1000" /><!-- 设置延迟执行时间,单位为毫秒 -->
<property name="startTime">
<bean class="java.util.Date" />
</property>
</bean>
<bean class="org.springframework.scheduling.quartz.CronTriggerBean" id="myCronTriggerBean">
<property name="cronExpression" value="0/5 * * * * ?" />
<property name="jobDetail" ref="myJobFactoryBean" />
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean" id="mySecheduler">
<property name="autoStartup" value="true" /><!-- 设置是否为初始化后自动开启,默认为true,如果需要手动开启,则将此属性设为false -->
<!-- <property name="configLocation" value="classpath:quartz.properties" /> --><!-- 读取我们的quartz属性文件资源 -->
<property name="jobDetails" >
<list >
<ref local="myJobDetail"/>
<ref local="myJobFactoryBean"/>
</list>
</property>
<property name="triggers">
<list>
<ref local="mySimpleTrigger"/>
<ref local="myCronTriggerBean"/>
</list>
</property>
<property name="startupDelay" value="2" /><!-- 以秒为单位,设置延迟开始时间 -->
</bean>
其中用到的两个目标类如下
public class ForumMaintainJob implements Job{
@Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
System.out.println("论坛维护");
}
}
public class ArticleServiceImpl {
public static void copyArticle() {//注意,我们的代理方法必须是静态的
System.out.println("复制文章操作");
}
}
进行测试
@Test
public void test4() throws InterruptedException{
//只需初始化我们的Spring容器即可完成定时器配置
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/spring-task.xml");
Thread.sleep(100000);//这里让主线程休眠确保定时器能正常运行
}
运行程序后,部分打印结果如下:
INFO : org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
INFO : org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
INFO : org.quartz.core.QuartzScheduler - Quartz Scheduler v.1.8.6 created.
INFO : org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
INFO : org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v1.8.6) ‘mySecheduler’ with instanceId ‘NON_CLUSTERED’
Scheduler class: ‘org.quartz.core.QuartzScheduler’ - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool ‘org.quartz.simpl.SimpleThreadPool’ - with 10 threads.
Using job-store ‘org.quartz.simpl.RAMJobStore’ - which does not support persistence. and is not clustered.INFO : org.quartz.impl.StdSchedulerFactory - Quartz scheduler ‘mySecheduler’ initialized from an externally provided properties instance.
INFO : org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 1.8.6
INFO : org.quartz.core.QuartzScheduler - JobFactory set to: org.springframework.scheduling.quartz.AdaptableJobFactory@65d1fefa
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Finished creating instance of bean ‘mySecheduler’
DEBUG: org.springframework.context.support.ClassPathXmlApplicationContext - Unable to locate LifecycleProcessor with name ‘lifecycleProcessor’: using default [org.springframework.context.support.DefaultLifecycleProcessor@3e81d0bf]
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean ‘mySecheduler’
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean ‘lifecycleProcessor’
INFO : org.springframework.context.support.DefaultLifecycleProcessor - Starting beans in phase 2147483647
DEBUG: org.springframework.context.support.DefaultLifecycleProcessor - Starting bean ‘mySecheduler’ of type [class org.springframework.scheduling.quartz.SchedulerFactoryBean]
INFO : org.springframework.scheduling.quartz.SchedulerFactoryBean - Will start Quartz Scheduler [mySecheduler] in 2 seconds
DEBUG: org.springframework.context.support.DefaultLifecycleProcessor - Successfully started bean ‘mySecheduler’
DEBUG: org.springframework.core.env.PropertySourcesPropertyResolver - Searching for key ‘spring.liveBeansView.mbeanDomain’ in [systemProperties]
DEBUG: org.springframework.core.env.PropertySourcesPropertyResolver - Searching for key ‘spring.liveBeansView.mbeanDomain’ in [systemEnvironment]
DEBUG: org.springframework.core.env.PropertySourcesPropertyResolver - Could not find key ‘spring.liveBeansView.mbeanDomain’ in any property source. Returning [null]
DEBUG: org.quartz.utils.UpdateChecker - Checking for available updated version of Quartz…
INFO : org.springframework.scheduling.quartz.SchedulerFactoryBean - Starting Quartz Scheduler now, after delay of 2 seconds
INFO : org.quartz.core.QuartzScheduler - Scheduler mySecheduler_$_NON_CLUSTERED started.
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
论坛维护
论坛维护
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group2.job2
复制文章操作
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
论坛维护
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
论坛维护
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
论坛维护
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
论坛维护
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
论坛维护
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group2.job2
复制文章操作
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
论坛维护
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
论坛维护
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
论坛维护
DEBUG: org.quartz.core.JobRunShell - Calling execute on job group1.testJob1
论坛维护
这里“论坛维护”任务每隔1s运行一次,共运行十次,“复制文章”操作每隔5s执行一次,无次数限制
以上是基于1.8.6的,如果我们想要在Quartz2.+版本中运行,我们只需改动:
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean" id="myJobDetail" >
<property name="durability" value="true"></property>
....
</bean>
<bean class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean" id="mySimpleTrigger" >
....
</bean>
<bean class="org.springframework.scheduling.quartz.CronTriggerFactoryBean" id="myCronTriggerBean">
.....
</bean>
即将原来的xxxBean换成xxxFactoryBean来生成相应的接口实现类即可。
源码下载
本篇文章源码请移步https://github.com/jeanhao/spring的springQuartz文件夹下载。