Job Scheduling in Javaを読む(ってか打てよ!!)

スケジューリングされたタスクの例とかJavaのTimerを使った場合のサンプル等…そして本題へ

  • >Beyond the Ordinary

 とりあえずQuartzの利点が紹介されている。ダイナミックなトリガ、PersistentなJob等。どうやらレポートツールを作っていきながらQuartzを解説していくようだ。まず一発目のサンプルコードは、

package net.nighttale.scheduling;

import org.quartz.*;

public class QuartzReport implements Job {

  public void execute(JobExecutionContext cntxt)
    throws JobExecutionException {
      System.out.println(
        "Generating report - " +
     cntxt.getJobDetail().getJobDataMap().get("type")
      );
      //TODO Generate report
  }

  public static void main(String[] args) {
    try {
      SchedulerFactory = schedFact 
        new org.quartz.impl.StdSchedulerFactory();
      Scheduler sched = schedFact.getScheduler();
      sched.start();
      JobDetail jobDetail =
        new JobDetail(
          "Income Report",
          "Report Generation",
          QuartzReport.class
        );
      jobDetail.getJobDataMap().put(
                                "type",
                                "FULL"
                               );
      CronTrigger trigger = new CronTrigger(
        "Income Report",
        "Report Generation"
      );
      trigger.setCronExpression(
        "0 0 12 ? * SUN"
      );
      sched.scheduleJob(jobDetail, trigger);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

てな感じ。っていきなりCronTriggerっすか…ってのもあるけどサンプルなんで'='が軒並み抜けてるんだ?一応修正しておく。さらりとでもTutorialをやったお陰か、というかサンプルが単純なだけだろうけどさらっと読める。しかしcronは使ったことないのですぐにルールの書式をわすれちゃうなぁ。

で、Jobの説明はすっ飛ばす。差し当たりJobの実行中にパラメータを変更したいかどうかによってステートフルかステートレスなJobか決めなさい、と。ステートフルにする場合はStatefulJobってなマーカーインタフェースをimplementsしなさいよ、と。ステートフルだとJobの同時実行はできませんよ、と。だってJobの実行中にパラメータ変更されちゃうかもしれないんだもの、と。たぶんTutorialでも説明があったのだろうけど、すっかり忘れてる。かと言って日記を確認するのもメンドい。

そして、このサンプルを動かすにはクラスパス上にquartz.propertiesが必要です、と。それ以外のファイル名を使う場合はStdSchedulerFactoryのコンストラクタにその名前を渡してあげなさい、と。んで、これが必要最小限の設定。

 #
 # Configure Main Scheduler Properties 
 #
 org.quartz.scheduler.instanceName = TestScheduler
 org.quartz.scheduler.instanceId = one
 #
 # Configure ThreadPool 
 #
 org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
 org.quartz.threadPool.threadCount =  5
 org.quartz.threadPool.threadPriority = 4
 #
 # Configure JobStore 
 #
 org.quartz.jobStore.misfireThreshold = 5000
 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

なるほどこれもさらっと読める。

そうか、JobStoreにはJobとかTriggerの情報が格納されるのか!(って考えるまでもなくすぐわかるだろう、普通…)JDBCJobStoreの場合の追加設定の説明があるが既知なので飛ばす。ちなみにサンプルではPostgreSQLを使用している。tablePrefixは同一DB内で複数のSchedulerを使用する場合にテーブルを区別する場合に設定するといいらしい(たぶんそんな場合は無いと思う)。

そして、Quartzのbeautyな所は設定ファイルを変更するだけでソースを一行も変えることなくJobStoreとかが切り替えられちゃうところなんだってさぁ。

 listenerがいいぜ!と言っている。例えば、レポートの作成中にエラーが発生したら、JobListenerで拾って開発チームにメールなりを送ったりなんかするとエレガントなんじゃない、と。

そして、Triggerのmiss-fire(Schedulerが落ちた場合とか)したときの挙動をsetMisfireInstruction() で設定しなさい、と。設定できるのは

 Trigger.INSTRUCTION_NOOP: does nothing. 
 Trigger.INSTRUCTION_RE_EXECUTE_JOB: executes the job immediately. 
 Trigger.INSTRUCTION_DELETE_TRIGGER: deletes the misfired trigger. 
 Trigger.INSTRUCTION_SET_TRIGGER_COMPLETE: declares the trigger completed. 
 Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE: 
  declares all triggers for that job completed. 
 Trigger.MISFIRE_INSTRUCTION_SMART_POLICY: 
  chooses the best fit misfire instruction for a particular Trigger implementation. 

てな値。自分で便利なのを作ってもいいし、TriggerListenerを使っていろいろやってみなさい、と。

でもってlistenerのサンプル。

package net.nighttale.scheduling;

import org.quartz.*;

public class MyJobFailedListener implements JobListener {
  public String getName() {
    return "FAILED JOB";
  }

  public void jobToBeExecuted(JobExecutionContext arg0) {}

  public void jobWasExecuted(
    JobExecutionContext context,JobExecutionException exception) {

    if (exception != null) {
      System.out.println("Report generation error");
      // TODO notify development team
    }	
  }
}

しつこいようだが、さらっと読める。そして登録はこんな感じ。

 sched.addGlobalJobListener(new MyJobFailedListener());

でもってGlovalではなく特定のJobに登録する場合は、

 sched.addJobListener(new MyJobFailedListener());
 jobDetail.addJobListener("FAILED JOB");

てな感じ。そしてそしてこれを試すには、execute()内で

 throw new JobExecutionException();

ってする。でもってjobWasExecuted()が呼ばれてexceptionに例外が設定されています、と。最後に、listenerをたくさん登録するとパフォーマンスが落ちるから気をつけてね、とおっしゃってます。

また、Quartzを拡張するんなら org.quartz.spi.SchedulerPlugin を実装したプラグインを作成しなさい、と。デフォルトでshutdownHookってのがあります、と。これを使う場合は、

 org.quartz.plugin.shutdownHook.class = 
    org.quartz.plugins.management.ShutdownHookPlugin
 org.quartz.plugin.shutdownHook.cleanShutdown = true

ってのを設定ファイルに追加する。でも何してくれるのかはわからない、調べるのもメンドい。たぶんJVMのシャットダウンをフックするからリソースの開放とかこれでやってねとかって類のものだろう。

  • >Adaptable in Every Environment

 ・RMI
  差し当たり必要ないので無視(結構説明量が多いんですけど)

 ・Web and Enterprise
  Webアプリを開発しているとして、君はどこでSchedulerを起動しますか?と。
 Quartzではorg.quartz.ee.servlet.QuartzInitializerServletてのを用意してます、と。
 web.xmlはこんな感じ。


  
   QuartzInitializer
  
  
   Quartz Initializer Servlet
  
  
   org.quartz.ee.servlet.QuartzInitializerServlet
  
  
   1
  

 JobからEJBをコールするにはorg.quartz.ee.ejb.EJBInvokerJob を使うらしいが、EJB
 きっと使わないので無視。

 ・Web Services
  Webサービスには組み込みで対応はしてないけど、プラグインを使えばいいんだって。
 で、Jakarta XML-RPC ライブラリが必要なのと、設定ファイルに

 org.quartz.plugin.xmlrpc.class = org.quartz.plugins.xmlrpc.XmlRpcPlugin
 org.quartz.plugin.xmlrpc.port = 8080

 を記述する。プラグインhttp://www.nighttale.net/OpenSource/QuartzXML-RPCplugin.htmlで配布されている。

ってなことでさらっと読破。結局参考になったのは後半部分だけかなぁ。チュートリアル読んでなかったら結構つまづいてたと思う。現に前読んだときはわけわからんくてあきらめていたわけですし。でも一体いつになったらコーディングするんだろう…