CLOVER🍀

That was when it all began.

JAX-RS(Jakarta RESTful Web Services) 3.1.0で、Contextアノテーションの代わりにCDIが推奨されるようになっていたという話

これは、なにをしたくて書いたもの?

RESTEasy 6.1.0に関するブログを見ていて、JAX-RS(Jakarta RESTful Web Services)のインジェクションの仕組みはCDIの利用が
推奨されるようになっていたので、こちらを軽く確認しておきました。

RESTEasy Releases

RESTEasy 6.1.0.Final Release

JAX-RS 3.1.0で@Contextが非推奨になり、CDIの利用が推奨に

RESTEasy 6.1.0.Finalのリリースに関するブログエントリーを見ると、以下のように書かれていました。

In Jakarta REST 3.1 the @Context is deprecated. While @Context is still currently supported, future versions may remove support.

RESTEasy 6.1.0.Final Release

@Contextは非推奨になり、将来的には削除されるようですね。

JAX-RS 3.1.0の仕様の方を見ると、@Contextと@ContextResolverは将来的にはサポートされなくなり、CDIを使うようになっていく
みたいです。

As part of an effort to better align with Jakarta CDI, future versions of this API will no longer support @Context injection and related types such as ContextResolver. As much as possible, all injection tasks will be delegated to Jakarta CDI for a better integration into the Jakarta EE ecosystem.

Jakarta RESTful Web Services 3.1.0 / Introduction / Status / Support for @Context Injection

@ContextのJavadocを見ると、@deprecatedとまではなっていないものの、将来的にはサポートが停止されることは書かれています。

Note that future versions of this API will stop supporting injection via Context as part of a tighter integration and alignment with Jakarta CDI.

Context (Jakarta EE Platform API)

ContextResolverの方には、特になにも書かれていません。

ContextResolver (Jakarta EE Platform API)

話を戻して。
RESTEasy 6.1.0では、以下の要素がCDI管理Beanとしてインジェクション可能になっているようです。

With this, RESTEasy has added some support for injecting the known types which @Context also injects. One note is this currently does not work with method parameter injection. The following types can be injected as global fields in CDI managed beans.

  • ApplicationScoped
    • jakarta.ws.rs.core.Configuration
    • jakarta.ws.rs.ext.Providers
    • jakarta.ws.rs.core.Application
    • jakarta.ws.rs.client.Client
    • jakarta.ws.rs.container.ResourceContext
    • jakarta.ws.rs.container.ResourceInfo
      • RESTEasy 6.2.0.Finalで追加
    • jakarta.ws.rs.core.SecurityContext
    • jakarta.ws.rs.sse.Sse
    • jakarta.ws.rs.core.UriInfo
  • RequestScoped

※) ブログエントリーの情報に、スコープやRESTEasy 6.2.0.Finalでの追加要素を入れています

RESTEasy 6.1.0.Final Release

注意点としては、@Contextの時とは違ってメソッドの引数としてのインジェクションはできません。

今回、こちらの一部を簡単に確認しておきましょう。

環境

今回の環境は、こちら。

$ 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"

確認には、WildFly 27.0.1.Finalを使います。

$ bin/standalone.sh --version
=========================================================================

  JBoss Bootstrap Environment

  JBOSS_HOME: /path/to/wildfly-27.0.1.Final

  JAVA: /usr/lib/jvm/default/bin/java

  JAVA_OPTS:  -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true  --add-exports=java.desktop/sun.awt=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.url.ldap=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.url.ldaps=ALL-UNNAMED --add-exports=jdk.naming.dns/com.sun.jndi.dns=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED -Djava.security.manager=allow

=========================================================================

19:31:48,663 INFO  [org.jboss.modules] (main) JBoss Modules version 2.0.3.Final
WildFly Full 27.0.1.Final (WildFly Core 19.0.1.Final)

起動。

$ bin/standalone.sh

WildFly 27.0.1.Finalに含まれているRESTEasyは6.2.1.Finalであり、JAX-RS(Jakarta RESTful Web Services) 3.1.0の実装なので今回は
RESTEasy 6.2.1.Finalの情報で見ていきます。

JAX-RSを使ったアプリケーションを作成する

では、JAX-RSを使ったアプリケーションを作成していきます。

pom.xmlから。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>resteasy-cdi-injection</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <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>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-web-api</artifactId>
            <version>10.0.0</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>ROOT</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
        </plugins>
    </build>
