spring学习笔记(25)spring整合quartz多版本实现企业级任务调度

简介: <div class="markdown_views"><p>在我们的另一个专栏<a href="http://blog.csdn.net/column/details/quartz.html">《深入浅出Quartz任务调度》</a>详细的讲解了使用Quartz适用于从普通门户至网站企业级系统的任务调度实现方法。在下面我们结合实例来完整spring和quartz的整合工作,

在我们的另一个专栏《深入浅出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.MethodInvokingJobDetailFactoryBeanStatefulMethodInvokingJoblistener1listener2methodInvokerorg.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean@3d50475bgetJobClass: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文件夹下载。

目录
相关文章
|
20天前
|
XML 安全 Java
|
1月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
63 9
|
2月前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
41 9
|
1月前
|
Java Kotlin 索引
学习Spring框架特性及jiar包下载
Spring 5作为最新版本,更新了JDK基线至8,修订了核心框架,增强了反射和接口功能,支持响应式编程及Kotlin语言,引入了函数式Web框架,并提升了测试功能。Spring框架可在其官网下载,包括文档、jar包和XML Schema文档,适用于Java SE和Java EE项目。
32 0
|
2月前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
27 1
|
2月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
100 2
|
2月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
194 1
|
2月前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
34 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
2月前
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
36 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
|
2月前
|
Java 关系型数据库 MySQL
springboot学习五:springboot整合Mybatis 连接 mysql数据库
这篇文章是关于如何使用Spring Boot整合MyBatis来连接MySQL数据库,并进行基本的增删改查操作的教程。
211 0
springboot学习五:springboot整合Mybatis 连接 mysql数据库
下一篇
DataWorks