Dez 26

In einem meiner früheren Artikel habe ich beschrieben, wie man mit Apache CXF, Spring Framework sowie Jetty als embedded HTTP Server einen einfachen Webservice implementiert und bereitstellen kann. Dabei wurden JSR-181 und JAXB Annotations verwendet um die Java Klassen mit entsprechenden Metadaten anzureichern. Dies wird auch als „Implementation-First“ oder „Java-First“ Ansatz bezeichnet. In einem weiteren Artikel wurde gezeigt welche Nachteile dieser Ansatz hat und wie man die Generierung der WSDL-Datei mit Annotations beeinflussen und somit den Webservice Contract optimieren kann.

Dieser Artikel beschreibt das Vorgehen nach dem “Contract-First” bzw. „WSDL-First“ Ansatz. Bei diesem Ansatz wird die Schnittstelle vor der Implementierung in Form einer WSDL-Datei definiert. Die entsprechenden Java Klassen werden dann mit einem Codegenerator aus der WSDL-Datei generiert.

Das hier beschriebene Beispiel basiert auf Apache CXF 2.0.3, JSR-181, JAXB 2.1, Spring Framework 2.5, sowie Jetty 6.1.5. Der komplette Source Code inklusive einem Maven Build Script steht zum Download bereit.

Schritt 1: Anforderungen aufnehmen, dokumentieren und Schnittstelle entwerfen

Der Lebenszyklus für jeden Webservice beginnt mit neuen Anforderungen aus dem Fachbereich. Diese Anforderungen müssen wie bei jedem Softwareentwicklungsprojekt aufgenommen und dokumentiert werden. Anhand dieser Anforderungen kann anschließend eine Schnittstelle für den Service entworfen werden. In unserem Beispiel verwenden wir die Schnittstelle aus dem vorherigen Artikel. Das folgende UML-Diagramm zeigt die Schnittstelle des Services sowie die Datenobjekte mit denen der Service arbeitet:

CustomerService Classdiagram

Wie man dem Klassendiagramm entnehmen kann, soll unser Beispiel-Service eine Methode anbieten, die eine Kundenummer als Inputparameter entgegennimmt und einen Kunden als Output zurück gibt. Die Serviceschnittstelle wurde für dieses Beispiel zur besseren Verständlichkeit natürlich stark vereinfacht.

Schritt 2: Datenstrukturen als XML-Schema definieren

Zunächst müssen die Datenstrukturen als XML-Schema definiert werden. Es ist sinnvoll jede Datenstruktur in einer separaten XSD-Datei abzuspeichern. Auf diese Weise können die Datenstrukturen auch in anderen Services wiederverwendet werden. In unserem Beispiel entstehen folgende vier Schema-Definitionen:

Customer.xsd
Person.xsd
Gender.xsd
Address.xsd

Schritt 3: Service Interface in einer WSDL-Datei definieren

Im nächsten Schritt muss das Service Interface in einer WSDL-Datei beschrieben werden. Die WSDL-Datei inkludiert die XML-Schema Dateien, die die Datenstrukturen beschreiben und definiert die vorhanden Operationen sowie die Input- und Output-Nachrichten:

CustomerService.wsdl

Schritt 4: Java Source Code aus WSDL-Datei definieren

Aus der WSDL-Datei kann nun mit Hilfe eines Genarators Java Code generiert werden. Apache CXF bittet dazu ein entsprechendes WSDL2Java-Tool an, welches sowohl von der Kommandozeile als auch aus Ant und Maven Skripten aufgerufen werden kann. In unserem Beispiel verwenden wir ein Maven Build-Skript und der Codegenerator wird automatisch vor dem Kompilieren aufgerufen. Die Konfiguration für den Codegenerator kann der pom.xml Datei entnommen werden.

Schritt 5: Service Interface implementieren

Nachdem die entsprechenden Java-Klassen für die Datenstrukturen und den Service generiert wurden, muss das Interface implementiert werden. Die Implementierung der Geschäftslogik befindet sich in der Klasse CustomerServiceImpl.java, welche das generierte Interface ContactService implementiert:

