2015-10-13 4 views
10

Gemäß der Dokumentation,Reusing JAX RS-Client in Multi-Threaded-Umgebung (mit Resteasy)

„Die Kunden sind schwere Objekte, die die clientseitige Kommunikationsinfrastruktur. Initialisierung sowie Entsorgung verwalten von eine Client-Instanz eine ziemlich teuere Operation sein kann. Es ist daher nur eine geringe Anzahl von Client-Instanzen in der Anwendung zu konstruieren beraten. "

Ok, ich versuche Client selbst und Web zwischenzuzuspeichern Zielinstanzen in einer statischen Variablen, die irgendeinemethode() in Multi-Threaded-Umgebung aufgerufen:

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; 
} 

Aber manchmal (nicht immer) Ich erhalte eine Ausnahme:

Ungültige Verwendung von BasicClientConnManager: Verbindung noch zugewiesen. Stellen Sie sicher, dass Sie die Verbindung freigeben, bevor Sie eine andere zuweisen.

Wie kann Client/WebTarget korrekt wiederverwendet/zwischengespeichert werden? Ist es mit JAX RS Client API möglich? Oder ich muss einige Framework-spezifische Features verwenden (Resteasy/Jersey) Könnten Sie ein Beispiel oder eine Dokumentation bereitstellen?

+0

Mögliches Duplikat von [Ist JAX-RS Client Thread Safe] (http://stackoverflow.com/questions/24700798/is-jax-rs-client-thread-safe) – tddmonkey

Antwort

5

Ihre Implementierung ist nicht Thread-sicher. Wenn zwei Threads gleichzeitig auf someMethod zugreifen, teilen sie sich Client und einer wird versuchen, eine zweite Anfrage zu machen, während die erste noch nicht fertig ist.

Sie haben zwei Möglichkeiten:

  • den Zugriff auf die manuell Client und WebTarget synchronisieren.
  • Lassen Sie den Container die Nebenläufigkeit verwalten, indem Sie den umschließenden Typ mit @javax.ejb.Singleton annotieren, der Thread-Sicherheit garantiert. (Siehe Kapitel 4.8.5 der EJB specification)

Wenn someMethod in einem Container verwalteten Umgebung ich den zweiten Ansatz verwenden würde.

3

Zuerst WebTarget nicht erneut verwenden. Der Einfachheit halber können Sie immer ein neues WebTarget erstellen.

Zweitens, wenn Sie Resteasy verwenden, können Sie Ihrem Projekt die angegebene Abhängigkeit für den Resteasy-Client hinzufügen. Beispiel in Gradle:

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

Dann können Sie Ihre Verbindung wie folgt erstellen:

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

Es besteht keine Notwendigkeit maxPooledPerRoute gesetzt ist, diese automatisch von Resteasy gesetzt (in RestEasyClientBuilder Klasse Quelle gefunden werden Code).

Wenn Sie connectionPoolSize festlegen, erhalten Sie keinen Fehler mehr, wenn der Client erneut verwendet wird, und Sie können ihn problemlos in der gesamten Anwendung wiederverwenden. Ich habe diese Lösung bei vielen Projekten ausprobiert und es funktioniert wirklich gut.Wenn Sie Ihre Anwendung jedoch in einem nicht resistenten Container (wie Glassfish) bereitstellen, funktioniert Ihr Code nicht und Sie müssen die ClientBuilder-Klasse erneut verwenden.

5

Da diese Frage ist noch offen zum Zeitpunkt des Schreibens (3.0.x) RESTEASY: deprecated Apache classes cleanup

Sie können tiefer gehen die neueren zu verwenden, nicht verworfenen Klassen anstelle von Client-Resteasy zu erstellen. Sie werden auch mehr Kontrolle darüber, wie Sie den Pool wollen usw. sein

Hier ist, was ich getan habe:

// 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(); 

Auch stellen Sie sicher, dass die Verbindung freigeben nach einem Anruf. Der Aufruf von response.close() erledigt das für Sie, also setzen Sie das wahrscheinlich in einen finally-Block.