在多线程环境中重用JAX RS Client(具有resteasy)

时间:2015-10-13 08:00:56

标签: java multithreading jersey jax-rs resteasy

根据文件,

  

"客户端是管理客户端的重量级对象   通信基础设施初始化以及处理   客户端实例可能是相当昂贵的操作。因此   建议只构建少量的Client实例   应用。 "

好的,我试图在一个静态变量中缓存Client本身和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:仍然分配了连接。   确保在分配另一个连接之前释放连接。

如何正确地重用/缓存Client / WebTarget?是否可以使用JAX RS Client API?或者我必须使用一些特定于框架的功能(resteasy / jersey)你能提供一些示例或文档吗?

3 个答案:

答案 0 :(得分:7)

您的实现不是线程安全的。当两个线程同时访问someMethod时,它们共享相同的Client,并且当第一个请求未完成时,将尝试发出第二个请求。

您有两种选择:

  • 手动同步ClientWebTarget的访问权限。
  • 让容器通过用@javax.ejb.Singleton注释封闭类型来管理并发性,这样可以保证线程安全。 (见EJB specification
  • 的第4.8.5章

如果容器管理环境中有someMethod,我会使用第二种方法。

答案 1 :(得分:7)

由于此问题在撰写本文时仍处于打开状态(版本3.0.X)RESTEASY: deprecated Apache classes cleanup

您可以更深入地使用较新的,不推荐使用的类来创建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块中。

答案 2 :(得分:4)

首先,不要重复使用WebTarget。为简单起见,您始终可以创建新的WebTarget。

其次,如果您正在使用Resteasy,则可以将Resteasy客户端提供的依赖项添加到您的项目中。 Gradle中的示例:

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

然后,您可以像这样创建连接:

text

没有必要设置maxPooledPerRoute,这是由RestEasy自动设置的(可以在RestEasyClientBuilder类源代码中找到)。

当您设置connectionPoolSize时,您将不再在重复使用客户端时收到错误,并且您可以愉快地在整个应用程序中重复使用它们。我在很多项目中尝试过这个解决方案,但实际上效果很好。但是当您将应用程序部署到非resteasy容器(如Glassfish)时,您的代码将无法正常工作,您将不得不再次使用ClientBuilder类。