</project>

JAX-RSの有効化。

src/main/java/org/littlewings/jaxrs/cdi/JaxrsActivator.java

package org.littlewings.jaxrs.cdi;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("")
public class JaxrsActivator extends Application {
}

JAX-RSリソースクラス。

src/main/java/org/littlewings/jaxrs/cdi/SampleResource.java

package org.littlewings.jaxrs.cdi;

import java.util.Map;

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.client.Client;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo;

@Path("sample")
@ApplicationScoped // あってもなくても動く
public class SampleResource {
    @Inject
    UriInfo uriInfo;

    @Inject
    Client client;

    @GET
    @Path("uriInfo1")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, String> uriInfo1() {
        return Map.of(
                "getAbsolutePath", uriInfo.getAbsolutePath().toASCIIString(),
                "getBaseUri", uriInfo.getBaseUri().toASCIIString(),
                "getRequestUri", uriInfo.getRequestUri().toASCIIString(),
                "getPath", uriInfo.getPath(),
                "getQueryParameters", uriInfo.getQueryParameters().toString()
        );
    }

    @GET
    @Path("uriInfo2")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, String> uriInfo2() {
        return Map.of(
                "getAbsolutePath", uriInfo.getAbsolutePath().toASCIIString(),
                "getBaseUri", uriInfo.getBaseUri().toASCIIString(),
                "getRequestUri", uriInfo.getRequestUri().toASCIIString(),
                "getPath", uriInfo.getPath(),
                "getQueryParameters", uriInfo.getQueryParameters().toString()
        );
    }

    @GET
    @Path("google")
    @Produces(MediaType.TEXT_PLAIN)
    public String google() {
        return
                client
                        .target("https://www.google.co.jp/")
                        .request()
                        .get(String.class);
    }
}

すでに使っていますが、こんな感じでJAX-RSのインターフェースをCDIでインジェクションできます。

    @Inject
    UriInfo uriInfo;

    @Inject
    Client client;

今回はUriInfoでアクセスされた時の情報をメソッド別に見たり、Clientを使ってGoogleにアクセスしたりしてみます。

パッケージングして

$ mvn package

WildFlyにデプロイ。

$ cp target/ROOT.war /path/to/wildfly-27.0.1.Final/standalone/deployments

確認してみます。

UriInfo。

$ curl -s localhost:8080/sample/uriInfo1?foo=bar | jq
{
  "getRequestUri": "http://localhost:8080/sample/uriInfo1?foo=bar",
  "getPath": "/sample/uriInfo1",
  "getAbsolutePath": "http://localhost:8080/sample/uriInfo1",
  "getBaseUri": "http://localhost:8080/",
  "getQueryParameters": "{foo=[bar]}"
}


$ curl -s localhost:8080/sample/uriInfo2?hoge=fuga | jq
{
  "getRequestUri": "http://localhost:8080/sample/uriInfo2?hoge=fuga",
  "getPath": "/sample/uriInfo2",
  "getAbsolutePath": "http://localhost:8080/sample/uriInfo2",
  "getBaseUri": "http://localhost:8080/",
  "getQueryParameters": "{hoge=[fuga]}"
}

Client。

$ curl -s localhost:8080/sample/google
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ja"><head><meta content="世界中のあらゆる情報を検索するためのツールを提供しています。さまざまな検索機能 を活用して、お探しの情報を見つけてください。" name="description">

〜省略〜

OKですね。

各インターフェースがCDI管理Beanとして登録されている箇所は?

最後に、実装を見ていきましょう。

RESTEasyのどこで各インターフェースがCDI管理Beanとして登録されているか、確認してみます。

ほとんどのクラスは、以下のクラスで@Producesを使ってCDI管理Beanとして登録されています。

https://github.com/resteasy/resteasy/blob/6.2.1.Final/resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/ContextProducers.java

Clientだけは、こちらで登録されているようです。

https://github.com/resteasy/resteasy/blob/6.2.1.Final/resteasy-cdi/src/main/java/org/jboss/resteasy/cdi/ResteasyCdiExtension.java

まとめ

JAX-RS 3.1.0で、インジェクションでは@ContextアノテーションではなくCDIを使うように勧められていたので、簡単に試してみました。

JAX-RS 3.1.0以降を使うのであれば、これらの要素はCDIで扱っていきましょうか。