[英]Reusing JAX RS Client in multi-threaded environment (with resteasy)
根據文檔,
“客戶端是管理客戶端通信基礎設施的重量級對象。客戶端實例的初始化和處置可能是一項相當昂貴的操作。因此建議在應用程序中只構造少量的客戶端實例。”
好的,我正在嘗試將 Client 本身和 WebTarget 實例緩存在 static 變量中,在多線程環境中調用 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)你能提供一些例子或文檔嗎?
您的實現不是線程安全的。 當兩個線程同時訪問someMethod
時,它們共享同一個Client
並且當第一個請求未完成時,將嘗試發出第二個請求。
你有兩個選擇:
Client
和WebTarget
的訪問。 @javax.ejb.Singleton
注釋封閉類型來管理並發性,這可以保證線程安全。 (參見EJB規范的第4.8.5章) 如果在容器管理環境中使用someMethod
,我會使用第二種方法。
由於此問題在編寫時仍處於打開狀態(版本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塊中。
首先,不要重用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類。
不幸的是,文檔對於什么可以和不能安全地重用不是很清楚。 如有疑問,請不要重復使用。 但是,如果您決心將開銷降至最低,則可以根據調用的方法安全地重用大多數對象。
從您的代碼開始,這里有一些關於正在發生的事情的評論:
// (1) Store an instance of Client with its own configuration
private static Client client = ClientBuilder.newClient();
// (2) Store an instance of WebTarget with its own configuration (inherited from client)
private static WebTarget webTarget = client.target("someBaseUrl");
...
public static String someMethod(String arg1, String arg2)
{
// (3) New instance of WebTarget (copy entrTarget config) with "arg1" param
// (4) New instance of WebTarget (copy anonymous config) with "arg2" param
WebTarget target = entrTarget.queryParam("arg1", arg1).queryParam("arg2", arg2);
// (5) New instance of Invocation.Builder (copy target config)
// (6) Invoke GET request with stored configuration
Response response = target.request().get();
final String result = response.readEntity(String.class);
response.close();
return result;
}
我按原樣評論了代碼,但我猜(3)
應該引用了 static webTarget
字段。
這里創建了很多對象。 每次創建 object 時,都會有一個新實例及其自己的配置副本(因此不會影響其前身)。 在這種特殊情況下,應該沒有競爭條件,但肯定有一些方法可以 go 出錯。
如果在(3)
之前或之前你做過這樣的事情(假設這些是合法的財產):
WebTarget target = webTarget.property("foo", fooProperty).queryParam("arg1", arg1);
然后您將更改 static webTarget
字段的配置,這可能會導致競爭條件。 有很多方法可以更改 static 字段的配置,因此您要么需要小心保護它們,要么根本不使用它們。
另外,請注意,幾乎每個從原始client
生成的 object 都會引用它,以確定 httpEngine 是否已關閉。 因此,除非您試圖優雅地關閉您的應用程序,否則關閉客戶端可能永遠不是一個好主意。
我通過挖掘源代碼發現了所有這些,因為真的沒有很好的參考。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.