Web Services mit Spring WS

Schritt 3: Domänenmodell und Serviceklasse implementieren

Insgesamt müssen Sie dafür fünf Klassen implementieren. Wir verzichten für dieses Beispiel komplett auf Interfaces. Die vier Domänenklassen sind einfache POJOs mit Gettern und Settern. Diese finden Sie natürlich schon implementiert in dem mitgelieferten Beispiel. Die Serviceklasse implementiert die eigentliche Businesslogik. In einem realen Projekt würde die Servicemethode über Datenzugriffskomponenten auf eine Datenbank zugreifen und die in Frage kommenden Produkte dort abfragen. Für dieses Beispiel habe ich es uns etwas einfacher gemacht und gebe immer eine Liste mit zwei Produkten zurück (Listing 2).

Listing 2
public class ProductService {

  public List findProductsForSupplierAndCategoryInDateRange(
  Category category, Supplier supplier, DateRange dateRange) {
      
  List products = new ArrayList();
    
  products.add(new Product(12345, "Product1", category, supplier));
  products.add(new Product(12346, "Product2", category, supplier));
  return products;
  }
}
Contract First vs. Contract Last

Es gibt zwei Ansätze, wie Web Services entwickelt werden können. Entweder man beginnt, eine WSDL zu definieren und erzeugt aus dieser den benötigten Java-Quellcode (Contract First), oder man schreibt zuerst den Java-Quellcode und erzeugt daraus eine WSDL (Contract Last).

Doch welche Methode ist die bessere? Wie bei vielem kann man das nicht abschließend beantworten. Doch es gibt einige Argumente für den Contract-First-Ansatz. Definieren Sie zuerst die WSDL, dann haben Sie viel mehr Steuerungsmöglichkeiten, um die WSDL zu optimieren. Oft referenzieren Klassen ja andere Klassen, die nicht in der WSDL definiert werden sollen. Dies kann besser in einer WSDL umgangen werden. Ein anderer Punkt ist die Interoperabilität. Beschränkt man sich bei den Datentypen auf die Typen, die in einem XML Schema definiert werden können, dann wird es in den wenigsten Fällen zu Problemen in den verschiedenen Sprachen kommen, die den Web Service später konsumieren. Es gibt noch einige andere Punkte zu diesem Thema, die die Spring-WS-Entwickler unter [5] zusammengetragen haben.

Schritt 3: Domänenmodell und Serviceklasse implementieren

Insgesamt müssen Sie dafür fünf Klassen implementieren. Wir verzichten für dieses Beispiel komplett auf Interfaces. Die vier Domänenklassen sind einfache POJOs mit Gettern und Settern. Diese finden Sie natürlich schon implementiert in dem mitgelieferten Beispiel. Die Serviceklasse implementiert die eigentliche Businesslogik. In einem realen Projekt würde die Servicemethode über Datenzugriffskomponenten auf eine Datenbank zugreifen und die in Frage kommenden Produkte dort abfragen. Für dieses Beispiel habe ich es uns etwas einfacher gemacht und gebe immer eine Liste mit zwei Produkten zurück (Listing 2).

Listing 2
public class ProductService {

  public List findProductsForSupplierAndCategoryInDateRange(
  Category category, Supplier supplier, DateRange dateRange) {
      
  List products = new ArrayList();
    
  products.add(new Product(12345, "Product1", category, supplier));
  products.add(new Product(12346, "Product2", category, supplier));
  return products;
  }
}
Schritt 4: Maven konfigurieren

Bisher haben wir nur die Bordmittel der JVM verwendet. Der nächste Schritt ist allerdings die Implementierung des Endpoints. Der Endpoint ist die Instanz, die die Anfragen von einem Web-Service-Konsumenten beantwortet. In diesem Teil verwenden wir JDOM, um die Anfrage zu analysieren und aus dem SOAP-Body die benötigten Daten zu extrahieren. Außerdem greifen wir zum ersten Mal auf Spring-WS-Funktionen zurück. Um die benötigten Bibliotheken in das Projekt zu integrieren, verwenden wir Maven. Dazu aktivieren wir für unser Projekt das Maven Dependency Management (Rechtsklick auf PROJEKT | MAVEN | ENABLE DEPENDENCY MANAGEMENT). Daraufhin erscheint ein Dialogfenster wie in Abbildung 4 zu sehen ist. Tragen Sie hier die gewünschten Daten ein und klicken auf FINISH.

Abb. 4: Dialog zum Aktivieren des Maven-Dependency-Managements

Um den Endpoint zu implementieren, benötigen wir zuerst nur zwei Abhängigkeiten:

  • spring-ws-core
  • jdom

