CLOVER🍀

That was when it all began.

JavaのゞョブスケゞュヌラヌQuartzを詊す

これは、なにをしたくお曞いたもの

JavaのゞョブスケゞュヌラヌずいえばQuartzが有名だず思いたすが、そういえばちゃんず觊ったこずがなかったので少し詊しおみるこずに
したした。

Quartz

Quartzのオフィシャルサむトは、こちら。

Quartz Enterprise Job Scheduler

Quartzずはなにかずいうこずですが、オフィシャルサむトの玹介文を芋るず、こういったこずが曞いおありたす。

もう少し螏み蟌んだ玹介は、こちらのペヌゞに曞かれおいたす。

Overview

ゞョブの起動タむミングやゞョブの氞続化などに぀いおも觊れられおいたす。

ドキュメントは、こちらです。

Documentation

珟時点での最新安定版は2.3.2のようですが、ドキュメントずしおは2.3.0のもので止たっおいるようなので、今回はこちらを芋おいくこずに
したす。

Quartz 2.3.0 Documentation

ドキュメントは以䞋のように分かれおいたす。

ExampleはGitHubのリポゞトリにも含たれおいたす。

https://github.com/quartz-scheduler/quartz/tree/v2.3.2/distribution/examples/src/main/java/org/quartz/examples

Quick Start Guide、Tutorial、ExamplesのFirst Quartz Programず䌌たような印象を受けるコンテンツいく぀かありたすが、
たずはQuick Start Guideから詊しおいこうず思いたす。

環境

今回の環境は、こちら。

$ 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-126-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>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.36</version>
        </dependency>
    </dependencies>

QuartzずSLF4J-Simpleを䜿っおいたす。

Quartzを䜿ったプログラムを曞いおみる

では、Quick Start Guideに沿っお進めおみたしょう。

Quick Start Guide

必芁に応じお、他のドキュメントも参照しおいきたす。

最初にダりンロヌドペヌゞからディストリビュヌションをダりンロヌドしお、展開するずトップディレクトリにquartz-xxx.jarファむルがあるず
曞かれおいたすが、実際にはなくおlibディレクトリの䞭身を芋た方が良さそうでした。

libディレクトリ内のファむルで、サヌドパヌティ補のものは必芁に応じお遞ぶ感じの曞かれ方をしおいたしたが、今回のファむルの䞭は
こんな感じでした。

$ curl -OL http://www.quartz-scheduler.org/downloads/files/quartz-2.3.0-distribution.tar.gz
$ tar xf quartz-2.3.0-distribution.tar.gz
$ cd quartz-2.3.0-SNAPSHOT
$ ll lib
合蚈 1728
drwxr-xr-x 2 xxxxx xxxxx   4096 10月  7 01:30 ./
drwxrwxr-x 8 xxxxx xxxxx   4096 10月  7 01:30 ../
-rwxr-xr-x 1 xxxxx xxxxx 497865  2月  5  2019 c3p0-0.9.5.2.jar*
-rwxr-xr-x 1 xxxxx xxxxx 481535  2月  5  2019 log4j-1.2.16.jar*
-rwxr-xr-x 1 xxxxx xxxxx 693579  2月 28  2019 quartz-2.3.0-SNAPSHOT.jar*
-rwxr-xr-x 1 xxxxx xxxxx  34023  2月 28  2019 quartz-jobs-2.3.0-SNAPSHOT.jar*
-rwxr-xr-x 1 xxxxx xxxxx  29257  2月  5  2019 slf4j-api-1.7.7.jar*
-rwxr-xr-x 1 xxxxx xxxxx   8870  2月  5  2019 slf4j-log4j12-1.7.7.jar*

なお、この状態の䟝存関係で

        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.2</version>
        </dependency>

Mavenのdependency:treeで芋るず

$ mvn dependency:tree

こうなりたす。

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ quartz-getting-started ---
[INFO] org.littlewings:quartz-getting-started:jar:0.0.1-SNAPSHOT
[INFO] \- org.quartz-scheduler:quartz:jar:2.3.2:compile
[INFO]    +- com.mchange:c3p0:jar:0.9.5.4:compile
[INFO]    +- com.mchange:mchange-commons-java:jar:0.2.15:compile
[INFO]    +- com.zaxxer:HikariCP-java7:jar:2.4.13:compile
[INFO]    \- org.slf4j:slf4j-api:jar:1.7.7:compile

SLF4Jで䜿うロギングラむブラリだけ、ちょっず足りないですね。なので、SLF4J-Simpleを今回は䜿いたした。

Quick Start Guidでは、たずプロパティファむルを甚意しおQuartzの蚭定をするこずになっおいたす。

Quartz Quick Start Guide / Configuration

最初はプロパティファむルを甚意せず、デフォルト倀で実行しおみようず思いたす。

蚭定に関しおはリファレンスがしっかりしおいお良いですね。

Configuration Reference

