web-dev-qa-db-ja.com

複雑なオブジェクトを引数としてRESTfulサービスに渡すにはどうすればよいですか?

JSONにシリアル化されたオブジェクトを返す「RESTに似た」サービスを作成する簡単なテストのセットアップに成功しました。これは非常に簡単で迅速でした( この記事に基づいて ) 。

しかし、PeachのようにJSON化されたオブジェクトを返すことは簡単でしたが、プリミティブではない入力パラメーターを扱う例はまだ見ていません。複雑なオブジェクトを引数として渡すにはどうすればよいですか?私はApache CXFを使用していますが、ジャクソンのような他のフレームワークを使用した例も歓迎します:)

クライアント側はおそらく、javascriptオブジェクトを作成してJSON.stringify(complexObj)に渡し、その文字列をパラメーターの1つとして渡すようなものです。

サービスはおそらく次のようになります

@Service("myService")
class RestService {
    @GET
    @Produces("application/json")
    @Path("/fooBar")
    public Result fooBar(@QueryParam("foo") double foo, @QueryParam("bar") double bar,
        @QueryParam("object") MyComplex object) throws WebServiceException {
    ...
    }
}

パラメーターとしてシリアル化されたオブジェクトを送信すると、Internet Explorerによって課される2KBのURL制限にすぐに触れる可能性があります。これらの場合にPOSTを使用することをお勧めしますか。関数定義を大幅に変更する必要がありますか?

35
oligofren

少し掘り下げた後、基本的に2つのオプションがあることがすぐにわかりました。

オプション1

他のすべてのパラメーターを含む「ラッパーオブジェクト」をサービスに渡します。これをJettisonベースのプロバイダーと連携させるために、このラッパークラスに@XmlRootElementのようなJAXBアノテーションを付ける必要がありますが、代わりにJacksonを使用する必要はありません。コンテンツタイプを適切なタイプに設定するだけで、適切なメッセージ本文リーダーが呼び出されます。これは、POSTタイプのサービス(AFAIK)に対してのみ機能します。

これは、元の質問で言及したサービスをラッパーオブジェクトを使用して1つに変換する例にすぎません。

@Service("myService")
class RestService {

    @POST
    @Produces("application/json")
    @Path("/fooBar")
    public Result fooBar(

          /** 
          * Using "" will inject all form params directly into a ParamsWrapper 
          * @see http://cxf.Apache.org/docs/jax-rs-basics.html
          */
          @FormParam("") FooBarParamsWrapper wrapper

        ) throws WebServiceException {
            doSomething(wrapper.foo);
    }
}

class ParamsWrapper {
  double foo, bar;
  MyComplexObject object;
}

オプション2

オブジェクトをパックする特別な文字列形式を提供し、この文字列を取得してオブジェクトを作成するクラスに、文字列、静的なvalueOf(String s)または静的なfromString(String s)を取得するコンストラクタを実装できます。それから。または非常に似て、まったく同じことを行うParameterHandlerを作成します。

知る限り、JSONPを使用してブラウザからサービスを呼び出すことができるのは2番目のバージョンのみです(JSONPはGETに限定されたトリックであるため)。 URIで複雑なオブジェクトの配列を渡すことができるように、このルートを選択しました。

これがどのように機能するかの例として、次のドメインクラスとサービスを取り上げます。

@GET
@Path("myService")
public void myService(@QueryParam("a") MyClass [] myVals) {
    //do something
}

class MyClass {
    public int foo;
    public int bar;

   /** Deserializes an Object of class MyClass from its JSON representation */
   public static MyClass fromString(String jsonRepresentation) {
           ObjectMapper mapper = new ObjectMapper(); //Jackson's JSON marshaller
           MyClass o= null;
           try {
                   o = mapper.readValue(jsonRepresentation, MyClass.class );
           } catch (IOException e) {
                    throw new WebApplicationException()
           }
           return o;
   }
}

URI http://my-server.com/myService?a={"foo":1, "bar":2}&a={"foo":100, "bar":200}この場合、2つのMyClassオブジェクトで構成される配列に逆シリアル化されます。

2019 comment:この答えが2019年にもまだヒットしているのを見て、コメントする必要があると思います。後から考えて、オプション2のコメントはしません。GETコールを実行できるようにするためだけにこれらの手順を実行すると、おそらく価値のない複雑さが追加されるためです。サービスがこのような複雑な入力を受け取る場合、入力の順列の数のために、クライアント側のキャッシュを使用することはおそらくできないでしょう。サーバー上で適切なCross-Origin-Sharing(CORS)ヘッダーとPOST入力を構成します。その後、サーバー上で可能な限りキャッシュに焦点を当てます。

32
oligofren

受け入れられた答えには@BeanParamがありません。詳細については、 https://docs.jboss.org/resteasy/docs/3.0-rc-1/javadocs/javax/ws/rs/BeanParam.html を参照してください。これにより、ラッパーオブジェクト内でクエリパラメーターを定義できます。例えば。

public class TestPOJO {

    @QueryParam("someQueryParam")
    private boolean someQueryParam;

    public boolean isSomeQueryParam() {
        return someQueryParam;
    }

    public boolean setSomeQueryParam(boolean value) {
        this.someQueryParam = value;
    }
}

... // inside the Resource class
@GET
@Path("test")
public Response getTest(@BeanParam TestPOJO testPOJO) {
    ...
}
3
r_ganaus

最良かつ最も簡単な解決策は、オブジェクトをjson文字列として送信し、サーバー側でそのjsonをデコードし、必要に応じて指定されたオブジェクトにマップするメソッドを実装することです。

1
Koustuv Ganguly