於是便有Quartz。不過,Quartz太久沒有更新了,而且它太複雜。由於我的系統是基於Spring構建的,所以我希望能使用Spring支持的scheduling類庫,可惜Spring只支持commonj和Quartz,正確來說,在Java界,並沒有別的scheduling類庫了,而commonj只是一個interface,沒有具體的實現,似乎在Weblogic之類的裏面有實現。
當然,也有另外一個選擇,也是輕量級的腳本語言常用的做法,就是使用Linux的crontable,可以實現比較複雜的定時。不過,腳本語言調用數據庫並不是很方便(應該說我們的團隊技術累積上的問題),如果用crontable啓動Java,每次啓動的成本又比較高。
在評估過各種方案之後,我還是選擇了使用Quartz,首先從Spring的輔助類開始入手吧。
題外話,在一個集羣的環境裏面(也就是多個Tomcat的環境下),定時任務應該是獨立的應用,也就是不應該在每一個Tomcat裏面都啓動Quartz或者定時線程。另外,在Tomcat的應用裏面,也是儘量不要使用線程,有可能一點點小錯誤就會導致整個Tomcat崩潰(其實我們還是使用很多的,呵呵)。
根據Quartz的使用行爲,一個任務我們至少需要一個Job、一個JobDetail、一個Trigger(真複雜)
- JobDetail jobDetail = new JobDetail("myJob", // job name
- sched.DEFAULT_GROUP, // job group
- DumbJob.class); // the java class to execute
- Trigger trigger = TriggerUtils.makeDailyTrigger(8, 30);
- trigger.setStartTime(new Date());
- trigger.setName("myTrigger");
- sched.scheduleJob(jobDetail, trigger);
JobDetail jobDetail = new JobDetail("myJob", // job name
sched.DEFAULT_GROUP, // job group
DumbJob.class); // the java class to execute
Trigger trigger = TriggerUtils.makeDailyTrigger(8, 30);
trigger.setStartTime(new Date());
trigger.setName("myTrigger");
sched.scheduleJob(jobDetail, trigger);
首先!!我在這裏要明確一個事情。Job類是沒有狀態的!!
這是什麼概念呢,就是說,你實現的一個Job(例如上面的代碼的DumbJob),並不是由你自己new出來的,留意一下new JobDetail的代碼,傳入的參數是DumbJob.class,而不是一個具體的job實例。Quartz幫你吧Job new一份出來,並且調用相應的接口,並沒有別的功能。
這裏會帶來一個什麼問題呢,我們先來看看Spring的輔助類。
Spring有兩個輔助類可以產生JobDetail類,需要留意的是,Spring並不輔助產生Job類,也就是Spring認爲Job類不需要管理。
我們先看看第一個,JobDetailBean
- <bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean">
- <property name="jobClass" value="example.ExampleJob" />
- <property name="jobDataAsMap">
- <map>
- <entry key="timeout" value="5" />
- </map>
- </property>
- </bean>
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="example.ExampleJob" />
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5" />
</map>
</property>
</bean>
不知道大家有沒有看出問題在哪裏。property jobClass是一個類名,並不是一個實例名!也就是跟Quartz的調用一樣,是Quartz負責幫你new一個example.ExampleJob類出來,也就是說你不能對Job類進行任何形式的注入(IOC),比如說,我們的example.ExampleJob是一個DAO,需要傳入DataSource進行DB操作,沒轍。
因此,Spring提供了另外一個JobDetail輔助類MethodInvokingJobDetailFactoryBean
- <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
- <property name="targetObject" ref="exampleBusinessObject" />
- <property name="targetMethod" value="doIt" />
- </bean>
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exampleBusinessObject" />
<property name="targetMethod" value="doIt" />
</bean>
你可以留意到,property targetObject是一個ref,指向的是一個常規的Spring管理的Bean。
但是!
MethodInvokingJobDetailFactoryBean很不友好。首先,它是通過反射調用的,而不是Interface,因此我們必須要看了Spring的xml才能知道誰被調用了,你還可能會寫一大堆property targetMethod=doIt,而且Job Interface是會傳入一個JobExecutionContext,這個被miss了。
其次,如果我們需要大量的Job的話(因爲我就是做一個專門用來定時的應用),Spring的配置文件會變得非常臃腫,我希望Job和JobDetail不需要Spring專門管理,只要他是一個Spring管理的Bean,並且實現了Job這個接口就ok了。
這裏補充一個事情,我們跳過了Trigger的部分,每一個JobDetail必須配備一個相應的Trigger,因此配置文件是你之前想象中的兩倍那麼大,而且你還得給每一個Bean命名一個ID,而這個類你以後都不會用到。
我的目標是:
1、只要是實現了Job接口的Spring管理的Bean,自動加入scheduling,根本不用關心JobDetail的存在,也不會有注入的問題
2、所有Job均使用CronTrigger,並且通過配置文件設定Cron Expressions
通過研究MethodInvokingJobDetailFactoryBean和Quartz的代碼,我明白到JobDetail是有狀態的,而MethodInvokingJobDetailFactoryBean正是利用這點來實現具體效果的,於是便有了我一下這些輔助代碼
首先
,需要一個DummyJob,由於Quartz的主入口始終是Job類
- public class DummyJob implements Job {
- ublic void execute(JobExecutionContext context)
- throws JobExecutionException {
- Job job = (Job) context.getMergedJobDataMap().get("methodInvoker");
- if (job != null) {
- job.execute(context);
- }
- }
public class DummyJob implements Job {
public void execute(JobExecutionContext context)
throws JobExecutionException {
Job job = (Job) context.getMergedJobDataMap().get("methodInvoker");
if (job != null) {
job.execute(context);
}
}
}
jobDataMap就是JobDetail存儲狀態的地方,DummyJob唯一要做的就是,知道實際的Job類,並且調用它
接下來是戲玉了
- Map<String, Job> jobMap = context.getBeansOfType(Job.class);
- for (Map.Entry<String, Job> entry : jobMap.entrySet()) {
- String taskName = entry.getKey();
- String cronExpression = props.getProperty(taskName);
- if (cronExpression == null) {
- logger.warn("[{}] don't have a cronExpression", taskName);
- continue;
- }
- try {
- Trigger trigger = new CronTrigger(taskName + "Trigger", null,
- cronExpression);
- JobDetail jobDetail = new JobDetail(taskName + "Job", null,
- DummyJob.class);
- jobDetail.getJobDataMap()
- .put("methodInvoker", entry.getValue());
- scheduler.scheduleJob(jobDetail, trigger);
- } catch (ParseException e) {
- logger.error("", e);
- } catch (SchedulerException e) {
- logger.error("", e);
- }
- }
Map<String, Job> jobMap = context.getBeansOfType(Job.class);
for (Map.Entry<String, Job> entry : jobMap.entrySet()) {
String taskName = entry.getKey();
String cronExpression = props.getProperty(taskName);
if (cronExpression == null) {
logger.warn("[{}] don't have a cronExpression", taskName);
continue;
}
try {
Trigger trigger = new CronTrigger(taskName + "Trigger", null,
cronExpression);
JobDetail jobDetail = new JobDetail(taskName + "Job", null,
DummyJob.class);
jobDetail.getJobDataMap()
.put("methodInvoker", entry.getValue());
scheduler.scheduleJob(jobDetail, trigger);
} catch (ParseException e) {
logger.error("", e);
} catch (SchedulerException e) {
logger.error("", e);
}
}
從Spring context裏面讀取所有實現了Job的類遍歷,props是從文件裏面讀取相應的cronExpression配置。
- JobDetail jobDetail = new JobDetail(taskName + "Job", null,
- DummyJob.class);
- jobDetail.getJobDataMap()
- .put("methodInvoker", entry.getValue());
JobDetail jobDetail = new JobDetail(taskName + "Job", null,
DummyJob.class);
jobDetail.getJobDataMap()
.put("methodInvoker", entry.getValue());
這兩句是關鍵
於是,Quartz變得更sexy了