SOAP Web Services mit JAX-WS

Begleitendes Beispiel: Kalkulator

Zur Verdeutlichung der Theorie dient uns in Folge ein einfacher Berechnungsdienst, der mithilfe von JAX-WS als SOAP Web Service realisiert werden soll. Der Kalkulator soll seinen Nutzern die folgenden Funktionen bereitstellen:

  • Addition zweier Zahlen
  • Division zweier Zahlen
  • Anmeldung bei einem mathematischen Newsletter
  • Überprüfung einer Zahl auf Primzahleigenschaft

Quellcodes

Die Quellcodes zum Beispiel des Artikels stehen im Java-Magazin-Archiv zum Download bereit.

Implementierung eines Web Service

Ein jeder SOAP Web Service besteht aus einem SEI (Service Endpoint Interface) und einer SIB (Service Implementation Bean). Beim SEI handelt es sich um ein Interface, bei der SIB um ein POJO, das dieses Interface implementiert und die eigentliche Implementierung enthält. Die SIB kann auch eine Stateless Session Bean sein. Sowohl das Interface als auch die Klasse müssen mit @WebService annotiert werden. Zusätzlich ist in der Klasse das zugehörige Annotationsattribut endpointInterface auf den Namen des SEI zu setzen. Mehr ist bei einfachen Web Services nicht zu tun – Convention over Configuration sei Dank! Optional kann der Entwickler noch die Attribute serviceName und portName mit eigenen Werten belegen. Das muss ausnahmsweise ebenfalls in der Klasse erfolgen. In den meisten Fällen ist ihm aber freigestellt, ob er die Annotationen im Interface oder in der Klasse einfügt. Es empfiehlt sich jedoch, die SIB möglichst annotationsfrei zu halten und diese im SEI anzuführen.

Im Falle unseres Berechnungsdienstes benötigen wir ein Interface CalculatorWS (Listing 1) sowie die zugehörige Klasse CalculatorWSImpl (Listing 2). In beiden sind die Annotationen wie vorhin beschrieben zu setzen. Zusätzlich sind natürlich die Klassen User und CalculatorException erforderlich, auf die wir allerdings nicht näher eingehen wollen. Beim Deployment werden nun gemäß Konvention alle öffentlichen Methoden des SEI – im konkreten Fall sum(), div(), subscribeToNewsletter()und isPrimeNumber()– automatisch unter ihrem Namen als Web-Service-Methoden bereitgestellt.

Listing 1
package de.javamagazin.calculator;

import javax.jws.*;

@WebService
public interface CalculatorWS {
  public long sum(long a, long b);
  public double div(double a, double b) throws CalculatorException;
  public void subscribeToNewsletter(User user);
  public boolean isPrimeNumber(String number) throws CalculatorException;
}
Listing 2
package de.javamagazin.calculator;

import javax.jws.*;

@WebService(endpointInterface="de.javamagazin.calculator.CalculatorWS",
            serviceName="CalculatorWSService",
            portName="CalculatorWSPort")
public class CalculatorWSImpl implements CalculatorWS {
  public long sum(long a, long b) {
    return a + b;
  }

  public double div(double a, double b) throws CalculatorException {
    if (b == 0) {
      throw new CalculatorException("Division by zero!");
    }
    else {
      return a / b;
    }
  }
  ...
}

Bei Bedarf kann der Entwickler mittels der Annotationen @WebMethod, @WebParam und @WebResult auf die gemäß JAX-WS-Konvention vergebenen Namen in der Schnittstellenbeschreibung Einfluss nehmen. So lässt sich beispielsweise die Divisionsmethode im SEI (oder in der SIB) wie folgt annotieren:

@WebMethod(operationName="divide")
public @WebResult(name="quotient") double div(
  @WebParam(name="dividend") double a,
  @WebParam(name="divisor")  double b) throws CalculatorException;

Anstatt der Standardnamen werden in der WSDL nun die individuellen Namen verwendet. Erfolgt der Zugriff eines Clients auf den Kalkulator über einen dynamischen Service Proxy, so haben diese Änderungen keinerlei offensichtliche Auswirkungen. Lässt sich der Programmierer aber die Zugriffsklassen zur Schnittstellenbeschreibung von einem Werkzeug erzeugen, so findet er plötzlich die Methode divide()anstatt div()vor.

Mit @Oneway stellt JAX-WS auch eine Annotation zur Verfügung, um Methoden zur asynchronen Ausführung auszuzeichnen. Ruft ein Client eine solche Operation auf, dann wird diese serverseitig nur angestoßen. Das clientseitige Blockieren bis zu ihrer Beendigung entfällt jedoch. Im Falle unseres Kalkulators empfiehlt es sich, subscribeToNewsletter()dergestalt zu markieren:

@Oneway
public void subscribeToNewsletter(User user);

Diese Vorgehensweise nach dem Fire-and-Forget-Prinzip funktioniert allerdings nur bei Methoden, die kein Ergebnis zurückliefern. Ist dies nicht der Fall, so ist die Generierung entsprechender Zugriffsklassen sicherlich der einfachste Weg. Wie das funktioniert, werden wir uns noch später ansehen.

Die für Java-Komponenten typischen Life-Cycle-Annotationen stehen dem Web-Service-Entwickler ebenfalls zur Verfügung. So ruft das System eine in der SIB mit @PostConstruct ausgezeichnete Methode nach dem Instanziieren des Web Service, eine mit @PreDestoy vor dem Zerstören der Instanz auf. Hier können Initialisierungs- bzw. Aufräumarbeiten erledigt werden. Die SIB unseres Berechnungsdienstes lässt sich damit so erweitern, dass nach dessen Instanziierung automatisch eine Verbindung zum Manager, der dummyweise alle komplexeren Funktionen bereitstellt, aufgebaut wird (Listing 3).

Listing 3
...
public class CalculatorWSImpl implements CalculatorWS {
  private MagicManager manager; 

  @PostConstruct
  public void setup() {
    manager = MagicManager.getInstance();
    manager.connect();
  }
  ...
}

Durch einen Blick auf die im SEI angeführten Methodenköpfe lässt sich erkennen, welche Daten bei einem Methodenaufruf zwischen dem Client und dem Web Service transferiert werden müssen. Nachdem deren Kommunikation mittels SOAP-Nachrichten erfolgt, muss jedes Java-Objekt vor dem Nachrichtenversand in eine adäquate XML-Darstellung gebracht werden (Marshalling). Auf der Empfängerseite läuft der umgekehrte Vorgang ab. Aus dem XML-Konstrukt wird wieder ein Java-Objekt gewonnen (Unmarshalling). Bei der Übermittlung des Ergebnisses sowie einer Ausnahme wird genauso vorgegangen. Dank JAXB (Java Architecture for XML Binding) [3] laufen diese Transformationsprozesse zumeist ohne weiteres Zutun vollkommen automatisch ab. Selbst auf das Annotieren der Klassen mit @XmlRootElement kann der Entwickler bei Metro verzichten. Will er dennoch auf die XML-Darstellung der zu transferierenden Objekte Einfluss nehmen, so stellt JAXB verschiedene Annotationen bereit, mittels derer sich ein bestimmtes Format erzwingen lässt.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.