02.02.2013
InvocationCallback, Async und die Cient-API bei JAX-RS 2.0
In diesem Teil des Tutorials geht es darum, wie auf Client-Seite ein Aufruf asynchron an einen REST-Service abgesetzt werden kann. D.h., durch die Verwendung von async()
und dem InvocationCallback
wird dafür gesorgt, dass der Aufruf eines REST-Services asynchron abläuft und der Client nicht blockiert.
Auf der Abbildung sieht man, wie der Client einen Request an den Service /itemService/itemstring des Servers absetzt. Der Aufruf kehrt direkt zurück. Die eigentliche Response mit dem Ergebnis Item 1 wird erst dann geliefert, wenn sie vorliegt.
Dazu stelle man sich einfach vor, dass der REST-Service eine Methode calculateAnswerWait
anbietet, die 3 Sekunden zur Berechnung des Wertes 42
benötigt.
Der Quellcode dazu sieht folgendermaßen aus:
package org.hameister.itemservice;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
@Path("/itemservice")
public class ItemService {
...
@GET
@Path("calculateAnswerWait")
@Produces("text/plain")
public String calculateAnswerWait() {
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
}
return "42 was calculated by calculateAnswerWait at "+System.currentTimeMillis();
}
}
Wenn diese Methode vom Client auf herkömmlichen Wege aufgerufen wird, dann ist die Client mindestens 3 Sekunden lang blockiert. Um das zu verhindert, gibt es die Möglichkeit ein InvocationCallback
für den Rückgabewert des Methoden-Aufrufs anzulegen und diesem beim Aufruf in der get
-Methode mitzugeben.
Der InvocationCallback
sieht folgendermaßen aus:
InvocationCallback<String> resultCalculatedAnswer = new InvocationCallback<String>() {
@Override
public void completed(String rspns) {
System.out.println("==== Calculated answer: " + rspns);
}
@Override
public void failed(Throwable thrwbl) {
System.out.println("== Something wet wrong ==");
}
};
Der InvocationCallback
ist ein Interface, welches die Methoden completed
und failed
anbietet und die beim Erstellen einer Instanz implementiert werden müssen. Außerdem muss ein Rückgabewert (RESPONSE) festgelegt werden. In dem Beispiel wird ein String
als erwartet.
Dieser InvocationCallback
kann nun einfach beim Aufruf des REST-Service als Parameter der get
-Methode mitgegeben werden.
client.target(SERVER +"/itemservice/calculateAnswerWait").
request("text/plain").async().get(resultCalculatedAnswer);
Wichtig ist auch, dass die Methode async()
an den InvocationBuilder
übergeben wird.
Wenn der Aufruf in der Form abgesetzt wird, dann kehrt die Methode sofort zurück und der Programmfluss wird fortgesetzt. Sobald das Ergebnis von Server zurückkommt, wird der InvocationCallback
aufgerufen und das Ergebnis wird ausgegeben.
Ein JUnit-Test dazu sieht folgendermaßen aus:
package org.hameister.itemservice.test;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientFactory;
import javax.ws.rs.client.InvocationCallback;
import junit.framework.Assert;
import org.hameister.itemservice.Item;
import org.hameister.itemservice.ItemListMessageBodyReader;
import org.hameister.itemservice.ItemListMessageBodyWriter;
import org.hameister.itemservice.ItemMessageBodyReader;
import org.hameister.itemservice.ItemMessageBodyWriter;
import org.junit.After;
import org.junit.AfterClass;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class JAXRESTClientTest {
Client client;
private static final String SERVER = "http://localhost:8080/JAXRSServer/webresources";
public JAXRESTClientTest() {
}
@Before
public void setUp() {
client = ClientFactory.newClient();
}
@After
public void tearDown() {
client.close();
}
...
@Test
public void calculatAnswerAsyncCallback() {
long startTime = System.currentTimeMillis();
InvocationCallback<String> resultCalculatedAnswer = new InvocationCallback<String>() {
@Override
public void completed(String rspns) {
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "==== Calculated answer: {0}", rspns);
Assert.assertTrue("Result must start with 42", rspns.startsWith("42"));
}
@Override
public void failed(Throwable thrwbl) {
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "== Something wet wrong ==");
fail("Async call failed.");
}
};
client.target(SERVER + "/itemservice/calculateAnswerWait").request("text/plain").async().get(resultCalculatedAnswer);
long endTime = System.currentTimeMillis();
Assert.assertTrue("Call must return immediately", (endTime - startTime) < 1000);
}
}
In dem Test wird überprüft, ob der Aufruf "sofort" zurückgekehrt und ob die Response das gewünschte Ergebnis enthält.
Dieser Teil des Tutorial behandelte ausschließlich die Client-Seite. Allerdings gibt es auch die Möglichkeit auf Server-Seite Anpassungen zu machen, die die asynchrone Ausführung von Methoden beeinflussen. Darauf wird im nächsten Teil eingegangen.