quartz 學習筆記

 一個系統裏面經常需要做一些定時任務,比如說定時清空今日得分,或者定時清理臨時文件。簡單的定時任務很容易實現,用線程或者用Timer就可以了,但是始終需要自己寫大量代碼才能實現複雜的需求。

於是便有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(真複雜)
Java代碼 複製代碼 收藏代碼
  1. JobDetail jobDetail = new JobDetail("myJob",               // job name   
  2.                                   sched.DEFAULT_GROUP,   // job group   
  3.                                   DumbJob.class);        // the java class to execute   
  4. Trigger trigger = TriggerUtils.makeDailyTrigger(830);   
  5. trigger.setStartTime(new Date());   
  6. trigger.setName("myTrigger");   
  7. 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
Java代碼 複製代碼 收藏代碼
  1. <bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean">   
  2.   <property name="jobClass" value="example.ExampleJob" />   
  3.   <property name="jobDataAsMap">   
  4.     <map>   
  5.       <entry key="timeout" value="5" />   
  6.     </map>   
  7.   </property>   
  8. </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
Java代碼 複製代碼 收藏代碼
  1. <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">   
  2.   <property name="targetObject" ref="exampleBusinessObject" />   
  3.   <property name="targetMethod" value="doIt" />   
  4. </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類
Java代碼 複製代碼 收藏代碼
  1.   public class DummyJob implements Job {   
  2. ublic void execute(JobExecutionContext context)   
  3.     throws JobExecutionException {   
  4. Job job = (Job) context.getMergedJobDataMap().get("methodInvoker");   
  5. if (job != null) {   
  6.     job.execute(context);   
  7. }   
  8.   
  9.   }  
    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類,並且調用它

接下來是戲玉了
Java代碼 複製代碼 收藏代碼
  1. Map<String, Job> jobMap = context.getBeansOfType(Job.class);   
  2. for (Map.Entry<String, Job> entry : jobMap.entrySet()) {   
  3.     String taskName = entry.getKey();   
  4.     String cronExpression = props.getProperty(taskName);   
  5.     if (cronExpression == null) {   
  6.         logger.warn("[{}] don't have a cronExpression", taskName);   
  7.         continue;   
  8.     }   
  9.   
  10.     try {   
  11.         Trigger trigger = new CronTrigger(taskName + "Trigger"null,   
  12.                 cronExpression);   
  13.         JobDetail jobDetail = new JobDetail(taskName + "Job"null,   
  14.                 DummyJob.class);   
  15.         jobDetail.getJobDataMap()   
  16.                 .put("methodInvoker", entry.getValue());   
  17.         scheduler.scheduleJob(jobDetail, trigger);   
  18.     } catch (ParseException e) {   
  19.         logger.error("", e);   
  20.     } catch (SchedulerException e) {   
  21.         logger.error("", e);   
  22.     }   
  23. }  
		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配置。
Java代碼 複製代碼 收藏代碼
  1. JobDetail jobDetail = new JobDetail(taskName + "Job"null,   
  2.         DummyJob.class);   
  3. jobDetail.getJobDataMap()   
  4.         .put("methodInvoker", entry.getValue());  
				JobDetail jobDetail = new JobDetail(taskName + "Job", null,
						DummyJob.class);
				jobDetail.getJobDataMap()
						.put("methodInvoker", entry.getValue());

這兩句是關鍵

於是,Quartz變得更sexy了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章