Spring
中正確使用
Quartz
和
CronExpression
???? Quartz
作為企業級任務調度框架以其靈活的使用方式、強大的功能已經得到廣泛應用,作為一向喜歡將業內流行的工具納入支持的
Spring
自然已經內置
了對
Quartz
的支持,使得
Quartz
中最常使用的
SimpleTrigger
和
CronTrigger
的使用得到了最大簡化,分別對應
Spring
的
org.springframework.scheduling.quartz.SimpleTriggerBean
和
org.springframework.scheduling.quartz.CronTriggerBean
,這兩個類用起來非常方便,其中
SimpleTrigger
更類似于
JDK
中的
Timer
,它只是簡單的以某個時間間隔來執行某個任務而已,比較簡陋,而
CronTrigger
功能則十
分強大,可以設定制定任務在任意指定時刻內調用,其使用
Unix
中的
Cron Expression
來制定調度策略,十分靈活,不過
Cron Expression
可能需要用點時間來學習,不過一旦掌握會覺得真的很不錯,掌握了這兩種
Trigger
基本上就可以應付實現大多數
J2EE
應用中的時
間任務調度服務了。
????
下面舉一個簡單例子說明一下在
Spring
????
首先建立兩個要調度的任務,他們都必須繼承自
org.springframework.scheduling.quartz.QuartzJobBean
,而業務邏輯都是放在
executeInternal
方法中,指定的任務邏輯實現之后,需要將其注入到一個
JobDetailBean
中,
JobDetailBean
可以看作
為一個具體任務的設置它指定了要執行的任務和執行任務的時間策略,
JobDetailBean
不用實現,只需要在
Spring
的配置文件中設置便可:
package com.peuo.albumSys.scheduling;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.apache.log4j.Logger;
import com.peuo.albumSys.business.IAlbumSysService;
import com.peuo.albumSys.domainobj.User;
import com.peuo.albumSys.domainobj.Album;
/**
*
在指定的時刻查詢當前所有的用戶,并顯示其姓名。
* @author Shippo Field
*/
public class ScheduledQueryUsers extends QuartzJobBean {
???? private static Logger log = Logger.getLogger(ScheduledQueryUsers.class);
????
???? private IAlbumSysService service = null;
???? /**
????? * @param service
要設置的業務邏輯
Bean
實例,可以是已被納入事務管理的某個
Bean
,這樣以來
QuartzJobBean
內部邏輯也可以納入事務管理。
????? */
???? public void setService(IAlbumSysService service) {
???????? this.service = service;
???? }
????
???? /*
(非
Javadoc
)
????? * @see org.springframework.scheduling.quartz.QuartzJobBean#executeInternal(org.quartz.JobExecutionContext)
????? */
???? protected void executeInternal(JobExecutionContext ctx)
???????????? throws JobExecutionException {
???????? log.debug("Now entering method executeInternal()...");
???????? List users = service.findAllUser();
???????? StringBuffer str = new StringBuffer();
???????? for(int i = 0; i < users.size(); i++){
???????????? User user = (User)users.get(i);
???????????? str.append("\nUser[" + i + "] = " + user.getUserName());
???????????? Set albums = user.getAlbumSet();
???????????? Iterator it = albums.iterator();
???????????? str.append("(Albums:");
???????????? while(it.hasNext()){
???????????????? str.append(((Album)it.next()).getAlbumName()).append(";");
???????????? }
???????? }
???????? log.debug(str.toString());
???? }
}
??
package com.peuo.albumSys.scheduling;
import java.util.Date;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.apache.log4j.Logger;
import com.peuo.albumSys.business.IAlbumSysService;
import com.peuo.albumSys.domainobj.Album;
import com.peuo.albumSys.domainobj.Photo;
/**
*
本程序會在指定的時刻插入照片
* @author Field
*/
public class ScheduledAddPhoto extends QuartzJobBean {
???? private static Logger log = Logger.getLogger(ScheduledAddPhoto.class);
????
???? private IAlbumSysService service = null;
???? private static int count = 0;
???? /*
(非
Javadoc
)
????? * @see org.springframework.scheduling.quartz.QuartzJobBean#executeInternal(org.quartz.JobExecutionContext)
????? */
???? protected void executeInternal(JobExecutionContext arg0)
???????????? throws JobExecutionException {
???????? Date now = new Date(System.currentTimeMillis());
???????? Photo newPhoto = new Photo();
???????? newPhoto.setAlbum(new Album(new Integer(1)));
???????? newPhoto.setPhotoName( now.toString());
???????? newPhoto.setPhotoUrl("Photo url" + ++count);
???????? service.addPhoto(newPhoto);
???????? log.debug("New photo added : name =" + newPhoto.getPhotoName() +"; url = " + newPhoto.getPhotoUrl());
???? }
???? /**
????? * @return
當前業務邏輯實例。
????? */
???? public IAlbumSysService getService() {
???????? return service;
???? }
???? /**
????? * @param service
要設置的業務邏輯實例。
????? */
???? public void setService(IAlbumSysService service) {
???????? this.service = service;
???? }
}
然后在Spring的配置文件中分別設置一個SimpleTriggerBean和CronTriggerBean來調用上面兩個任務,配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "
http://www.springframework.org/dtd/spring-beans.dtd
">
<beans>
……………………………………………………………………………………………………
??? <bean id="queryUserJob" class="org.springframework.scheduling.quartz.JobDetailBean">
???? <property name="jobClass">
????? <value>com.peuo.albumSys.scheduling.ScheduledQueryUsers</value>
???? </property>
???? <property name="jobDataAsMap">
????? <map>
?????? <entry key="service">
??????? <ref local="albumSysService"/>
???? </entry>
????? </map>
???? </property>
??? </bean>
???
??? <bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
???? <property name="jobDetail">
????? <ref local="queryUserJob"/>
???? </property>
???? <property name="startDelay">
????? <value>15000</value>
???? </property>
???? <property name="repeatInterval">
????? <value>60000</value>
???? </property>
???? <property name="repeatCount">
????? <value>-1</value>
???? </property>
??? </bean>
???
<bean id="addPhotoJob" class="org.springframework.scheduling.quartz.JobDetailBean">
?? <property name="jobClass">
??? <value>com.peuo.albumSys.scheduling.ScheduledAddPhoto</value>
?? </property>
<property name="jobDataAsMap">
??? <map>
???? <entry key="service">
????? <ref local="albumSysService" />
???? </entry>
??? </map>
?? </property>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
?? <property name="jobDetail">
??? <ref local="addPhotoJob"/>
?? </property>
?? <property name="cronExpression">
??? <value>0 20 19 * * ?</value>
?? </property>
</bean>
??? <bean id="sfb" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
???? <property name="triggers">
????? <list>
?????? <ref local="simpleTrigger"/>
???? <ref local="cronTrigger"/>
????? </list>
???? </property>
??? </bean>
??
上面紅色標出的部分可以看出,
Spring
對于將
QuartzJob
注入
JobDetailBean
的方式有點特殊,你需要將要注入給
QuartzJob
的某個屬性注入到
JobDetailBean
的
jobDataAsMap
中,
這不同于
Spring
平時直接對某個
Bean
的屬性直接注入的方式,這主要是因為
Quartz
執行一個任務(
JOB
或者叫作業)的時候會提供給任務一個
JobExecutionContext
的任務執行上下文,從這個上下文中可以獲得
Job
的環境設置,具體的可以看一下
Quartz
的
API
文檔就明白
了,反正只要記住這里的注入方式比較特殊就行了。
以上配置文件分別設置了一個
SimpleTriggerBean
和
CronTriggerBean
,前者將會在程序執行
15
秒之后開始不斷以間隔一
分鐘的方式不斷查詢當前用戶信息并顯示出來,后者則指定每天的
19
:
20
分執行向
ID
為
1
的用戶的相冊內添加一張照片的任務,其
Cron Expression
為
0 20 19 * * ?
。
從以上可以看出,
Spring
大大簡化了對
Quartz
任務時間調度框架的使用,將其完美的融合入了
Spring
提供的
IoC
容器,只是其注入方式需要注意,還有就是
Cron Expression
一定要掌握,這個是
Quartz
真正的強大之處。
??
網上似乎對
Cron
表達式的中文介紹相當少,我干脆就把
Quartz
中的
doc
翻譯一下,各位需要的朋友可以快速了解一下大致用法:
一個
Cron-
表達式是一個由六至七個字段組成由空格分隔的字符串,其中
6
個字段是必須的而一個是可選的,如下:
字段名
|
?? |
允許的值
|
?? |
允許的特殊字符
|
秒
|
?? |
0-59
|
?? |
, - * /
|
分
|
?? |
0-59
|
?? |
, - * /
|
小時
|
?? |
0-23
|
?? |
, - * /
|
日
|
?? |
1-31
|
?? |
, - * ? / L W C
|
月
|
?? |
1-12 or JAN-DEC
|
?? |
, - * /
|
周幾
|
?? |
1-7 or SUN-SAT
|
?? |
, - * ? / L C #
|
年
(
可選字段
)
|
?? |
empty, 1970-2099
|
?? |
, - * /
|
'*'
字符可以用于所有字段,在
“
分
”
字段中設為
"*"
表示
"
每一分鐘
"
的含義。
'?'
字符可以用在
“
日
”
和
“
周幾
”
字段
.
它用來指定
'
不明確的值
'.
這在你需要指定這兩個字段中的某一個值而不是另外一個的時候會被用到。在后面的例子中可以看到其含義。
'-'
字符被用來指定一個值的范圍,比如在
“
小時
”
字段中設為
"10-12"
表示
"10
點到
12
點
".
','
字符指定數個值。比如在
“
周幾
”
字段中設為
"MON,WED,FRI"
表示
"the days Monday, Wednesday, and Friday".
'/'
字符用來指定一個值的的增加幅度
.
比如在
“
秒
”
字段中設置為
"0/15"
表示
"
第
0, 15, 30,
和
45
秒
"
。而
"5/15"
則表示
"
第
5, 20, 35,
和
50".
在
'/'
前加
"*"
字符相當于指定從
0
秒開始
.
每個字段都有一系列可以開始或結束的數值。對于
“
秒
”
和
“
分
”
字段來說,其數值范圍為
0
到
59
,對于
“
小時
”
字段來說其為
0
到
23,
對于
“
日
”
字段來說為
0
到
31,
而對于
“
月
”
字段來說為
1
到
12
。
"/"
字段僅僅只是幫助你在允許的數值范圍內從開始
"
第
n"
的值。
因此
對于
“
月
”
字段來說
"7/6"
只是表示
7
月被開啟而不是
“
每六個月
”,
請注意其中微妙的差別。
'L'
字符可用在
“
日
”
和
“
周幾
”
這兩個字段。它是
"last"
的縮寫
,
但是在這兩個字段中有不同的含義。例如
,“
日
”
字段中的
"L"
表示
"
一個月中的最后一天
" ——
對于一月就是
31
號對于二月來說就是
28
號(非閏年)。而在
“
周幾
”
字段中
,
它簡單的表示
"7" or "SAT"
,但是如果在
“
周幾
”
字段中使用時跟在某個數字之后
,
它表示
"
該月最后一個星期
×" ——
比如
"
'W'
可用于
“
日
”
字段。用來指定歷給定日期最近的工作日
(
周一到周五
)
。比如你將
“
日
”
字段設為
"15W"
,意為
: "
離該月
15
號最近的工作日
"
。因此如果
15
號為周六,觸發器會在
14
號即周五調用。如果
15
號為周日
,
觸發器會在
16
號也就是周一觸發。如果
15
號為周二
,
那么當天就會觸發。然而如果你將
“
日
”
字段設為
"1W",
而一號又是周六
,
觸發器會于下周一也就是當月的
3
號觸發
,
因為它不會越過當月的值的范圍邊界。
'W'
字符只能用于
“
日
”
字段的值為單獨的一天而不是一系列值的時候。
'L'
和
'W'
可以組合用于
“
日
”
字段表示為
'LW'
,意為
"
該月最后一個工作日
"
。
'#'
字符可用于
“
周幾
”
字段。該字符表示
“
該月第幾個周
×”
,比如
"6#3"
表示該月第三個周五
( 6
表示周五而
"#3"
該月第三個
)
。再比如
: "2#1" =
表示該月第一個周一而
"4#5" =
該月第五個周三。注意如果你指定
"#5"
該月沒有第五個
“
周
×”
,該月是不會觸發的。
'C'
字符可用于
“
日
”
和
“
周幾
”
字段,它是
"calendar"
的縮寫。
它表示為基于相關的日歷所計算出的值(如果有的話)。如果沒有關聯的日歷
,
那它等同于包含全部日歷。
“
日
”
字段值為
"
對于
“
月份
”
字段和
“
周幾
”
字段來說合法的字符都不是大小寫敏感的。
下面是一些完整的例子
:
表達式
|
?? |
含義
|
"0 0 12 * * ?"
|
?? |
每天中午十二點觸發
|
"0 15 10 ? * *"
|
?? |
每天早上
10
:
15
觸發
|
"0 15 10 * * ?"
|
?? |
每天早上
10
:
15
觸發
|
"0 15 10 * * ? *"
|
?? |
每天早上
10
:
15
觸發
|
"0 15 10 * * ? 2005"
|
?? |
2005
年的每天早上
10
:
15
觸發
|
"0 * 14 * * ?"
|
?? |
每天從下午
2
點開始到
2
點
59
分每分鐘一次觸發
|
"0 0/5 14 * * ?"
|
?? |
每天從下午
2
點開始到
2
:
55
分結束每
5
分鐘一次觸發
|
"0 0/5 14,18 * * ?"
|
?? |
每天的下午
2
點至
2
:
55
和
6
點至
6
點
55
分兩個時間段內每
5
分鐘一次觸發
|
"0 0-5 14 * * ?"
|
?? |
每天
14:00
至
14:05
每分鐘一次觸發
|
"0 10,44 14 ? 3 WED"
|
?? |
三月的每周三的
14
:
10
和
14
:
44
觸發
|
"0 15 10 ? * MON-FRI"
|
?? |
每個周一、周二、周三、周四、周五的
10
:
15
觸發
|
"0 15 10 15 * ?"
|
?? |
每月
15
號的
10
:
15
觸發
|
"0 15 |
?? |
每月的最后一天的
10
:
15
觸發
|
"0 15 10 ? * |
?? |
每月最后一個周五的
10
:
15
觸發
|
"0 15 10 ? * |
?? |
每月最后一個周五的
10
:
15
觸發
|
"0 15 10 ? * |
?? |
2002
年至
2005
年的每月最后一個周五的
10
:
15
觸發
|
"0 15 10 ? * 6#3"
|
?? |
每月的第三個周五的
10
:
15
觸發
|