【问题标题】:Reusing JAX RS Client in multi-threaded environment (with resteasy)在多线程环境中重用 JAX RS 客户端(带 resteasy)
【发布时间】:2016-01-10 21:11:21
【问题描述】:

根据文档,

“客户端是管理客户端的重量级对象 通信基础设施。初始化和处置 客户端实例可能是一个相当昂贵的操作。因此是 建议在 应用。 "

好的,我正在尝试将 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 客户端 API 吗?或者我必须使用一些特定于框架的功能(resteasy/jersey)你能提供一些示例或文档吗?

【问题讨论】:

标签: java multithreading jersey jax-rs resteasy


【解决方案1】:

由于此问题在撰写本文时仍处于打开状态(版本 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】:

    首先,不要重复使用 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 后,在重用 Client 时将不再出现错误,您可以愉快地在整个应用程序中重用它们。我已经在许多项目上尝试过这个解决方案,它实际上运行良好。但是,当您将应用程序部署到非 resteasy 容器(如 Glassfish)时,您的代码将无法运行,您将不得不再次使用 ClientBuilder 类。

    【讨论】:

      【解决方案3】:

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

      你有两个选择:

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

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

      【讨论】:

      • 这是不正确的。 Client 用于共享。这就是客户端实现类具有可变变量的原因。 WebTarget 不是线程安全的。
      • 您能否证明这一点,例如文档链接?
      • @SaptarshiBasu:正如链接问题Is JAX-RS Client Thread Safe 中所讨论的,不幸的是,规范并不要求Client 是线程安全的。在某些实现中它是(例如在泽西岛,我相信),在其他实现中它不是(例如 Apache CXF)。
      猜你喜欢
      • 2019-09-25
      • 1970-01-01
      • 2017-05-05
      • 1970-01-01
      • 1970-01-01
      • 2019-04-19
      • 2016-12-06
      • 1970-01-01
      • 2015-05-16
      相关资源
      最近更新 更多