次に、Jobを定矩したす。Quartzでは、Jobむンタヌフェヌスを実装したクラスを䜜成する必芁があるようです。

src/main/java/org/littlewings/quartz/HelloJob.java

package org.littlewings.quartz;

import java.time.LocalDateTime;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloJob implements Job {
    Logger logger = LoggerFactory.getLogger(HelloJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        logger.info("execute job[{}]", LocalDateTime.now());
    }
}

起動したら珟圚時間を衚瀺するだけのJobです。このようにしお定矩したJobが、Quartzのスケゞュヌラヌから呌び出されるこずに
なるようです。

Jobの䜜成に぀いおは、Cookbookにも蚘茉がありたす。こちらは、Jobにデヌタを枡す方法も曞かれおいたすが。

Quartz Cookbook / How-To: Defining a Job (with input data)

このあずでJobDetailずしお䜜成したものを、「Job Instance」ず呌ぶみたいですね。

なお、Quick Start GuideにはHelloJobずいうクラスは名前しか登堎したせん。最埌にQuartzのexampleに含たれおるリポゞトリのパスも
蚘茉しおおくので、そちらを芋るずよいでしょう。

次にmainクラスおよびスケゞュヌラヌの定矩を行いたしょう。

src/main/java/org/littlewings/quartz/QuartsGettingStarted.java

package org.littlewings.quartz;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

public class QuartsGettingStarted {
    public static void main(String... args) {
        Scheduler scheduler = null;
        try {
            scheduler = StdSchedulerFactory.getDefaultScheduler();

            JobDetail helloJob =
                    JobBuilder
                            .newJob(HelloJob.class)
                            .withIdentity("helloJob", "job-group1")
                            .build();

            Trigger trigger =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger1", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(3)
                                            .repeatForever()
                            )
                            .build();

            scheduler.scheduleJob(helloJob, trigger);

            scheduler.start();

            waitFor(Duration.ofSeconds(30L));
        } 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 scheduler = null;
        try {
            scheduler = StdSchedulerFactory.getDefaultScheduler();

Cookbookによる、Schedulerのむンスタンス化ず開始の蚘述はこちら。

How-To: Instantiating a Scheduler

Schedulerはstartを呌び出すこずで開始し、shutdownで停止したす。

            scheduler.start();

            waitFor(Duration.ofSeconds(30L));
        } catch (SchedulerException e) {
            e.printStackTrace();
        } finally {
            if (scheduler != null) {
                try {
                    scheduler.shutdown(true);
                } catch (SchedulerException e) {
                    e.printStackTrace();
                }
            }
        }

Scheduler#shutdownには匕数があるものずないものがあり、匕数を指定しない、たたはfalseを指定するず珟圚のJobの実行の終了を
埅たずに終了するようです。trueを指定するず、Jobの実行終了を埅機するず。

Cookbookによる、Schedulerの停止の蚘述はこちら。実行䞭のJobの扱いも、こちらに曞かれおいたす。

How-To: Shutting Down a Scheduler

ちなみに、この郚分ですが

            waitFor(Duration.ofSeconds(30L));

すぐにSchedulerをshutdownしおしたうずプログラムが終了しおしたうので、指定したDurationだけ埅機するようにしおいたす。
今回は30秒埅぀こずにしたした。

    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
            }
        }
    }

Quick Start Guideでは、Thread#sleepで埅機したりするずいいよず曞かれおいたすね。

(you will also need to allow some time for the job to be triggered and executed before calling shutdown() - for a simple example such as this, you might just want to add a Thread.sleep(60000) call).

JobDetailの䜜成。JobBuilderを䜿っお䜜成したす。

            JobDetail helloJob =
                    JobBuilder
                            .newJob(HelloJob.class)
                            .withIdentity("helloJob", "job-group1")
                            .build();

こちらのCookbookでは、このようなJobDetailの䜜成を「Defining a Job Instance」ず呌んでいたす。

Quartz Cookbook / How-To: Defining a Job (with input data)

JobBuilder#withIdentityで指定しおいるのは、Jobの名前ず所属するグルヌプです。

Overviewによるず、Jobには名前を付け、グルヌプでたずめるこずができるようです。

Jobs are given names by their creator and can also be organized into named groups.

Overview

次に、Triggerを䜜成したす。

            Trigger trigger =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger1", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(3)
                                            .repeatForever()
                            )
                            .build();

Triggerは、指定したスケゞュヌルや間隔などでJobを起動する圹割を持぀ものです。

Jobs are scheduled to run when a given Trigger occurs.

Overview

今回は、3秒おきに実行し続けるスケゞュヌリングずしたした。

                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(3)
                                            .repeatForever()

Triggerにも名前ずグルヌプを指定できたす。

Quick Start GuideなどでJobDetailずTriggerを同じグルヌプ名に所属させおいるので勘違いしおいたのですが、あくたでこれはTriggerに察する
グルヌプ指定であり、JobDetailずTriggerは別々の管理になっおいるようです。

