spring中的定时任务

http://blog.csdn.net/xxtjp/article/details/7016517
http://blog.sina.com.cn/s/blog_670eabd10100pe0o.html2009-08-11 
Spring的任务调度管理  
Spring的任务调度管理  
从兴公司 | 陈百平  
­  
­  
在项目开发中,有不少程序需要定时触发。比如,统计报表程序需要在每月初运行一次,对上月份数据进行统计。而这种触发,在unix上就是使用 crontab或at命令来指定。其中at命令可以设定程序在某年某月某日、何时何分定时启动,at命令只执行一次程序。而crontab则可以设定程序在指定的时刻重复执行。在window操作系统中也有类似的命令。Window的at命令在功能上基本与unix的一致。Window控制面板中的“任务计划”与unix上从crontab类似,只是它是基于图形界面的,更人性化一些。  
在开发上一个项目时,我遇到以下这样的一个问题。项目是java+tomcat开发的,在window系统上运行,但我们没有该服务器上的管理员权限(因为该服务器上还运行着其它重要的系统,管理员只给了我们一个最低权限的帐号),无法配置“任务计划”。我们使用该功能的话,需要打申请,通过客户的系统管理员帮我们配置。考虑到我们的业务统计报表程序需要有一段稳定期,期间必定需要做不少的修正,每修改一次,都需要打申请后才能测试,一个流程下来会耗费不少时间。另外,数据库连接使用了tomcat自带的连接池,如果使用了window的任务计划,则需要启动独立于tomcat的应用程序,而项目组中没有人知道如何在这个应用程序获取tomcat连接池中的数据库连接。替代方法是不使用tomcat的连接池,并给这个应用程序配置独立数据库参数配置文件。这样的话在应用程序中获取数据库连接的方法就得改动,并且需要维护两套配置文件,也挺麻烦。  
­  
最后经小组商讨,决定自己编写定时服务调度线程,并随tomcat应用程序一起启动运行,共用tomcat的数据库连接池。为此,我们耗费了一个工作周的开发时间。最惨的是,服务调度线程运行不太稳定,有时还需人工干预一下。  
­  
最近看Spring,发现Spring有任务调度管理功能,能很好地解决以上的问题。于是我们决定将定时程序迁移到Spring中。下面就结合我们这次程序的迁移,介绍一下如何使用Spring的任务调度。  
­  
在讨论Spring的任务调度前,我们先谈谈Spring。仔细探究Spring,你会发现很多有趣的东西。第一,Spring中并没有多少新技术,就如AOP这些概念,其实于上个****早已存在了。 MS的操作系统 window2000及以后版本的MTS(Microsoft Transaction Server,事务服务器)就是使用AOP实现的。至于更早的如TUXEDO之类的交易中间件,我想在其中也应该有着AOP理念。第二,Spring并没有实现很多功能,它只是集成了很多功能。如数据层,它可以集成Hibernate或iBATIS;MVC框架,它可以集成Struts;邮件收发,它集成了JavaMail;包括下面我们将要讨论的Spring任务调度,它也是集成了第三方的任务调度类库:JDK自带的Timer类库或第三方的 Quartz类库。Spring只是做了个抽象的接口和集成,方便我们调用。Sping看起来很庞大,可以做很多事情,其实它什么事都不做!这可能也是它的“轻量级”含义所在的吧。  
­  
在Spring中,使用JDK的Timer类库来做任务调度功能不是很方便,关键它不可以象cron服务那样可以指定具体年、月、日、时和分的时间。你只能将时间通过换算成微秒后传给它。如任务是每天执行一次,则需要在spring中如下配置:  
­  
<bean id="scheduledTask" class= "org.springframework.scheduling.timer.ScheduledTimerTask">  
<!--程序启动后开始执行任务的延迟时间 -->  
<property name="delay" value="0" />  
<!--每隔一天【一天=24×60×60×1000微秒】执行一次-->  
<property name="period" value="86400000" />  
<!--业务统计报表bean -->  
<property name="timerTask" ref="businessReport" />  
</bean>  
­  
其中period就是一天的微秒数。如果每月1日运行一次,那就复杂了,不知如何配置。因为月份有大、小月之分,每月的微秒数都不一样。  
­  
而Quartz类库不但有着上述JDK的Timer类库类似的配置,更重要的,它还有着类似于unix的cron服务的配置。因此,在迁移中我们采用了Quartz类库的接口。  
­  
Quartz可以通过两种方式来调度程序:一是使用Spring提供的MethodInvokingJobDetailFactoryBean 代理类,Quartz通过该代理类直接调度任务类的某个函数;二是任务类继承并实现Quartz接口,Quartz通过该接口进行调度。  
­  
如果采用第一种方式,即由Quartz直接调度任务类的某个接口,那么,业务类是不必进行任何修改的。我们的业务类大概如下  
­  
public class BusinessReport {  
public void perform(){ //执行报表统计入口函数   
//业务逻辑   
}  
}  
­  
第一步,在Spring配置文件中增加本业务类  
­  
<bean id=" businessReport " class=" BusinessReport "/>  
­  
第二步,定义任务。在Spring配置文件中配置代理类MethodInvokingJobDetailFactoryBean,定义任务的详细信息。  
­  
<bean id=" reportTask " class= "org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">  
<property name=" targetObject " ref=" businessReport " />  
<property name=" targetMethod " value=" perform " />  
<property name=" concurrent " value=" false " />  
</bean>  
­  
这个配置告诉Spring,我们的任务是执行id为businessReport的bean中的perform函数。其中参数concurrent告诉Spring,不要并发运行这个任务。  
­  
第三步,配置一个触发器。在Spring配置文件中配置触发器类CronTriggerBean 。  
­  
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">  
<property name="jobDetail" ref=" reportTask " />  
<property name="cronExpression" value="0 0 1 1 * ?" />  
</bean>  
­  
触发器将告诉Quartz两件事:在何时触发任务、触发哪个任务。其中属性参数cronExpression为调度时间,格式和unix上的 crontab类似,具体见下表1。“0 0 1 1 * ?”表示每月1日凌晨1点运行。其中问号表示忽略该位置(星期)上的值。属性参数jobDetail指向具体的任务bean:reportTask 。如果你有多个任务,每个任务的触发时间都不一样,则你可以在此配置多个不同的触发器。  
­  
表1. cronExpression的时间格式  
位置  
­  
含义  
­  
1  
­  
秒(0–59)  
­  
2  
­  
分(0–59)  
­  
3  
­  
时(0–23)  
­  
4  
­  
日(1–31)  
­  
5  
­  
月(1–12)  
­  
6  
­  
星期(SUN–SAT or 1–7)  
­  
7  
­  
年(可选, 1970–2099)  
­  
­  
第四步,配置一个调度器。在Spring配置文件中配置调度器类SchedulerFactoryBean。  
­  
<bean class= "org.springframework.scheduling.quartz.SchedulerFactoryBean">  
<property name="triggers">  
<list>  
<ref bean="cronTrigger" />  
</list>  
</property>  
</bean>  
­  
该调度器用于管理触发器。只有在调度器中列表出现的触发器才被Quartz系统调度执行。至此,所有的配置已完成,任务已能正常跑了。  
­  
如果采用第二种方式,那业务类是要进行小小的修改。整个过程如下。  
­  
第一步,修改上述的业务类,修改如下:  
­  
public class BusinessReport implements org.quartz.Job {//继承quartz 的job接口   
//实现job接口的execute函数,在其中简单调用perform()函数就可以了。   
public void execute(org.quartz.JobExecutionContext context){  
//执行报表统计入口函数   
perform()  
}  
//其它的保持不变。   
public void perform(){ //执行报表统计入口函数   
//业务逻辑   
}  
}  
­  
修改过程比较简单,只增加了两三行代码。  
­  
第二步,定义任务。在Spring配置文件中配置如下任务的详细信息。  
­  
<bean name=" reportTask " class= "org.springframework.scheduling.quartz.JobDetailBean">  
<property name="jobClass" value=" BusinessReport " />  
</bean>  
­  
此配置告诉Quartz,我们的任务类的名字为BusinessReport。在定时触发时,Quartz会利用该类名来创建任务的实例,并执行该实例的execute方法。  
­  
第三、第四步与第一种的调度方式相应的步骤一样。  
­  
按第二种方式,整个Spring的配置文件如下:  
­  
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  
"http://www.springframework.org/dtd/spring-beans.dtd">  
<beans>  
­  
<!-- 我们的任务 -->  
<bean name=" reportTask " class= "org.springframework.scheduling.quartz.JobDetailBean">  
<property name="jobClass" value=" BusinessReport " />  
</bean>  
­  
<!-- 触发器 -->  
<bean id="cronTrigger" class= "org.springframework.scheduling.quartz.CronTriggerBean">  
<!-- 指向我们的任务 -->  
<property name="jobDetail" ref=" reportTask " />  
<!-- 每月1日凌晨1点运行 -->  
<property name="cronExpression" value="0 0 1 1 * ?" />  
</bean>  
­  
<!-- 调度器 -->  
<bean class= "org.springframework.scheduling.quartz.SchedulerFactoryBean">  
<property name="triggers">  
<list><!-- 触发器列表-->  
<ref bean="cronTrigger" />  
</list>  
</property>  
</bean>  
­  
</beans>  
­  
按照上述步骤,整个迁移十分顺利,程序也稳定运行。  
­  
到Quartz的官方网站溜达了一下,发现Quartz还有不少强大的功能,包括任务的持久化、事务化、容错、负载均衡等功能。但这些功能暂时在项目中都用不上。  
­  
Spring的任务调度管理就介绍到这里。Spring还有很多其它实用的功能,如果我们能拿过来用在项目中,不但加快开发进度,而且还可以保证项目质量。  
­  
参考文献:  
1.http://www.springframework.org Spring的官方网站   
2.http://www.opensymphony.com/quartz/ Quartz的官方网站   
3.《Pro Spring》Rob Harrop and Jan Machacek,电子版  
 
 
 
 
 
 
 
 
 
 
 
 
 
 
补充,如果你使用的不是固定时间执行,而是隔几分钟或者几小时执行的话,就会有一点修改的地方:  
 