Mit Maven ist es sehr einfach, diese Abhängigkeit hinzuzufügen. Sie brauchen nur die pom.xml und in dem POM-Editor die Quellansicht zu öffnen (der rechte Tab mit der Beschriftung pom.xml). Dort fügen Sie den Inhalt aus Listing 3 ein.

Listing 3
org.springframework.wsspring-ws-core1.5.8org.jdomjdom1.1

Nun haben Sie alles beisammen, um mit der Implementierung des Endpoints zu beginnen.

Schritt 5: Implementierung des Endpoints

Ein Endpoint wird benötigt, damit die Anfragen beantworten werden können. Der Endpoint muss den SOAP-Request verarbeiten können und auch wissen, was er mit den Daten anfangen soll. Damit Spring WS eine Klasse als Endpoint erkennt, muss diese das Interface org.springframework.ws.server.endpoint.PayloadEndpoint implementieren. Aber Spring wäre nicht Spring (das gilt auch für Spring WS), wenn es da nicht schon was vorbereitet hätte. Es gibt schon einige abstrakte Implementierungen, die Sie direkt verwenden können und die Ihnen viel Arbeit ersparen. Abbildung 5 zeigt die verfügbaren Implementierungen.

Abb. 5: Typhierarchie des Interfaces PayloadEndpoint

Da wir für diesen Artikelteil jDOM verwenden wollen, benutzen wir die AbstractJDomPayloadEndpoint-Implementierung. Die einzige Methode, die wir implementieren müssen, ist die protected Element invokeInternal(Element requestElement) throws Exception {}. Listing 4 zeigt eine erste Implementierung unseres Endpoints. Im Konstruktor des Endpoints definieren wir schon die XPath Expressions, mit denen wir später den Body des SOAP-Requests parsen wollen, um an die notwendigen Daten zu kommen.

Listing 4
public class ProductServiceEndpoint extends AbstractJDomPayloadEndpoint {
  private XPath categoryExpression;
  private Namespace namespace;
  private XPath supplierExpression;
  private XPath dateRangeExpression;

  
  public ProductServiceEndpoint () throws JDOMException {
    namespace = Namespace.getNamespace("prod",
        "http://www.itemis.de/hoa/spring/ws/product");
    categoryExpression = XPath.newInstance("//prod:Category");
    categoryExpression.addNamespace(namespace);

    supplierExpression = XPath.newInstance("//prod:Supplier");
    supplierExpression.addNamespace(namespace);

    dateRangeExpression = XPath.newInstance("//prod:DateRange");
    dateRangeExpression.addNamespace(namespace);

  }

  @Override
  protected Element invokeInternal(Element requestElement){
    return null ;
  }

}

Die Methode invokeInternal bekommt als Parameter den Body des SOAP-Requests. Mit den drei Xpath-Expressions werden die Elemente für die drei Parameter für die Servicemethode ermittelt. Doch halt! Wie rufen wir diese Servicemethode überhaupt auf? Wir haben noch keine Referenz auf die Serviceklasse. Das erledigen wir schnell, indem wir ein Feld und eine zugehörige Setter-Methode für den ProductService definieren. Nachdem dies erledigt ist, können wir uns der Implementierung der invokeInternal-Methode widmen. Der eigentliche Serviceaufruf geschieht mit Java-Mitteln, d. h. aus den Daten des SOAP-Bodys müssen die entsprechenden Objekte erzeugt werden. In Listing 5 finden Sie eine mögliche Lösung dieser Aufgabe.

Listing 5
protected Element invokeInternal(Element requestElement) throws Exception {
  Category category = null;
  Supplier supplier = null;
  DateRange dateRange = null;

  Element categoryElement = (Element) categoryExpression
      .selectSingleNode(requestElement);
  category = new Category();
  category.setId(Integer.parseInt(categoryElement.getChildText("id",
      namespace)));
  category.setName(categoryElement.getChildText("name", namespace));

  Element supplierElement = (Element) supplierExpression
      .selectSingleNode(requestElement);
  supplier = new Supplier();
  supplier.setId(Integer.parseInt(supplierElement.getChildText("id",
      namespace)));
  supplier.setName(supplierElement.getChildText("name", namespace));

  SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yy");
  Element dateRangeElement = (Element) dateRangeExpression
      .selectSingleNode(requestElement);
  dateRange = new DateRange();
    dateRange.setStartDate(dateFormat.parse(dateRangeElement.getChildText(
        "startDate", namespace)));
Kommentare

Schreibe einen Kommentar

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