web-dev-qa-db-ja.com

マルチスレッド環境でのJAX RSクライアントの再利用(resteasyあり)

ドキュメントによると、

「クライアントは、クライアント側の通信インフラストラクチャを管理する重量級のオブジェクトです。クライアントインスタンスの初期化と破棄は、かなりコストのかかる操作になる可能性があります。したがって、アプリケーションで少数のクライアントインスタンスのみを構築することをお勧めします。」

OK、クライアント自体とWebTargetインスタンスを静的変数にキャッシュしようとしています。マルチメソッド環境でsomeMethod()が呼び出されます。

private static Client client = ClientBuilder.newClient();
private static WebTarget webTarget = client.target("someBaseUrl");
...
public static String someMethod(String arg1, String arg2)
{
    WebTarget target = entrTarget.queryParam("arg1", arg1).queryParam("arg2", arg2);
    Response response = target.request().get();
    final String result = response.readEntity(String.class);
    response.close();
    return result;
}

しかし、ときどき(常にではない)例外が発生します。

BasicClientConnManagerの無効な使用:接続はまだ割り当てられています。別の接続を割り当てる前に、必ず接続を解放してください。

どのようにしてクライアント/ WebTargetを正しく再利用/キャッシュできますかJAX RSクライアントAPIで可能ですか?または、フレームワーク固有の機能(resteasy/jersey)を使用する必要があります。例やドキュメントを提供していただけませんか?

16
Alexandr

実装はスレッドセーフではありません。 2つのスレッドが同時にsomeMethodにアクセスすると、それらは同じClientを共有し、最初の要求が完了していないときに2番目の要求を試みます。

次の2つの選択肢があります。

  • ClientおよびWebTargetへのアクセスを手動で同期します。
  • コンテナに、囲んでいる型に@javax.ejb.Singletonスレッドの安全性を保証します。 ( EJB仕様)の4.8.5章を参照

コンテナ管理環境でsomeMethodを使用する場合、2番目のアプローチを使用します。

7
lefloh

この問題は執筆時点でもまだ残っているため(バージョン3.0.X) RESTEASY:非推奨のApacheクラスのクリーンアップ

RESTEASYクライアントを作成する代わりに、新しい、廃止されていないクラスを使用するためにさらに深く行くことができます。また、プールをどのようにするかなどをより詳細に制御できます。

これが私がしたことです:

// This will create a threadsafe JAX-RS client using pooled connections.
// Per default this implementation will create no more than than 2
// concurrent connections per given route and no more 20 connections in
// total. (see javadoc of PoolingHttpClientConnectionManager)
PoolingHttpClientConnectionManager cm =
        new PoolingHttpClientConnectionManager();

CloseableHttpClient closeableHttpClient =
        HttpClientBuilder.create().setConnectionManager(cm).build();
ApacheHttpClient4Engine engine =
        new ApacheHttpClient4Engine(closeableHttpClient);
return new ResteasyClientBuilder().httpEngine(engine).build();

また、電話をかけた後、接続を解放することを確認してください。 response.close()を呼び出すとそれが行われるので、おそらくそれをfinallyブロックに入れます。

8
Patrick

まず、WebTargetを再利用しないでください。簡単にするために、いつでも新しいWebTargetを作成できます。

次に、Resteasyを使用している場合、Resteasyクライアントに提供された依存関係をプロジェクトに追加できます。 Gradleの例:

    provided 'org.jboss.resteasy:resteasy-client:3.0.14.Final'

次に、次のように接続を作成できます。

        ResteasyClientBuilder builder = new ResteasyClientBuilder();
        builder.connectionPoolSize(200);

MaxPooledPerRouteを設定する必要はありません。これはRestEasyによって自動的に設定されます(RestEasyClientBuilderクラスのソースコードにあります)。

ConnectionPoolSizeを設定すると、クライアントが再利用されるときにエラーが発生しなくなり、アプリケーション全体でそれらを喜んで再利用できます。私は多くのプロジェクトでこのソリューションを試しましたが、実際にうまくいきます。ただし、アプリケーションを(Glassfishな​​どの)復元不可能なコンテナにデプロイすると、コードは機能せず、ClientBuilderクラスを再度使用する必要があります。

4
Pavel Pscheidl