これは、なにをしたくて書いたもの?
RESTEasy 6.1.0に関するブログを見ていて、JAX-RS(Jakarta RESTful Web Services) 3.1ではJava SE環境でJAX-RSを動かすことが
できるようになっていたことに気づいたので、少し試してみようかなと。
SeBootstrap
Java SE環境でJAX-RS 3.1を使うには、SeBootstrap
というクラスを使うようです。
以下のブログエントリーで登場し、「Example SeBootstrap Usage」にサンプルコードがあります。
JAX-RS 3.1.0の仕様。
Jakarta RESTful Web Services 3.1.0 / Applications / Publication / Java SE / Java SE Bootstrap
SeBootstrap
の記述があり、Java SE環境でJAX-RS実装により起動された組み込みHTTPサーバーを使って、アプリケーションを公開できる
仕組みだとされています。
設定に関しては、SeBootstrap.Configuration
を使って行えるようです。
ベンダー間でコードに移植性があるので、Java SE環境でJAX-RSを動かす時にはSeBootstrap
の使用が推奨されているようです。
このように書くと別の方法もありそうなのですが、createEndpoint
とRuntimeDelegate
というものに関する記述がありました。
Jakarta RESTful Web Services 3.1.0 / Applications / Publication / Java SE /
この方法を使ってJAX-RSのエンドポイントを公開する方法は仕様の範囲外らしく、RESTEasyのドキュメントにも記載がないので
今回はパスします。
RESTEasy 6.1.0のリリースに関するブログを見ると、SeBootstrap
を使う時に組み合わせられるアーティファクトが以下のように
記載されています。
- org.jboss.resteasy:resteasy-undertow-cdi
- 推奨
- org.jboss.resteasy:resteasy-undertow
- 推奨
- org.jboss.resteasy:resteasy-netty4
- org.jboss.resteasy:resteasy-vertx
- SSL実装なし
resteasy-netty4-cdiは入ってないんですね(実際、resteasy-undertow-cdiと入れ替えてみたらCDIの部分が動かなかったのでそうなんだなと)。
なお、RESTEasyのドキュメントでSeBootstrap
に関する記載があるのはこちらです。
RESTEasy / Jakarta RESTful Web Services SeBootstrap
では、ドキュメントを見るのはこれくらいにして、実際に使っていってみましょう。
環境
今回の環境は、こちらです。
$ java --version openjdk 17.0.5 2022-10-18 OpenJDK Runtime Environment (build 17.0.5+8-Ubuntu-2ubuntu120.04) OpenJDK 64-Bit Server VM (build 17.0.5+8-Ubuntu-2ubuntu120.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 17.0.5, 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-135-generic", arch: "amd64", family: "unix"
お題
resteasy-undertow-cdi
が使えるということだったので、今回は簡単なJAX-RSとCDIを使ったアプリケーションを、SeBootstrap
を使って
作ってみます。
また、Jandexを使ったパターンも試してみます。
resteasy-undertow-cdi
以外のアーティファクトは、今回は試しません。
JAX-RS+CDIアプリケーションを作成する
ではまずは、簡単なJAX-RSとCDIを使ったアプリケーションを作成してみましょう。
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.jboss.resteasy</groupId> <artifactId>resteasy-core</artifactId> <version>6.2.2.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-undertow-cdi</artifactId> <version>6.2.2.Final</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.4.0</version> <executions> <execution> <id>copy-dependencies</id> <phase>prepare-package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/libs</outputDirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.3.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>libs/</classpathPrefix> <mainClass>org.littlewings.resteasy.sebootstrap.App</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build>
RESTEasyは、6.2.2.Finalを使います。また、このpom.xml
の記述は後から追加していきます。
こちらは、Maven Dependency PluginとMaven JAR Pluginを使って、java -jar
でアプリケーションを実行できるようにする設定です。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.4.0</version> <executions> <execution> <id>copy-dependencies</id> <phase>prepare-package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/libs</outputDirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.3.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>libs/</classpathPrefix> <mainClass>org.littlewings.resteasy.sebootstrap.App</mainClass> </manifest> </archive> </configuration> </plugin>
JAX-RSリソースクラス。
src/main/java/org/littlewings/resteasy/sebootstrap/MessageResource.java
package org.littlewings.resteasy.sebootstrap; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; @Path("message") @ApplicationScoped public class MessageResource { @Inject MessageService messageService; @GET @Produces(MediaType.TEXT_PLAIN) public String get(@QueryParam("text") String text) { return messageService.decorate(text); } }
CDI管理Bean。
src/main/java/org/littlewings/resteasy/sebootstrap/MessageService.java
package org.littlewings.resteasy.sebootstrap; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped public class MessageService { public String decorate(String target) { return "★★★" + target + "★★★"; } }
JAX-RS有効化のためのクラス。
src/main/java/org/littlewings/resteasy/sebootstrap/JaxrsActivator.java
package org.littlewings.resteasy.sebootstrap; import java.util.Set; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; @ApplicationPath("") public class JaxrsActivator extends Application { @Override public Set<Class<?>> getClasses() { return Set.of(MessageResource.class); } }
この時点では、Application
のサブクラスからJAX-RSリソースクラスを返した方がよいです。
main
メソッドを持ったクラス。この中身は、あとで埋めていきます。
src/main/java/org/littlewings/resteasy/sebootstrap/App.java
package org.littlewings.resteasy.sebootstrap; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.ExecutionException; import jakarta.ws.rs.SeBootstrap; import org.jboss.jandex.Index; import org.jboss.jandex.IndexReader; import org.jboss.logging.Logger; import org.jboss.resteasy.core.se.ConfigurationOption; public class App { public static void main(String... args) throws ExecutionException, InterruptedException, IOException { Logger logger = Logger.getLogger(App.class); // 後で } }
beans.xml
も必要ですが、中身は空でかまいません。
src/main/resources/META-INF/beans.xml
このアプリケーションは、パッケージングして
$ mvn package
java -jar
で起動できるものとします。
$ java -jar target/[JARファイル名]
SeBootstrapを使う
まずは、簡単にSeBootstrap
を使ってみます。こんな感じですね。
public class App { public static void main(String... args) throws ExecutionException, InterruptedException, IOException { Logger logger = Logger.getLogger(App.class); SeBootstrap.Instance instance = SeBootstrap .start(new JaxrsActivator()) .toCompletableFuture() .get(); logger.info("server startup."); System.console().readLine("> Enter stop."); instance .stop() .toCompletableFuture() .get(); } }
SeBootstrap#start
に、Application
のサブクラスのインスタンスを渡します。
SeBootstrap.Instance instance =
SeBootstrap
.start(new JaxrsActivator())
.toCompletableFuture()
.get();
パッケージングして、アプリケーションを起動。
$ mvn package $ java -jar target/[JARファイル名]
確認。
$ curl localhost:8081/message?text=Hello ★★★Hello★★★
起動してもUndertowのログにリッスンポートが表示されたりはしていないのですが、デフォルトでは8081ポートでリッスンしています。
こちらの内容ですね。
SeBootstrap.Configurationを使ってSeBootstrapの設定を行う
次に、SeBootstrap.Configurationを使ってSeBootstrapの設定を行ってみます。
RESTEasy / Jakarta RESTful Web Services SeBootstrap
SeBootstrap.Configuration.Builder
で設定を指定して、
SeBootstrap.Configuration.Builder (Jakarta EE Platform API)
SeBootstrap.Configuration
のインスタンスを作成してSeBootstrap#start
に渡すことになります。
SeBootstrap.Configuration (Jakarta EE Platform API)
標準の範囲でもプロトコル、ホスト(アドレス)、ポート、ルートパスやSSLの設定は行えそうです。
あとは実装固有のプロパティを指定することができます。
ここでは、リッスンするアドレスとポートを指定してみましょう。
public class App { public static void main(String... args) throws ExecutionException, InterruptedException, IOException { Logger logger = Logger.getLogger(App.class); SeBootstrap.Configuration configuration = SeBootstrap .Configuration .builder() .host("0.0.0.0") .port(8080) .build(); SeBootstrap.Instance instance = SeBootstrap .start(new JaxrsActivator(), configuration) .toCompletableFuture() .get(); logger.info("server startup."); System.console().readLine("> Enter stop."); instance .stop() .toCompletableFuture() .get(); } }
パッケージング、アプリケーションを起動して
$ mvn package $ java -jar target/[JARファイル名]
確認。
$ curl localhost:8080/message?text=Hello ★★★Hello★★★
リッスンポートが変更できていることを確認できました(リッスンしているアドレスの確認は省略します)。
Jandex Maven Pluginを使う
次に、Jandex Maven Pluginも使ってみましょう。こちらは、RESTEasyのドキュメントにも記載があります。
It's also suggested that if the resources to be used in the application are not explicitly define, then use the org.jboss.jandex:jandex-maven-plugin to create a Jandex Index. Without this the class path will be scanned for resources which could have significant performance impacts.
RESTEasy / Jakarta RESTful Web Services SeBootstrap
JAX-RSリソースクラスが明示されていない場合は、こちらを使用することが勧められています。
インデックスを作成してくれるみたいですね。
pom.xml
に、Jandex Maven Pluginを追加します。
<plugin> <groupId>org.jboss.jandex</groupId> <artifactId>jandex-maven-plugin</artifactId> <version>1.2.3</version> <executions> <execution> <id>make-index</id> <goals> <goal>jandex</goal> </goals> </execution> </executions> </plugin>
Application
のサブクラスからは、JAX-RSリソースクラスに関する情報を返さないようにしてみます。
@ApplicationPath("") public class JaxrsActivator extends Application { /* @Override public Set<Class<?>> getClasses() { return Set.of(MessageResource.class); } */ }
main
メソッドを持ったクラスは、以下のように変更してみます。
public class App { public static void main(String... args) throws ExecutionException, InterruptedException, IOException { Logger logger = Logger.getLogger(App.class); SeBootstrap.Configuration configuration = SeBootstrap .Configuration .builder() .host("0.0.0.0") .port(8080) .property(ConfigurationOption.JANDEX_INDEX.key(), Index.of(MessageResource.class)) .build(); SeBootstrap.Instance instance = SeBootstrap .start(new JaxrsActivator(), configuration) .toCompletableFuture() .get(); logger.info("server startup."); System.console().readLine("> Enter stop."); instance .stop() .toCompletableFuture() .get(); } }
実装固有のプロパティとして、JANDEX_INDEX
を指定します。この時、JAX-RSリソースクラスはJandexを使って指定します。
SeBootstrap.Configuration configuration = SeBootstrap .Configuration .builder() .host("0.0.0.0") .port(8080) .property(ConfigurationOption.JANDEX_INDEX.key(), Index.of(MessageResource.class)) .build();
パッケージング、アプリケーションを起動して
$ mvn package $ java -jar target/[JARファイル名]
確認。
$ curl localhost:8080/message?text=Hello ★★★Hello★★★
Application
のサブクラスから、JAX-RSリソースクラスを返さなくても動作するようになりました。
この時、META-INF/jandex.idx
というファイルがJARファイルの中にあることを確認できます。これがインデックスファイルです。
$ jar -tvf target/[JARファイル名] | grep jandex 1087 Wed Dec 28 00:09:38 JST 2022 META-INF/jandex.idx
ちなみに、Jandexのインデックスがなくても今回のコードで動作はするのですが、パフォーマンスに影響があるという話のようですね。
この部分がインデックスがなくても動きます、ということです。
.property(ConfigurationOption.JANDEX_INDEX.key(), Index.of(MessageResource.class))
なお、今回はmvn package
でパッケージングしていますが、mvn compile
だとJandex Maven Pluginは動作しません。
これはデフォルトでprocess-classes
フェーズにマッピングされているからです。
パッケージングせずに動かすなら、こうですね。
$ mvn process-classes
もしくはフェーズの紐付けを変更しましょう。
JandexのインデックスファイルにJAX-RSリソースクラスの探索を任せる
最後に、JandexのインデックスファイルにJAX-RSリソースクラスの探索を任せるようにしてみましょう。
つまり、今回作成したMessageResource
というClass
クラスへの参照を削除するようにしてみます。
結果はこちら。
public class App { public static void main(String... args) throws ExecutionException, InterruptedException, IOException { Logger logger = Logger.getLogger(App.class); SeBootstrap.Instance instance = SeBootstrap .start(new JaxrsActivator()) .toCompletableFuture() .get(); logger.info("server startup."); System.console().readLine("> Enter stop."); instance .stop() .toCompletableFuture() .get(); } }
1番最初に戻りました。
こちらでも構わないのですが。
public class App { public static void main(String... args) throws ExecutionException, InterruptedException, IOException { Logger logger = Logger.getLogger(App.class); SeBootstrap.Configuration configuration = SeBootstrap .Configuration .builder() .host("0.0.0.0") .port(8080) .build(); SeBootstrap.Instance instance = SeBootstrap .start(new JaxrsActivator(), configuration) .toCompletableFuture() .get(); logger.info("server startup."); System.console().readLine("> Enter stop."); instance .stop() .toCompletableFuture() .get(); } }
今回は、バインドするアドレスとポートを変えた方を使ってみましょうか。
パッケージング、アプリケーションを起動して
$ mvn package $ java -jar target/[JARファイル名]
確認。
$ curl localhost:8080/message?text=Hello ★★★Hello★★★
OKですね。JAX-RSリソースクラスの探索を、Jandexに任せることができました。
これはどうなっているかというと、META-INF/jandex.idx
ファイルが存在する場合はRESTEasyが検索してくれるからです。
つまり、先ほどJANDEX_INDEX
を使ったコードを書いていましたが、実はなくてもよかったんだ、という話になります。
SeBootstrap.Configuration configuration = SeBootstrap .Configuration .builder() .host("0.0.0.0") .port(8080) .property(ConfigurationOption.JANDEX_INDEX.key(), Index.of(MessageResource.class)) .build();
ドキュメントに使い方の例として書かれていたので、そのままマネしていたのが裏目に出ました…。
もっと言うと、Application
のサブクラスがJAX-RSリソースクラスに関する情報を返さない場合は、ファイルシステムからクラスを探しに
いくようです。
なので、Jandexを使わない場合はApplication
のサブクラスがJAX-RSリソースクラスに関する情報を返した方がよい、となります。
組み込みサーバーの実装の決定方法
今回、組み込みサーバーとしてUndertowを使いましたが、RESTEasyの場合はサービスプロバイダーの仕組みで実装を探すようです。
具体的には、EmbeddedServer
インターフェースの実装が対象になります。
resteay-undertow-cdi
の場合は、以下のクラスおよびファイルになります。
そして、これらのクラスをJAX-RSのAPIからどうやって紐付けるのかというと、今回扱わなかった、JAX-RSのJava SE向けの仕様で
出てきたRuntimeDelegate
の実装が使われることになります。
こちらもサービスプロバイダーの仕組みで探索されることになります。
まとめ
SeBootstrap
を使って、JAX-RSをJava SE環境で動作させてみました。
RESTEasy自体は以前からJava SE環境で動かすことができたのですが、より簡単にできるようになって良いですね。