java 在多线程环境中重用 JAX RS 客户端(使用 resteasy)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33097230/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Reusing JAX RS Client in multi-threaded environment (with resteasy)
提问by Alexandr
According to the documentation,
根据文档,
"Clients are heavy-weight objects that manage the client-side communication infrastructure. Initialization as well as disposal of a Client instance may be a rather expensive operation. It is therefore advised to construct only a small number of Client instances in the application. "
“客户端是管理客户端通信基础设施的重量级对象。客户端实例的初始化和处置可能是一项相当昂贵的操作。因此,建议在应用程序中只构建少量的客户端实例。”
Ok, I'm trying to cache Client itself and WebTarget instances in a static variable, the someMethod() is invoked in multi-threaded environment:
好的,我正在尝试在静态变量中缓存 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;
}
But sometimes (not always) I'm get an exception:
但有时(并非总是如此)我会遇到一个例外:
Invalid use of BasicClientConnManager: connection still allocated. Make sure to release the connection before allocating another one.
BasicClientConnManager 的使用无效:连接仍然分配。确保在分配另一个连接之前释放连接。
How can Client/WebTarget be reused/cached correctly? Is it possible with JAX RS Client API? Or I have to use some framework-specific features (resteasy/jersey) Could you provide some example or documentation?
如何正确重用/缓存 Client/WebTarget?JAX RS 客户端 API 可以吗?或者我必须使用一些特定于框架的功能(resteasy/jersey)你能提供一些例子或文档吗?
采纳答案by lefloh
Your implementation is not thread-safe. When two threads access someMethod
at the same time they are sharing the same Client
and one will try to make a second request while the first one is not finished.
您的实现不是线程安全的。当两个线程同时访问someMethod
时,它们共享相同的内容Client
,一个将尝试在第一个请求未完成时发出第二个请求。
You have two choices:
你有两个选择:
- Synchronize the access to the
Client
andWebTarget
manually. - Let the container manage concurrency by annotating the enclosing type with
@javax.ejb.Singleton
which guarantees thread safety. (see chapter 4.8.5 of the EJB specification)
- 同步访问
Client
和WebTarget
手动。 - 让容器通过注释
@javax.ejb.Singleton
保证线程安全的封闭类型来管理并发。(参见EJB 规范的第 4.8.5 章)
If someMethod
in a container managed environment I would use the second approach.
如果someMethod
在容器管理的环境中,我会使用第二种方法。
回答by Patrick
Since this issue is still open at the time of writing (version 3.0.X) RESTEASY: deprecated Apache classes cleanup
由于在撰写本文时此问题仍然存在(版本 3.0.X)RESTEASY:不推荐使用的 Apache 类清理
You can go deeper to use the newer, non-deprecated classes instead to create you resteasy client. You will also have more control over how you want the pool to be etc.
您可以更深入地使用较新的、未弃用的类来创建您的 resteasy 客户端。您还可以更好地控制池的样式等。
Here is what I did:
这是我所做的:
// 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();
Also make sure you release the connectionafter making a call. Calling response.close() will do that for you so probably put that in a finally block.
还要确保在拨打电话后释放连接。调用 response.close() 会为你做这件事,所以可能把它放在 finally 块中。
回答by Pavel Pscheidl
First, do not reuse WebTarget. For simplicity, you can always create new WebTarget.
首先,不要重复使用 WebTarget。为简单起见,您始终可以创建新的 WebTarget。
Second, if you're using Resteasy, you can add provided dependency for Resteasy client to your project. Example in Gradle:
其次,如果您使用的是 Resteasy,您可以将 Resteasy 客户端提供的依赖项添加到您的项目中。 Gradle 中的示例:
provided 'org.jboss.resteasy:resteasy-client:3.0.14.Final'
Then, you can create your connection like this:
然后,您可以像这样创建连接:
ResteasyClientBuilder builder = new ResteasyClientBuilder();
builder.connectionPoolSize(200);
There is no need to set maxPooledPerRoute, this is set automatically by RestEasy (can be found in RestEasyClientBuilder class source code).
不需要设置maxPooledPerRoute,这是由RestEasy自动设置的(可以在RestEasyClientBuilder类源代码中找到)。
When you set connectionPoolSize, you will no longer get error when Client is reused and you can happily re-use them all across the application. I've tried this solution on many projects and it actually works well. But when you deploy your application to a non-resteasy container (like Glassfish), your code won't work and you will have to use ClientBuilder class again.
当您设置 connectionPoolSize 时,您将不再在 Client 被重用时出错,并且您可以愉快地在整个应用程序中重用它们。我已经在许多项目中尝试过这个解决方案,它实际上运行良好。但是,当您将应用程序部署到非 Resteasy 容器(如 Glassfish)时,您的代码将无法运行,您将不得不再次使用 ClientBuilder 类。