これは、なにをしたくて書いたもの?
Quartzを使っていて、指定した時間に1度だけ起動するJobを定義したいなと思いまして。
指定した間隔で起動し続けるJobばかり扱っていたので、どうしたらいいんだろうと思ったのですが、割と単純でした。
1回だけ起動するJobを定義する
TriggerBuilder#withSchedule
でどう表現したらいいんだろうとか思っていたのですが、Cookbookにそのまま書いてありました。
正確には、Trigger
でコントロールするわけですが。
以下が登録したらすぐに起動し、繰り返し実行は行わないTrigger
です。
// Define a Trigger that will fire "now", and not repeat Trigger trigger = newTrigger() .withIdentity("trigger1", "group1") .startNow() .build();
TriggerBuilder#withSchedule
で起動スケジュールを指定しなければ良いのです。
あとは、TutorialのTriggerに関する記述を見ると、startTime
で最初にTriggerが有効になるタイミングを説明しています。
The “startTime” property indicates when the trigger’s schedule first comes into affect. The value is a java.util.Date object that defines a moment in time on a given calendar date. For some trigger types, the trigger will actually fire at the start time, for others it simply marks the time that the schedule should start being followed. This means you can store a trigger with a schedule such as “every 5th day of the month” during January, and if the startTime property is set to April 1st, it will be a few months before the first firing.
CookbookのドキュメントではTriggerBuilder#startNow
を使っているので、開始時間が「即時」ですね。ちなみにTriggerBuilder#startNow
を
使う場合は、明示的に書かなくてもデフォルト値がそもそも同じ意味になっています。
TriggerBuilder#startAt
を使うことで、指定の時間から有効になるTrigger
を定義することができます。今回は、こちらが焦点です。
それから、Triggerの期限も指定できるようですね。
The “endTime” property indicates when the trigger’s schedule should no longer be in effect. In other words, a trigger with a schedule of “every 5th day of the month” and with an end time of July 1st will fire for it’s last time on June 5th.
では、試していきたいと思います。
環境
今回の環境は、こちら。
$ java --version openjdk 17.0.4 2022-07-19 OpenJDK Runtime Environment (build 17.0.4+8-Ubuntu-120.04) OpenJDK 64-Bit Server VM (build 17.0.4+8-Ubuntu-120.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 17.0.4, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-131-generic", arch: "amd64", family: "unix"
準備
Maven依存関係など。
<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency> </dependencies>
ログ出力にはLogbackを使用するので、設定ファイルも用意しておきます。
src/main/resources/logback.xml
<?xml version="1.0" encoding="utf-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration>
アプリケーションを作成する
まずは、Job
を定義します。
src/main/java/org/littlewings/quartz/oneshot/PrintMessageJob.java
package org.littlewings.quartz.oneshot; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PrintMessageJob implements Job { Logger logger = LoggerFactory.getLogger(PrintMessageJob.class); @Override public void execute(JobExecutionContext context) throws JobExecutionException { logger.info("[{} / {}] Hello Job!!", context.getJobDetail().getKey(), context.getTrigger().getKey()); } }
実行したらメッセージを表示するJob
ですが、この時にJobDetail
のキーとTrigger
のキーを出力するようにしました。
main
クラス。
src/main/java/org/littlewings/quartz/oneshot/App.java
package org.littlewings.quartz.oneshot; import java.time.Duration; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.Set; import java.util.concurrent.TimeUnit; import org.quartz.JobBuilder; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.impl.StdSchedulerFactory; public class App { public static void main(String... args) { Scheduler scheduler = null; try { scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); JobDetail printMessageJob = JobBuilder .newJob(PrintMessageJob.class) .withIdentity("printMessageJob1", "job-group1") .build(); Trigger triggerStartNow1 = TriggerBuilder .newTrigger() .withIdentity("triggerStartNow1", "trigger-group1") .build(); Trigger triggerStartNow2 = TriggerBuilder .newTrigger() .withIdentity("triggerStartNow2", "trigger-group1") .startNow() .build(); Trigger triggerStartDelay1 = TriggerBuilder .newTrigger() .withIdentity("triggerStartDelay1", "trigger-group1") .startAt(Date.from(LocalDateTime.now().plusSeconds(5L).atZone(ZoneId.of("Asia/Tokyo")).toInstant())) .build(); Trigger triggerStartDelay2 = TriggerBuilder .newTrigger() .withIdentity("triggerStartDelay2", "trigger-group1") .startAt(Date.from(LocalDateTime.now().plusSeconds(10L).atZone(ZoneId.of("Asia/Tokyo")).toInstant())) .build(); Trigger triggerStartBeforeTime = TriggerBuilder .newTrigger() .withIdentity("triggerStartBeforeTime", "trigger-group1") .startAt(Date.from(LocalDateTime.now().minusSeconds(60L).atZone(ZoneId.of("Asia/Tokyo")).toInstant())) .build(); scheduler.scheduleJob( printMessageJob, Set.of( triggerStartNow1, triggerStartNow2, triggerStartDelay1, triggerStartDelay2, triggerStartBeforeTime ), true ); waitFor(Duration.ofSeconds(20L)); } catch (SchedulerException e) { e.printStackTrace(); } finally { if (scheduler != null) { try { scheduler.shutdown(true); } catch (SchedulerException e) { e.printStackTrace(); } } } } static void waitFor(Duration waitTime) { LocalDateTime startTime = LocalDateTime.now(); while (ChronoUnit.SECONDS.between(startTime, LocalDateTime.now()) < waitTime.toSeconds()) { try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { // no-op } } } }
とりあえず、Scheduler
を開始します。
scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start();
JobDetail
を作成。
JobDetail printMessageJob = JobBuilder .newJob(PrintMessageJob.class) .withIdentity("printMessageJob1", "job-group1") .build();
このJobDetail
に関連付けるTrigger
を作成していきます。
まずは、即時起動するTrigger
。
Trigger triggerStartNow1 = TriggerBuilder .newTrigger() .withIdentity("triggerStartNow1", "trigger-group1") .build(); Trigger triggerStartNow2 = TriggerBuilder .newTrigger() .withIdentity("triggerStartNow2", "trigger-group1") .startNow() .build();
違いはTriggerBuilder#startNow
を呼んでいるかどうかですが、startTime
のデフォルト値はnew Date()
なので、開始タイミングという意味では
両者は等価です。
Scheduler
に登録次第、すぐに開始します。
続いて、TriggerBuilder#startAt
で指定した時間に起動するTrigger
。今回は、5秒後と10秒後に起動するTrigger
を用意。
Trigger triggerStartDelay1 = TriggerBuilder .newTrigger() .withIdentity("triggerStartDelay1", "trigger-group1") .startAt(Date.from(LocalDateTime.now().plusSeconds(5L).atZone(ZoneId.of("Asia/Tokyo")).toInstant())) .build(); Trigger triggerStartDelay2 = TriggerBuilder .newTrigger() .withIdentity("triggerStartDelay2", "trigger-group1") .startAt(Date.from(LocalDateTime.now().plusSeconds(10L).atZone(ZoneId.of("Asia/Tokyo")).toInstant())) .build();
あと、過去の時間にするとどうなるかというのも確認してみましょう。
Trigger triggerStartBeforeTime = TriggerBuilder .newTrigger() .withIdentity("triggerStartBeforeTime", "trigger-group1") .startAt(Date.from(LocalDateTime.now().minusSeconds(60L).atZone(ZoneId.of("Asia/Tokyo")).toInstant())) .build();
(たぶんMisfire扱いになっているからだと思うのですが)答えとしては、即時起動になります。
これらのTrigger
を、一括でJobDetail
に関連付けてスケジューリング。
scheduler.scheduleJob(
printMessageJob,
Set.of(
triggerStartNow1,
triggerStartNow2,
triggerStartDelay1,
triggerStartDelay2,
triggerStartBeforeTime
),
true
);
終了まで、20秒待ちます。
waitFor(Duration.ofSeconds(20L));
確認する
では、動作確認してみましょう。
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.quartz.oneshot.App
結果はこちら。
2022-11-03 17:03:06.440 [DefaultQuartzScheduler_Worker-3] INFO o.l.quartz.oneshot.PrintMessageJob - [job-group1.printMessageJob1 / trigger-group1.triggerStartBeforeTime] Hello Job!! 2022-11-03 17:03:06.440 [DefaultQuartzScheduler_Worker-1] INFO o.l.quartz.oneshot.PrintMessageJob - [job-group1.printMessageJob1 / trigger-group1.triggerStartNow1] Hello Job!! 2022-11-03 17:03:06.440 [DefaultQuartzScheduler_Worker-2] INFO o.l.quartz.oneshot.PrintMessageJob - [job-group1.printMessageJob1 / trigger-group1.triggerStartNow2] Hello Job!! 2022-11-03 17:03:11.435 [DefaultQuartzScheduler_Worker-4] INFO o.l.quartz.oneshot.PrintMessageJob - [job-group1.printMessageJob1 / trigger-group1.triggerStartDelay1] Hello Job!! 2022-11-03 17:03:16.434 [DefaultQuartzScheduler_Worker-5] INFO o.l.quartz.oneshot.PrintMessageJob - [job-group1.printMessageJob1 / trigger-group1.triggerStartDelay2] Hello Job!!
TriggerBuilder#startAt
で過去時間を指定したものは即時起動、
2022-11-03 17:03:06.440 [DefaultQuartzScheduler_Worker-3] INFO o.l.quartz.oneshot.PrintMessageJob - [job-group1.printMessageJob1 / trigger-group1.triggerStartBeforeTime] Hello Job!!
未指定、またはTriggerBuilder#startNow
を指定したものも即時起動、
2022-11-03 17:03:06.440 [DefaultQuartzScheduler_Worker-1] INFO o.l.quartz.oneshot.PrintMessageJob - [job-group1.printMessageJob1 / trigger-group1.triggerStartNow1] Hello Job!! 2022-11-03 17:03:06.440 [DefaultQuartzScheduler_Worker-2] INFO o.l.quartz.oneshot.PrintMessageJob - [job-group1.printMessageJob1 / trigger-group1.triggerStartNow2] Hello Job!!
TriggerBuilder#startAt
で5秒、10秒遅らせて起動させるようにしたもの。
2022-11-03 17:03:11.435 [DefaultQuartzScheduler_Worker-4] INFO o.l.quartz.oneshot.PrintMessageJob - [job-group1.printMessageJob1 / trigger-group1.triggerStartDelay1] Hello Job!! 2022-11-03 17:03:16.434 [DefaultQuartzScheduler_Worker-5] INFO o.l.quartz.oneshot.PrintMessageJob - [job-group1.printMessageJob1 / trigger-group1.triggerStartDelay2] Hello Job!!
わかりやすい結果になりました。
これで、今回確認したいことはOKです。
まとめ
Quartzで、指定した時間に1回だけ起動するJobを作成してみました。
割とSimpleTrigger
やCronTrigger
などに目が行きがちだったので、盲点といえば盲点でしたね。
使い方として覚えておきましょう。