Ohne die Sinnhaftigkeit von zustandsbehafteten Web Services vorab diskutieren zu wollen sind hier 3 Möglichkeiten genannt solche mit Hilfe von JAX-WS bzw. dessen Referenzimplementierung umzusetzen.
Variante 1: Direkter Zugriff auf die HTTP-Session über den WebServiceContext
Bei einem Web Service, der auf HTTP und SOAP aufsetzt und in einem Servlet-Container läuft, war es natürlich schon immer möglich die HTPP-Session des Servlet-Containers als Zustandsspeicher zu verwenden. Zugriff auf besagte Session bekommt man über den WebServiceContext
, welcher über die Annotation @Resource
injected werden kann. Ein Beispiel zeigt das folgende Listing:
@WebService public class StatefulHello1 implements StatefulHello { @Resource private WebServiceContext context; @WebMethod public int getCount() { MessageContext mc = context.getMessageContext(); HttpServletRequest request = (HttpServletRequest) mc .get(MessageContext.SERVLET_REQUEST); HttpSession session = request.getSession(); Integer count = (Integer) session.getAttribute("count"); if (count == null) { count = new Integer(0); } count = new Integer(count.intValue() + 1); session.setAttribute("count", count); return count; } }
Die Verwaltung der HTTP-Session auf der Serverseite übernimmt dann wie gewohnt der Servlet-Container. Dieser verwendet dafür Cookies oder URL-Rewriting. Damit dies funktioniert muss ein Client ebenfalls eine entsprechende Session-Verwaltung bereitstellen (bzw. mit Cookies umgehen können). Dies kann für den Client am RequestContext
des BindingProvider
über ein Flag aktiviert werden:
StatefulHello port = StatefulHelloClient1.getPort(); BindingProvider bp = (BindingProvider) port; Map<string, Object> ctx = bp.getRequestContext(); ctx.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true); int result = cort.getCount();
Mehr zu dieser Variante findet man in einem Blogartikel von Rama Pulavarthi.
Variante 2: Per Annotation eine Instanz pro HTTP-Session
Die im ersten Beispiel gezeigte Möglichkeit lässt sich mit einer Extension der JAX-WS Referenzimplementierung (ab Version 2.1) auch etwas eleganter ausdrücken:
@HttpSessionScope @WebService public class StatefulHello2 implements StatefulHello { private int count = 0; @WebMethod public int getCount() { return count++; } }
Die Annotation @com.sun.xml.ws.developer.servlet.HttpSessionScope
sorgt dafür, dass die JAX-WS RI pro HTTP-Session eine Instanz des Web Service erzeugt und man nicht selbst über den WebServiceContext
und die HttpSession
den gewünschten Zustand verwalten muss. Möglich wird dies durch einen entsprechenden InstanceResolver
im Hintergrund. Dabei handelt es sich um einen Erweiterungsmechanismus den die JAX-WS RI mitbringt.
Mehr dazu steht in einem Blogartikel von Kohsuke Kawaguchi. Natürlich ist für die erfolgreiche Nutzung dieser Variante ebenfalls ein Client notwendig, der Cookies verwalten kann.
Variante 3: Protokollunabhängig über WS-Adressing
Seit JAX-WS RI 2.1 EA2 unterstützt die Referenzimplementierung einen Mechanismus, der auf WS-Addressing aufsetzt und damit den Anspruch erhebt auch über Herstellergrenzen hinweg kompatibel zu sein und protokollunabhängig zu arbeiten. Dies wird ebenfalls in einem Blogartikel von Kohsuke Kawaguchi erwähnt und findet sich auch im User Guide zu JAX-WS RI wieder.
Im ersten Schritt annotiert man den Web Service wie in folgendem Beispiel und definiert eine öffentliche und statische Referenz vom Typ StatefulWebServiceManager
.
@Stateful @WebService @Addressing public class StatefulHello3 implements StatefulHello { public static StatefulWebServiceManager<statefulHello3> MANAGER; private int count = 0; @WebMethod public int getCount() { return count++; } }
Anschließend bedarf es eines zweiten Web Service, der eine Methode bzw. Operation bereitstellt, die folgendermaßen implementiert ist:
@WebService public class StatefulHello3Factory { @WebMethod public synchronized W3CEndpointReference login() { StatefulHello3 service = new StatefulHello3(); return StatefulHello3.MANAGER.export(service); } }
Über diesen zweiten Web Service kann dann jeweils ein “zustandsbehafteter Endpunkt” erzeugt werden. Der Client für einen solchen muss nun allerdings WS-Addressing unterstützen um die eindeutige ID, welche im Addressing-Header spezifiziert ist, korrekt zwischen den einzelnen Calls an den eigentlichen Web Service zu transportieren.
Fazit
Dass zustandsbehaftete Web Services durchaus kritisch zu sehen sind ist nicht neu. Im Allgemeinen werden Argumente wie mangelnde Skalierbarkeit oder Probleme beim Clustering angebracht. Ich möchte an dieser Stelle nur kurz 2 Quellen dazu nennen:
Im letzten Kommentar einer Forumsdiskussion gibt jemand zu bedenken, dass die 3. Variante es wohl nicht bis in die JAX-WS Spezifikation geschafft hat. Das ist insoweit korrekt, als dass es sich um eine Extension handelt, die dort nicht erwähnt wird. Sie sollte allerdings unter Glassfish/Metro funktionieren (genauso wie Variante 1 & 2).
Daneben ist ein Hinweis bei Oracle (A Note About the JAX-WS RI @Stateful Extension) interessant: Weil es sich um eine herstellerspezifische Erweiterung handelt, werde gerade die 3. Variante nicht unterstützt. Nach meiner eigenen Erfahrung scheint WebLogic in Version 10.3 aktuell nur Variante 1 zu unterstützen.