JAX-RSでパラメータの受け取り方をいろいろ試す

JAX-RSでは次に挙げるアノテーションをメソッドの引数などに付けることでクエリパラメータやパスの一部を受け取ることができます。

  • @MatrixParam
  • @QueryParam
  • @PathParam
  • @CookieParam
  • @HeaderParam

メソッドの引数はStringやプリミティブを使うことができますが、それ以外のクラスも使用できます。

Stringの引数をひとつだけ受け取るpublicなコンストラクタを持つクラス

次のようなクラスでもクエリパラメータなどを受け取ることが出来ます。

public class Hoge {

    public final String value;

    public Hoge(String value) {
        this.value = value;
    }
}

リソースメソッドでは次のように使います。

@GET
public String get(@QueryParam("abc") Hoge abc) {
    ...

この例でいうとabcという名前のクエリパラメータがnullの場合はHogeはインスタンス化されず引数abcはnullとなります。 クエリパラメータがnullの場合というのはabcという名前のクエリパラメータが無い場合です。 ?abc=&def=xyz というようなクエリパラメータの場合はabcはnullではなく空文字列です。

Stringの引数をひとつだけ受け取る”valueOf”という名前のstaticファクトリメソッドを持つクラス

次のようなクラスを使用することも可能です。

public class Hoge {

    public final String value;

    private Hoge(String value) {
        this.value = value;
    }

    public static Hoge valueOf(String value) {
        return new Hoge(value);
    }
}

staticファクトリメソッドを書く分の手間が増えますが、例えば、

  • valueが空文字列の場合はnullを返す
  • Integerのようにキャッシュすることができる
  • サブクラスを返すことができる

という風に柔軟に実装することが可能です。 また、クラス(この例でいうとHoge)をabstractにすることも可能です。

Stringの引数をひとつだけ受け取る”fromString”という名前のstaticファクトリメソッドを持つクラス

staticファクトリメソッドはvalueOf以外にfromStringという名前にすることも可能です。

ひとつのクラスにvalueOfとfromStringの両方が定義されている場合はvalueOfが呼ばれます。 ただし列挙型の場合はvalueOfが暗黙的に実装されており挙動を上書きできないためか、fromStringが呼ばれます。 列挙型でなくともfromStringを優先にしておけば良かったのでは、と思わなくもないです。

ParamConverterを使用する

JAX-RS 2から

というふたつのインターフェースが追加されました。

ParamConverterの実装クラスではStringから任意のクラスに変換するロジックを書きます。

public class HogeParamConverter implements ParamConverter<Hoge> {

    @Override
    public Hoge fromString(String value) {
        return new Hoge(value);
    }

    @Override
    public String toString(Hoge hoge) {
        return hoge.value;
    }
}

ParamConverterProviderの実装クラスではリソースメソッドの引数の型やアノテーションをもとにParamConverterを選択して返します。

public class HogeParamConverterProvider implements ParamConverterProvider {

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
        if (rawType == Hoge.class) {
            return (ParamConverter<T>) new HogeParamConverter();
        }
        return null;
    }
}

この方法は一番手間がかかりますがstaticファクトリメソッドを用いる方法と比べて

  • Hogeをインターフェースにすることが可能
  • 同じ型に対してもアノテーションによって異なるParamConverterを使用できる
  • javaから始まるパッケージのクラスなど既存のクラスも使用できる

といったことが利点だと思います。

まとめ

JAX-RS 2からParamConverterが入った事でクリエパラメータやリクエストヘッダをどんな型でも受け取ることができるようになりました。 インターセプターやクライアントAPIに比べると地味に見えますが、なかなか素晴らしい進化だと個人的には思っています。

サンプル書きました。

Java API for WebSocketの@PathParamを試す

@PathParam というアノテーションでパスの一部をメソッドの引数で受け取ることができるようなので試してみました。

package chat;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value = "/chat/{guest-id}")
public class ChatEndPoint {

    private static CopyOnWriteArraySet<Session> sessions = new CopyOnWriteArraySet<>();

    @OnOpen
    public void onOpen(@PathParam("guest-id") String guestId, Session session) {
        sessions.add(session);
        for (Session s : sessions) {
            s.getAsyncRemote().sendText(guestId + "さんが入室しました");
        }
    }

    @OnMessage
    public void onMessage(@PathParam("guest-id") String guestId, String text) throws
            IOException {
        for (Session s : sessions) {
            s.getAsyncRemote().sendText("[" + guestId + "] " + text);
        }
    }

    @OnClose
    public void onClose(@PathParam("guest-id") String guestId, Session session) {
        sessions.remove(session);
        for (Session s : sessions) {
            s.getAsyncRemote().sendText(guestId + "さんが退室しました");
        }
    }
}

クラスに付けたServerEndpointに書かれたパスの一部が {guest-id} となっていますね。 それをメソッドの引数に @PathParam(“guest-id”) と付けて受け取っています。 簡単ですね。

今回のソース

今回作ったクラスはchatというパッケージにしています。 また、mvn -Pchat exec:java とすればサーバが立ち上がるようにしています。 クライアントはchat.htmlです。 Chromeで何となく動くことを確認しています。 サーバを終了したい場合はコンソールで何かキーを押してください。

なお、今回のコードはきしださんが下記エントリで書かれたものを参考にしました。