Array

Wie man dem Source-Code entnehmen kann, gibt diese Implementierung immer den Kunden „Max Müller“ als Output zurück wenn die Kundennummer „12345“ als Inputparameter übergeben wurde und in allen anderen Fällen wird ein BusinessLogicException mit entsprechender Fehlermeldung geworfen.

Schritt 6: Service bereitstellen

In diesem Beispiel wird der Service mit Hilfe von Jetty über HTTP bereitgestellt. Die Konfiguration der Komponenten erfolgt über das Spring Framework. Die Spring Konfiguration ist in diesem Beispiel in der Datei server-applicationContext.xml enthalten.

server-applicationContext.xml

Über die main-Methode in der Klasse ServiceServer wird der Spring Context initialisiert und somit der Jetty Webcontainer gestartet, welcher die Service Implementierung unter http://localhost:9090/customerService verfügbar macht.

ServiceServer.java

Schritt 7: Client für den Service implementieren

Um den Service anzusprechen und testen zu können, empfiehlt es sich ein Client zu implementieren. Die Implementierung von Clients ist mit Apache CXF 2.0 relativ einfach deklarativ mit Hilfe des Spring Frameworks möglich. Die Spring Konfiguration für den Service Client ist in diesem Beispiel in der Datei client-applicationContext.xml enthalten.

client-applicationContext.xml

Die Klasse ServiceClient enthält eine main-Methode in der Spring Context initialisiert wird und auf das entsprechende Client-Bean zugegriffen wird.

ServiceClient.java

Alternativ eignet sich auch soapUI hervorragend zum Testen von Webservices.

Aug 24

In einem meiner früheren Artikel habe ich erläutert wie man mit Hilfe von Annotations unter Verwendung von JSR-181, JAXB sowie Apache CXF und Spring Framework ein Webservice implementieren kann. Dieser Artikel beschreibt wie man die Generierung der WSDL Datei mit Annotations beeinflussen und somit den Webservice Contract optimieren kann.

Update: Siehe auch Artikel zur WSDL-First Development.

Die Beispiele in diesem Tutorial basieren auf den Source Code aus dem vorangegangem Artikel. Der komplette Source Code aus diesem Artikel inklusive einem Maven Build Script steht zum Download bereit.

Schritt 1: Legen Sie den Namespace fest.

Bei der Generierung der WSDL-Datei verwendet Apache CXF den Java Package Namen als Standard-Namespace. Aus der Klasse Customer, die im Package com.mycompany.customerrelations liegt, wird standardmäßig das Element Customer im Namespace http://customerrelations.mycompany.com/ generiert. Oft ist es jedoch gewünscht dieses Verhalten zu verändern und z.B. zuerst den Domainnamen im Namespace zu verwenden. Der Namespache der Elemente lässt sich über die Annotations @XmlSchema und @WebService verändern. Dabei wird @XmlSchema für Datenstrukturen und @WebService für Service-Interfaces verwendet. Hier ein Beispiel:

@XmlSchema(namespace="http://www.mycompany.com/customerrelations")
@WebService(targetNamespace="http://www.mycompany.com/customerrelations", name="CustomerService")

Schritt 2: Definieren Sie die SOAP-Action für alle Operationen.

Anhand der SOAP-Action Property im Transport-Protokoll wird definiert wie eine SOAP-Nachricht interpretiert werden sollte bzw. welche Operation aufgerufen werden sollte. Apache CXF setzt standardmäßig den SOAP-Action Namen auf einen leeren String, also z.B: <soap:operation soapAction=""/>. Dies hat zur Folge dass die Bedeutung der SOAP-Nachricht anhand von HTTP Request URL interpretiert wird. Bei vielen Protokollen wie z.B. JMS oder SMTP ist diese Information jedoch nicht vorhanden und sollte daher im Header des Transport-Protokolls übergeben werden. Definieren Sie daher für jede Operation den SOAP-Action Namen. Verwenden Sie dazu die Annotation @WebMethod wie z.B.: @WebMethod(action = "getCustomer").