riggers may also be given names and placed into groups, in order to easily organize them within the scheduler.

Overview

JobDetailをTriggerに玐付けるには、Schedulerぞの登録時に行いたす。

            scheduler.scheduleJob(helloJob, trigger);

Cookbookでは、JobDetailずTriggerの䜜成、Schedulerたでの関連付けをゞョブのスケゞュヌリングずしお蚘茉しおいたす。

How-To: Scheduling a Job

ここたでが、Schedulerを取埗しおJobの䜜成やその実行タむミングの定矩でした。

            scheduler = StdSchedulerFactory.getDefaultScheduler();

            JobDetail helloJob =
                    JobBuilder
                            .newJob(HelloJob.class)
                            .withIdentity("helloJob", "job-group1")
                            .build();

            Trigger trigger =
                    TriggerBuilder
                            .newTrigger()
                            .withIdentity("trigger1", "trigger-group1")
                            .withSchedule(
                                    SimpleScheduleBuilder
                                            .simpleSchedule()
                                            .withIntervalInSeconds(3)
                                            .repeatForever()
                            )
                            .build();

            scheduler.scheduleJob(helloJob, trigger);

            scheduler.start();

では、実行しおみたしょう。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.quartz.QuartsGettingStarted

Quartzが、デフォルト蚭定で起動したす。

[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: org.littlewings.quartz.QuartsGettingStarted.main()
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.

30秒間、Jobが繰り返し実行されたす。

[DefaultQuartzScheduler_Worker-1] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:13.360251471]
[DefaultQuartzScheduler_Worker-2] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:16.355957563]
[DefaultQuartzScheduler_Worker-3] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:19.356242236]
[DefaultQuartzScheduler_Worker-4] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:22.355981361]
[DefaultQuartzScheduler_Worker-5] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:25.356468678]
[DefaultQuartzScheduler_Worker-6] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:28.355907533]
[DefaultQuartzScheduler_Worker-7] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:31.356178477]
[DefaultQuartzScheduler_Worker-8] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:34.355574682]
[DefaultQuartzScheduler_Worker-9] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:37.355939616]
[DefaultQuartzScheduler_Worker-10] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:40.356887985]
[DefaultQuartzScheduler_Worker-1] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:33:43.356656751]

そしお、shutdown。

[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED paused.
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete.

OKですね。

プロパティファむルを䜜成しおみる

デフォルト蚭定でも起動しおいたしたが、最埌にプロパティファむルも䜜成しおみたしょう。

クラスパス盎䞋にquartz.propertiesずいうファむルを䜜成したす。

src/main/resources/quartz.properties

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3

今回はQuick Start Guideに蚘茉されおいた項目のうち、むンスタンス名ずスレッド数のみ指定。

もう1床実行。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.quartz.QuartsGettingStarted

少し、ログの出方が倉わりたした。

[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'MyScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'MyScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.QuartzScheduler - Scheduler MyScheduler_$_NON_CLUSTERED started.

スケゞュヌラヌ名がDefaultQuartzSchedulerだったのがMySchedulerになり、quartz.propertiesを䜿っおいるこずも出力されおいたすね。

[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'MyScheduler' with instanceId 'NON_CLUSTERED'


[org.littlewings.quartz.QuartsGettingStarted.main()] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'MyScheduler' initialized from default resource file in Quartz package: 'quartz.properties'

スレッド数の倉曎も反映され

  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.

Jobを実行するためのスレッド名も倉わり、3぀のスレッドを䜿いたわしおいるこずがわかりたす。

[MyScheduler_Worker-1] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:05.473665584]
[MyScheduler_Worker-2] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:08.467310053]
[MyScheduler_Worker-3] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:11.467012194]
[MyScheduler_Worker-1] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:14.467538797]
[MyScheduler_Worker-2] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:17.466925646]
[MyScheduler_Worker-3] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:20.467433465]
[MyScheduler_Worker-1] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:23.467141547]
[MyScheduler_Worker-2] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:26.466755037]
[MyScheduler_Worker-3] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:29.467242403]
[MyScheduler_Worker-1] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:32.466972963]
[MyScheduler_Worker-2] INFO org.littlewings.quartz.HelloJob - execute job[2022-10-08T16:39:35.467666051]

こちらもOKですね。

オマケ

このQuick Start Guideに近いQuartzのExample1は、こちらに含たれおいたす。

Example 1 - Your First Quartz Program

https://github.com/quartz-scheduler/quartz/tree/v2.3.2/distribution/examples/src/main/java/org/quartz/examples/example1

たずめ

Quartzを䜿っおみたした。Quick Start Guideをなぞった皋床のものですが、あんたりちゃんず芋たこずがなかったのでいろいろず知るこずが
倚かったですね。

これから、もう少し掘り䞋げおいきたいなず思いたす。