これは、なにをしたくて書いたもの?
Jakarta Contexts and Dependency Injection(CDI)のコンテナ起動・終了時になにか処理をしたいと思った場合に、どうしたらいいのか
よく知らなかったので見ておくことにしました。
ちなみにJakarta Servletの場合はこちらですね。
Jakarta ServletのServletContainerInitializerを試す - CLOVER🍀
@Initialized/@BeforeDestroyed/@DestroyedかStartup/Shutdownか
調べてみると、やり方としては2つありそうです。
ひとつは@Initialized、@BeforeDestroyed、@Destroyedアノテーションを使って、あるスコープのコンテキストの初期化/破棄イベントの
通知を受け取ること。
それぞれコンテキストの初期化後、破棄前、破棄後、ですね。
Initialized (Jakarta Context Dependency Injection API)
BeforeDestroyed (Jakarta Context Dependency Injection API)
Destroyed (Jakarta Context Dependency Injection API)
ここでスコープをApplicationScopedにすると、実質的にアプリケーションの起動・終了時に紐付けることができます。
このやり方はQuarkusのガイドにも比較のために載っていますね。
もうひとつはStartupおよびShutdownイベントを受け取る方法です。
Startupは、@Initialized(ApplicationScope.class)の後に発生します。
This event is fired after the event with qualifier @Initialized(ApplicationScope.class) but before processing requests.
Startup (Jakarta Context Dependency Injection API)
Shutdownは@BeforeDestroyed(ApplicationScoped.class)の発生よりも遅くはない、とされています。
This event is fired during CDI container shutdown but not later than the event with qualifier @BeforeDestroyed(ApplicationScoped.class).
Shutdown (Jakarta Context Dependency Injection API)
ちなみにどちらのイベントも@Observesアノテーションで通知を受け取るのですが、複数のリスナーがある場合は@Priorityアノテーションで
優先順位付けできることが書かれています。
Observer methods for this event are encouraged to specify @Priority to determine ordering with lower priority numbers being recommended for platform/framework/library integration and higher numbers for user applications.
Priority (Jakarta Annotations API 2.1.1 API)
とりあえず、簡単に試してみることにします。
環境
今回の環境はこちら。
$ java --version openjdk 21.0.5 2024-10-15 OpenJDK Runtime Environment (build 21.0.5+11-Ubuntu-1ubuntu124.04) OpenJDK 64-Bit Server VM (build 21.0.5+11-Ubuntu-1ubuntu124.04, mixed mode, sharing) $ mvn --version Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.5, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-49-generic", arch: "amd64", family: "unix"
準備
Maven依存関係などは、以下のように定義。
<properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-bom</artifactId> <version>10.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>jakarta.ws.rs</groupId> <artifactId>jakarta.ws.rs-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>jakarta.enterprise</groupId> <artifactId>jakarta.enterprise.cdi-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>jakarta.inject</groupId> <artifactId>jakarta.inject-api</artifactId> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>ROOT</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.4.0</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <plugin> <groupId>org.wildfly.plugins</groupId> <artifactId>wildfly-maven-plugin</artifactId> <version>5.0.1.Final</version> <executions> <execution> <id>package</id> <goals> <goal>package</goal> </goals> </execution> </executions> <configuration> <overwrite-provisioned-server>true</overwrite-provisioned-server> <discover-provisioning-info> <version>34.0.1.Final</version> </discover-provisioning-info> </configuration> </plugin> </plugins> </build>
実行はWildFly 34.0.1.Finalをプロビジョニングして行います。
サンプルコード
@Initialized、@BeforeDestroyed、@Destroyedアノテーションを使って、@ApplicationScopedコンテキストのイベントを受け取る
リスナー。
src/main/java/org/littlewings/cdi/ApplicationScopedLifecycleListener.java
package org.littlewings.cdi; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.BeforeDestroyed; import jakarta.enterprise.context.Destroyed; import jakarta.enterprise.context.Initialized; import jakarta.enterprise.event.Observes; @ApplicationScoped public class ApplicationScopedLifecycleListener { void onInitialized(@Observes @Initialized(ApplicationScoped.class) Object initialized) { System.out.printf("[%s] initialized, event = %s%n", getClass().getSimpleName(), initialized); } void onBeforeDestroyed(@Observes @BeforeDestroyed(ApplicationScoped.class) Object beforeDestroyed) { System.out.printf("[%s] before destroyed, event = %s%n", getClass().getSimpleName(), beforeDestroyed); } void onDestroyed(@Observes @Destroyed(ApplicationScoped.class) Object destroyed) { System.out.printf("[%s] destroyed, event = %s%n", getClass().getSimpleName(), destroyed); } }
ひとまず受け取った内容を標準出力に書き出しているだけです。
ちなみに、これ自体もCDI管理Beanです。
もうひとつはStartupおよびShutdownイベントを受け取るリスナー。
src/main/java/org/littlewings/cdi/ApplicationLifecycleEventListener.java
package org.littlewings.cdi; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; import jakarta.enterprise.event.Shutdown; import jakarta.enterprise.event.Startup; @ApplicationScoped public class ApplicationLifecycleEventListener { void onStartup(@Observes Startup startup) { System.out.printf("[%s] startup, event = %s%n", getClass().getSimpleName(), startup); } void onShutdown(@Observes Shutdown shutdown) { System.out.printf("[%s] shutdown, event = %s%n", getClass().getSimpleName(), shutdown); } }
こちらも受け取った内容を標準出力に書き出しているだけですね。
あとはなんとなくJakarta RESTful Web Services(JAX-RS)も使っておきます。
JAX-RSの有効化。
src/main/java/org/littlewings/cdi/RestApplication.java
package org.littlewings.cdi; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; @ApplicationPath("/") public class RestApplication extends Application { }
JAX-RSリソースクラス。
src/main/java/org/littlewings/cdi/HelloResource.java
package org.littlewings.cdi; import jakarta.enterprise.context.ApplicationScoped; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; @Path("/hello") public class HelloResource { @GET @Produces(MediaType.TEXT_PLAIN) public String message() { return "Hello World!!"; } }
確認してみる
確認してみましょう。
パッケージング。
$ mvn package
プロビジョニングしたWildFlyを起動。
$ target/server/bin/standalone.sh
起動時にはこんなログが出力されます。
17:31:47,569 INFO [stdout] (ServerService Thread Pool -- 33) [ApplicationScopedLifecycleListener] initialized, event = io.undertow.servlet.spec.ServletContextImpl@1475f3fd 17:31:47,571 INFO [stdout] (ServerService Thread Pool -- 33) [ApplicationLifecycleEventListener] startup, event = jakarta.enterprise.event.Startup@cc8284e
確かに@Initialized(ApplicationScoped.class)の方がStartupの前に来ています。
動作確認。
$ curl localhost:8080/hello Hello World!!
WildFlyを停止します。
17:33:01,007 INFO [stdout] (ServerService Thread Pool -- 9) [ApplicationLifecycleEventListener] shutdown, event = jakarta.enterprise.event.Shutdown@33464610 17:33:01,008 INFO [stdout] (ServerService Thread Pool -- 9) [ApplicationScopedLifecycleListener] before destroyed, event = io.undertow.servlet.spec.ServletContextImpl@1475f3fd 17:33:01,009 INFO [stdout] (ServerService Thread Pool -- 9) [ApplicationScopedLifecycleListener] destroyed, event = io.undertow.servlet.spec.ServletContextImpl@1475f3fd
こちらはShutdownの方が先に来ていますね。
ということで確認できましたと。
おわりに
CDIでコンテナの起動・終了時になにか処理をしたい場合について調べてみました。
このあたり、あまり使っていないのでパッと思い浮かばないんですよね。1度試しておいてよかったかなと思います。