<bean id="SmsSendTaskScheduledTask" class="org.springframework.scheduling.quartz.CronTriggerBean">这之间的内容改为:  
 
 
 
<bean id="SmsSendTaskScheduledTask" class="org.springframework.scheduling.quartz.SimpleTriggerBean">  
 
<property name="startDelay">  
 
<value>60000</value><!-- 服务启动一分钟后执行 -->  
 
</property>  
 
<property name="repeatInterval">  
 
<value>6000</value><!-- 每隔1分钟执行一次 -->  
 
</property>  
 
<property name="jobDetail">  
 
<ref bean="SmsSendTaskDetail"/>  
 
</property>  
 
</bean>  
 
===========================================  
 
关于cronExpression的介绍:  
字段  
 
允许值  
 
允许的特殊字符  
 
秒  
 
0-59  
 
, - * /  
 
 
分  
 
0-59  
 
, - * /  
 
 
小时  
 
0-23  
 
, - * /  
 
 
日期  
 
1-31  
 
, - * ? / L W C  
 
 
月份  
 
1-12 或者 JAN-DEC  
 
, - * /  
 
 
星期  
 
1-7 或者 SUN-SAT  
 
, - * ? / L C #  
 
 
年(可选)  
 
留空, 1970-2099  
 
, - * /  
   