Schritt 3: Bestimmen Sie den Namen für alle Input-Parameter.

In Java kann bei einer kompilierten Klasse der Name eines Parameters aus der Signatur einer Methode nicht bestimmt werden. So ist es zwar möglich den Datentyp und die Reihenfolge der Parameter auszulesen, nicht jedoch den Namen des Parameters. Bei der Generierung der WSDL-Datei verwendet Apache CXF daher Parameter-Namen wie z.B. “arg0″. Für die Methode “public Customer getCustomer(String customerNumber)” im Service-Interface CustomerService wird z.B. folgende Schema-Definition generiert:

Array

Um die SOAP-Nachricht besser lesbar zu machen, sollten daher alle Input-Parameter mit einem Namen versehen werden. Dies kann mit der Annotation @WebParam erfolgen. Hier ein Beispiel:

@WebParam(targetNamespace="", name="customerNummber")

Schritt 4: Legen Sie den Namen für alle Output-Parameter fest.

Das Problem bei Input-Parametern (siehe Schritt 3) gilt natürlich auch für Output-Parameter. Der Name des Parameters lässt sich nicht aus der Java Klasse ermitteln. Apache CXF generiert in diesem Fall bei Response-Nachricht ein Element mit dem Namen “return”. Hier ein Auszug aus der WSDL-Datei:

Array

Die SOAP-Nachrichten sind in diesem Fall natürlich nicht besondern gut lesbar. Verbessern kann man dies mit der Annotation @WebResult. Dies könnte beispielweise so aussehen:

@WebResult(targetNamespace="", name="customers")

Schritt 5: Bestimmen Sie die Pflichtfelder.

Apache CXF definiert standardmäßig alle Elemente in der WSDL-Datei als optional, also z.B.:

<xs:element minOccurs=”0″ name=”lastname” type=”xs:string”/>

Ein guter Webservice Contract sollte jedoch unbedingt notwendige Datenelemente wie z.B. den Nachnamen bei einer Person als Pflichtfeld definieren. Dies kann über die Annotation @XmlElement beeinflusst werden. Ein Beispiel:

@XmlElement(required=true)

Schritt 6: Legen Sie fest, welche Datenelemente “nullable” sind.

In manchen Fällen ist es notwendig Null-Werte für bestimmte Elemente zu übermitteln. Wenn für den Vorname einer Person auch ein Null-Wert übermittelt werden darf, so könnte dies in der SOAP-Nachricht so aussehen:

<firstname xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>

Standardmäßig generiert Apache CXF die WSDL-Datei, dass Null-Werte nicht erlaubt sind. Für Datenelemente für die auch Null-Werte zulässig sind, muss dies daher expliziet definiert werden. Dies kann ebenfalls über die Annotation @XmlElement definiert werden. Hier ein Beispiel:

@XmlElement(required=true, nillable=true)

Schritt 7: Definieren Sie den Datentyp für das XML-Schema.

Bei der Generierung der WSDL-Datei werden Java Datentypen auf bestimmte XML-Schema Datentypen gemappt. Der Java-Datentyp java.lang.String wird beispielweise auf “xsd:string” gemappt. In manchen Fällen ist dieses Mapping jedoch nicht ganz sinnvoll. Der Nachname einer Person sollte beispielweise kein “Carriage Return” oder “Tab” als Zeichen enthalten. Hier wäre der XML-Schema Datentyp “token” die bessere Wahl.

Der Java-Datentyp java.util.Calendar wird standardmäßig auf den XML-Schema Datentyp “xsd:dateTime” gemappt. Beim Geburtsdatum einer Person ist jedoch in der Regel nur das Datum und nicht die Zeit von Bedeutung. Hier wäre der XML-Schema Datentyp “xsd:date” sinnvoller. Der XML-Schema Datentyp kann über die Annotation @XMLSchemaType festgelegt werden. Für das Geburtsdatum einer Person könnte dies folgendermaßen aussehen:

@XmlSchemaType(name="date")

Schritt 8: Legen Sie den Type-Adapter fest.

