Jakarta EE 10 - Jakarta RESTful Web Services 3.1 変更内容まとめ

blog1.mammb.com


はじめに

Jakarta EE 10 で Jakarta RESTful Web Services は 3.0 から 3.1 へバージョンアップします。

大きな変更点は、Java SE 環境向けのサポートとマルチパート・フォーム用APIの標準化になります。


Java SE 環境における JAX-RS アプリケーションブートストラップ

Java SE環境で JAX-RS アプリケーションをブートストラップする標準的なAPIが提供されました。

javax.ws.rs.JAXRS インターフェースが追加され、このインターフェース経由でJAX-RSアプリケーションを起動できます。

以下のような JAX-RS アプリケーション が定義されていた場合、

@ApplicationPath("helloworld")
@Path("hello")
public class HelloWorld extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        return Collections.singleton(HelloWorld.class);
    }

    @GET
    public String sayHello() {
        return "Hello, World!";
    }

}

以下のようにブートストラップできます。

public static final void main(final String[] args) throws InterruptedException {

    final Application application = new HelloWorld();

    final JAXRS.Configuration requestedConfiguration = JAXRS.Configuration.builder().build();

    JAXRS.start(application, requestedConfiguration).thenAccept(instance -> {

        Runtime.getRuntime().addShutdownHook(new Thread(() ->
            instance.stop().thenAccept(stopResult -> 
              System.out.printf("Stop result: %s [Native stop result: %s].%n",
                  stopResult,
                  stopResult.unwrap(Object.class)))));

        final Configuration actualConfigurarion = instance.configuration();
        final URI uri = UriBuilder.newInstance()
          .scheme(actualConfigurarion.protocol().toLowerCase())
             .host(actualConfigurarion.host()).port(actualConfigurarion.port())
             .path(actualConfigurarion.rootPath()).build();
        System.out.printf("Instance %s running at %s [Native handle: %s].%n",
            instance, uri, instance.unwrap(Object.class));
        System.out.println("Send SIGKILL to shutdown.");
    });

    Thread.currentThread().join();
}

ごちゃごちゃ書かれていますが、JAXRS.Configuration.builder().build()JAXRS.Configuration を作成し、JAXRS.start() するだけです。


マルチパート・フォーム用APIの標準化

RESTful Web Services では multipart/form-data を扱うための標準的なAPIが定義されていませんでした。 そのため、Jersey, Apache CFX, Resteasy などは、それぞれ独自にマルチパートをサポートしており、移植性の無い状況でした。

今回のバージョンアップにて、jakarta.ws.rs.core.EntityPart インターフェースが追加されました。

EntityPart は複数のエンティティを単一のエンティティとして送受信するマルチパートリクエストに対応するものです。


@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response postWidget(List<EntityPart> parts) {
    for (EntityPart part : parts) {
        String name = part.getName();
        Optional<String> fileName = part.getFileName();
        InputStream is = part.getContent();
        MultivaluedMap<String, String> partHeaders = part.getHeaders();
        MediaType mediaType = part.getMediaType();
        doSomethingWithPart(name, fileName, is, partHeaders, mediaType);
    }
    return Response.ok().build();
}

@FormParam により以下のように受けることもできます。

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response postWidget(
    @FormParam("part1Name") String part1, 
    @FormParam("part2Name") InputStream part2,
    @FormParam("part3Name") EntityPart part3) {...}

EntityPart を使うことで、それぞれのパートのヘッダにアクセスできます。

クライアントからの POST は以下のように行います。

List<EntityPart> parts = ...
WebTarget target = ClientBuilder.newClient().target("http://...");
Entity<List<EntityPart>> entity = Entity.entity(parts, MediaType.MULTIPART_FORM_DATA);
Response response = target.request().post(entity);

EntityPart の構築は以下のように行います。

EntityPart.withName(filename)
          .content(filename, Files.newInputStream(file))
          .mediaType("application/pdf")
          .build()


ContextResolver

JSON のシリアライズ・デシリアライズをカスタマイズするには、ContextResolver<>@Provider を使い、例えば以下のようにプライベートフィールドをシリアル化することができます。

@Provider
public class JsonConfiguration implements ContextResolver<Jsonb> {

    @Override
    public Jsonb getContext(Class<?> aClass) {
        return JsonbBuilder.newBuilder()
                .withConfig(new JsonbConfig()
                        .withPropertyVisibilityStrategy(new PrivateVisibilityJsonbStrategy()))
                .build();
    }

}

しかし、ContextResolver<Jsonb> の提供は、仕様上義務付けられていないため、RI である Glassfish/Jersey では機能するものの、他の実装では機能しない状況でした。

今回のバージョンアップにて、仕様上ContextResolver<Jsonb> の提供が義務付けられ、他の実装でも Jsonb インスタンス が提供されるようになります。


その他の変更点

  • リクエストパラメータを指定する @CookieParam, @FormParam, @HeaderParam, @MatrixParam, @QueryParam などで、List<T>, Set<T>, SortedSet<T> に加え、配列型(T[])を指定可能となった

  • JAX-RSの実装では、特定のサービスプロバイダの拡張が自動ロードされるようになった(ServiceLoader#loadによる)

  • Link.JaxbLinkLink.JaxbAdapter が非推奨かされた

  • ContainerRequestContext Configuration ClientRequestContext InterceptorContexthasProperty(String name) メソッドが(デフォルトメソッドとして)追加された

  • Response.created(URI) が、(ベースURIに対する)相対URIを絶対URIに解決するようになった(リクエストURIではなく)

  • @jakarta.ws.rs.core.Context は、将来CDIによる @Inject に置き換わることが明記された(現時点では Deprecated になっている訳ではない)

  • Cookie クラスのコンストラクタが非推奨となり、Cookie.Builder を使うようになった

    • NewCookie についても同様に NewCookie.Builder を使用
  • リクエストに Content-TypeAccept が指定されていない場合のリソースマッチングが明確化された

    • 仕様書上は 3.5. Declaring Media Type Capabilities の後半部
  • JAX-RS実装はデフォルトの Exception Mapper を提供する要件が追加された


module-info.java

Jakarta RESTful Web Services では、旧来から module-info.java が用意されていましたので、変更ではないのですが、以下のような定義になっています。

module jakarta.ws.rs {

    requires static jakarta.xml.bind;

    requires java.logging;

    exports jakarta.ws.rs;
    exports jakarta.ws.rs.client;
    exports jakarta.ws.rs.container;
    exports jakarta.ws.rs.core;
    exports jakarta.ws.rs.ext;
    exports jakarta.ws.rs.sse;

    uses jakarta.ws.rs.client.ClientBuilder;
    uses jakarta.ws.rs.ext.RuntimeDelegate;
    uses jakarta.ws.rs.sse.SseEventSource.Builder;

    opens jakarta.ws.rs.core to jakarta.xml.bind;
}