如上面的表达式所示:  
 
详细说明如下:  
 
The ′*′ character is used to specify all values. For example, "*" in the minute field means "every minute".  
 
“*”字符被用来指定所有的值。如:”*“在分钟的字段域里表示“每分钟”。  
 
The ′?′ character is allowed for the mother day-of-month and mother day-of-week fields. It is used to specify ′no specific value′. This is useful when you need to specify something in one of the two fileds, but not the other. See the examples below for clarification.  
 
“?”字符只在日期域和星期域中使用。它被用来指定“非明确的值”。当你需要通过在这两个域中的一个来指定一些东西的时候,它是有用的。看下面的例子你就会明白。  
 
The ′-′ character is used to specify ranges For example "10-12" in the hour field means "the hours 10, 11 and 12".  
 
“-”字符被用来指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点”。  
 
The ′,′ character is used to specify additional values. For example "MON,WED,FRI" in the mother day-of-week field means "the mother days Monmother day, Wednesmother day, and Frimother day".  
 
“,”字符被用来指定另外的值。如:“MON,WED,FRI”在星期域里表示”星期一、星期三、星期五”.  
 
============================  
 
每天早上6点  
0 6 * * *  
每两个小时  
0 */2 * * *  
晚上11点到早上8点之间每两个小时,早上八点  
0 23-7/2,8 * * *  
每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点  
0 11 4 * 1-3  
1月1日早上4点  
0 4 1 1 *  

猜你喜欢

转载自panshaobinsb.iteye.com/blog/1568657