Javaはバッチが弱いとは良く言われますが、安易に他の言語+cronを使ってしまうと、アーキテクチャとして散らかってしまいますし、システムのポータビリティも良くありません。メンテナンスコストや運用マニュアルのページ数も、大幅に増えてしまいます。
ここでは、設計書は設定ファイルにまとめたいので、Spring+Quartzを使ってcron機能を実現します。バッチ処理もwarファイルに閉じ込めてしまおうという発想。
もちろん、処理するデータ量によっては、オンラインアプリケーションに組み込むべきではない場合が多々あるので、そのあたりは慎重に。
マニュアルは以下のとおり。
・Quartz
・Spring Framework - Chapter 23
環境設定の注意点は、commons-collection.jarは、最新(Quartzに付属)のものを使ってください。NetBeansの場合、JSFライブラリに古いcommons-collectionが残っているので入れ替えが必要です。
はじめての・・
基本は次のようになります。ジョブを実装したクラスを作成して、applicationContext.xmlに実行スケジュールを設定します。
//ジョブの本体
package job;
public class SomeJob extends QuartzJobBean {
protected void executeInternal(JobExecutionContext ctx)
throws JobExecutionException {
System.out.println(this + " executed at : " + new Date());
// doSomeJob();
}
}
<bean name="someJob" class="org.springframework
.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="job.SomeJob" />
</bean>
<bean id="someJobTrigger" class="org.springframework
.scheduling.quartz.CronTriggerBean">
<!-- 毎日朝4時にsomeJobを実行 -->
<property name="jobDetail" ref="someJob" />
<property name="cronExpression" value="0 0 4 * * ?" />
</bean>
<bean class="org.springframework
.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list><ref bean="someJobTrigger" /></list>
</property>
</bean>
Exceptionハンドリング
ジョブの完了やエラーをハンドリングするには、JobListnerを作成して複数登録できます。
ほかにも、TriggerListenerやSchedulerListenerがあります。
リカバリや管理者通知など、運用のためには欠かせない機能でしょう。
//ジョブのコールバック
public class SomeJobListener implements JobListener {
public String getName() {
return "someJobListenerName";//リスナ登録名
}
/** 実行終了時のコールバック */
public void jobWasExecuted(JobExecutionContext ctx,
JobExecutionException exception) {
if( exception != null ){
System.out.println("ERROR");
//doSomeRecovery();
}else{
System.out.println("SUCCESS");
}
}
/** 実行前のコールバック */
public void jobToBeExecuted(JobExecutionContext ctx) {
System.out.println("To Be Execute");
//doSomePrepare();
}
/** 拒否されたとき?のコールバック */
public void jobExecutionVetoed(JobExecutionContext ctx) {
//?
}
}
<bean name="someJob" class="org.springframework
.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="job.SomeJob" />
<property name="jobListenerNames">
<list><value>someJobListenerName</value></list>
</property>
</bean>
<!-- コールバックのインスタンス -->
<bean id="someJobListener" class="job.SomeJobListener"/>
<bean id="someJobTrigger" class="org.springframework
.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="someJob" />
<property name="cronExpression" value="0 0 4 * * ?" />
</bean>
<bean class="org.springframework
.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list><ref bean="someJobTrigger"/></list>
</property>
<property name="jobListeners">
<list><ref bean="someJobListener"/></list>
</property>
</bean>
POJOをジョブにする
任意のPOJOをジョブとして利用したい場合は、JobDetailBeanの代わりにMethodInvokingJobDetailFactoryBeanを使います。
既存のサービスメソッドを再利用したいときは便利。いかにもSpringらしい機能ですが、正確なExceptionハンドリングが必要な場合は、JobDetailBeanを使った方が無難かもしれません。
package service;
public class SomeService {
public void doSomething() throws Exception {
System.out.println( this + "が実行中" + new Date() );
}
}
<bean id="someService" class="service.SomeService"/>
<bean id="someJob" class="org.springframework.scheduling
.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="someService" />
<property name="targetMethod" value="doSomething" />
<property name="jobListenerNames">
<list><value>someJobListenerName</value></list>
</property>
</bean>