In manchen Fällen ist es notwendig den Marshalling-Prozess von Datenstrukturen zu beeinflussen. Dies ist mit der Annotation @XmlJavaTypeAdapter und mit der Implementierung eines XMLAdapters möglich. Siehe dazu auch den Artikel in Kohsuke Kawaguchi’s Blog.

Hat man sich beispielsweise entschieden den XML-Schema Datentyp “xsd:token” für ein Datenelement zu verwenden, so sollte der CollapsedStringAdapter für die Deserialisierung des Strings verwendet werden. Hier ein Beispiel dazu:

@XmlJavaTypeAdapter(CollapsedStringAdapter.class)

Schritt 9: Legen Sie die ID bei referenzierten Objekten fest.

Datenelemente über die auf bestimmte Datenstrukturen referenziert werden kann wie z.B. die Kundennummer eines Kunden sollte den XML-Schema Datentyp “xsd:ID” erhalten. Verwenden Sie zu diesem Zweck die Annotation @XmlID.

Schritt 10: Bestimmen Sie die Reihenfolge der Elemente.

Apache CXF definiert standardmäßig die Elemente im XML-Schema sortiert in alphabetischer Reihenfolge. Für die Datenstruktur “Adresse” mit den Datenelementen “line1″, “line2″, “postalCode”, “city” und “country” würde dies bedeuten, dass das Element “city” in der SOAP-Nachricht als erstes Element vor allen anderen Elementen übertragen wird. Die SOAP-Nachricht ist somit schlechter lesbar.

Die Reihenfolge der Datenelemente kann über die Annotation @XmlType beeinflusst werden. Für die Datenstruktur einer Adresse könnte dies beispielweise so aussehen:

@XmlType(name = "Address", propOrder = {
"line1", "line2", postalCode", city", country")

Abschließende Gedanken

Wenn Sie alle 10 Schritte auf Ihren Webservice angewandt haben, so werden Sie mit Apache CXF eine relativ qualitativ hochwertige WSDL-Datei generieren können. Wünschenswert wäre es die XML-Schema Definition für die Datenstrukturen noch detaillierter festlegen zu können und z.B. die minimale oder maximale Länge von Texten mit “minLength” und “maxLength” anzugeben. Nach meinem aktuellen Wissensstand ist dies jedoch mit Annotations momentan noch nicht möglich. Auch die Einschränkung eines String mit Hilfe von Regulären Ausdrücken funktioniert momentan noch nicht über Annotations. Dies spricht gegen die Implementierung von Webservices nach dem “Java Code First”-Ansatz. Ich bin jedoch zuversichtlich dass diese Funktionen bald über den JSR-Prozess spezifiziert und demnächst auch von Apache CXF implementiert werden.

Haben Sie Feedback oder weitere Vorschläge? Ich freue mich über Ihre Kommentare.» » » » » » » » » » »

Aug 15

Dieser Blogbeitrag zeigt wie man mit gängigen Java Frameworks einen einfachen Webservice implementiert. Dabei wird nach dem Ansatz vorgegangen zuerst die Java Klassen statt der WSDL zu entwickeln. Um die Implementierung als Webservice zu deployen und die WSDL automatisch generieren zu können, werden die Java Klassen über Annotations mit den notwendigen Metadaten versehen.

Update: Siehe auch Artikel zur WSDL-First Development.

Das hier beschriebene Beispiel basiert auf Apache CXF 2.0, JSR-181, JAXB 2.1, Spring Framework, sowie Jetty als embedded HTTP Server um den Webservice bereitzustellen.

Der komplette Source Code inklusive einem Maven Build Script steht zum Download bereit.

Schritt 1: Anforderungen aufnehmen, dokumentieren und Schnittstelle entwerfen

Der Start für jeden neuen Webservice sind in der Regel neue Anforderungen aus dem Fachbereich. Diese Anforderungen müssen zunächst einmal aufgenommen und dokumentiert werden. Daraus kann man anschließend eine entsprechende Schnittstelle für den Service entwerfen. In diesem Beispiel wird ein einfacher Service zu Verwaltung von Kunden beschrieben. Das folgende UML-Diagramm zeigt die Schnittstelle des Services sowie die Datenobjekte mit denen der Service arbeitet:

CustomerService Classdiagram

Wie man dem Klassendiagramm entnehmen kann, wird unser Beispiel-Service eine einzige Methode anbieten, die eine Kundenummer als Inputparameter entgegennimmt und einen Kunden als Outputobjekt zurück liefert. Die Serviceschnittstelle wurde für dieses Beispiel natürlich stark vereinfacht.

Schritt 2: Datenstrukturen als Java Klassen implementieren

Die Java Klassen für die Datenstrukturen sind einfache POJOs und enthalten nur die entsprechenden Getter- und Setter-Methoden für die Attribute.

Customer.java
Person.java
Gender.java
Address.java

Schritt 3: Service Interface definieren

Als nächstes muss das Service Interface implementiert werden und mit entsprechenden JSR-181 Annotations versehen werden:

Array

Während die Annotation @WebService unbedingt erforderlich ist, ist die @WebMethod Annotation optional. Für die Nutzung von SOAP over JMS sollte jedoch hier die SOAP-Action angegeben werden.

CustomerService.java
BusinessLogicException.java

Schritt 4: Service Interface implementieren

Die eigentliche Implementierung der Businesslogik findet der Klasse CustomerServiceImpl statt welche das Interface ContactService implementiert:

Array

Wie man dem Source-Code entnehmen kann, gibt diese Implementierung immer den Kunden „Max Müller“ als Output zurück wenn die Kundennummer „12345“ als Inputparameter übergeben wurde und in allen anderen Fällen wird ein BusinessLogicException mit entsprechender Fehlermeldung geworfen.

CustomerServiceImpl.java

Schritt 5: Object-Factory sowie Request- und Response Klassen implementieren

Apache CXF 2.0 verwendet JAXB als Default-Datamapping. Aus diesem Grund ist es notwendig neben den Datenobjekten auch eine Factory für die Erzeugung der Datenobjekten sowie die Request- und Response Klassen zu implementieren.

ObjectFactory.java
GetCustomerRequest.java
GetCustomerResponse.java

Hierbei ist wichtig, dass die Request- und Response Klassen mit @XmlRootElement Annotation versehen sind.

Schritt 6: Service bereitstellen

In diesem Beispiel wird der Service mit Hilfe von Jetty über HTTP bereitgestellt. Die Konfiguration der Komponenten erfolgt über das Spring Framework. Die Spring Konfiguration ist in diesem Beispiel in der Datei server-applicationContext.xml enthalten.

server-applicationContext.xml

Über die main-Methode in der Klasse ServiceServer wird der Spring Context initialisiert und somit der Jetty Webcontainer gestartet, welcher die Service Implementierung unter http://localhost:9090/customerService verfügbar macht.

ServiceServer.java

Schritt 7: Client für den Service implementieren

Um den Service anzusprechen und testen zu können, empfiehlt es sich ein Client zu implementieren. Die Implementierung von Clients ist mit Apache CXF 2.0 relativ einfach deklarativ mit Hilfe des Spring Frameworks möglich. Die Spring Konfiguration für den Service Client ist in diesem Beispiel in der Datei client-applicationContext.xml enthalten.

client-applicationContext.xml

Die Klasse ServiceClient enthält eine main-Methode in der Spring Context initialisiert wird und auf das entsprechende Client-Bean zugegriffen wird.

ServiceClient.java

Alternativ eignet sich auch soapUI hervorragend zum Testen von Webservices.

Schritt 8: Projekt kompilieren und WSDL generieren

Um das Projekt einfach kompilieren zu können habe ich diesem Beispiel ein Maven Build Script beigefügt. Beim Kompileren des Projekts wird auch unter Zuhilfenahme des Java2WSDL Tools von Apache CXF die WSDL-Datei automatisch generiert. Diese WSDL-Datei wird im Verzeichnis /target/generated/wsdl/ abgelegt.» » » » » » » » »