02.02.2013

@Suspended, AsyncResponse und Async bei JAX-RS 2.0

In diesem Teil des Tutorials geht es darum, wie auf Server-Seite ein REST-Service erstellt werden, der Aufrufe asynchron abarbeitet, so dass weder der Client blockiert ist, noch dass sich die Anfragen im Server aufstauen. Durch die Verwendung der Annotation @Suspended und dem Parameter AsyncResponse hat man die Möglichkeiten den REST-Service so zu gestalten, dass dies ermöglicht wird.

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.

Im letzten Teil des Tutorial sind wir davon ausgegangen, dass die Ausführung der gesammten Methode auf Server-Seite viel Zeit in Anspruch nimmt. Es kann aber auch der Fall sein, dass nur Teile davon zeitintensiv sind. Diesen zeitintensive Task kann man mit JAX-RS in einen eigenen Thread auslagern, der nach dem Beenden, das Ergebnis direkt an den Client zurückliefert.

Ein Beispiel dafür ist die folgende Methode eines REST-Service, die für die Berechnung eines Werte 3 Sekunden benötigt.

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("calculateAnswer")
    @Produces("text/plain")
    public String calculateAnswer(@Suspended final AsyncResponse res) {
        Executors.newSingleThreadExecutor().submit(new Runnable() {
            @Override
            public void run() {
                //Calculate Result
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException ex) {
                }
                int calcutededAnswer = 21 + 21; // 42
                System.out.println("Caluculated value:" + calcutededAnswer);
                res.resume(calcutededAnswer+" was calculated by calculateAnswer at "+System.currentTimeMillis());
            }
        });
        System.out.println("Do some other stuff in the method...");
        return null; //Is ignored
    }
}

Die Berechnung wird in einen Executors aus dem Package java.util.concurrent ausgelagert, so dass die Ausgabe Do some other stuff in the method... direkt nach dem Aufruf auf der Konsole erscheint.

Die Antwort auf die Anfrage wird im Thread berechnet und anschließend mit resume in die AsyncResponse geschrieben.

Das besondere ist, dass die REST-Service Methode einen Parameter AsyncResponse erhält, der mit der Annotation @Suspended versehen ist. Aus Sicht des Clients, hat die Methode des REST-Service weiterhin keine Parameter!

Der Aufruf aus Client-Sicht sieht nun folgendermaßen aus:

Future<String> sr = client.target(SERVER + "/itemservice/calculateAnswer").
                                request("text/plain").async().get(String.class);
try {
    System.out.println("Future String: "+ sr.get());
} catch (InterruptedException ex) {    
} catch (ExecutionException ex) {
}

Bei Aufruf wird wieder die Methode async() verwendet und der Rückgabewert der Methode ist ein Future-Objekt.

Bei dieser Variante ist der Client solange blockiert, bis das Future-Objekt mit einem Ergebnis gefüllt wurde. Um das zu verhindern, kann wie im vorherigen Teil des Tutorials ein InvocationCallback verwendet werden.