Der folgende Text beschreibt, wie SOAP Web Services mit JAX-WS erstellt werden können.
Alternativ gibt es zum Beispiel die RESTful Web Services (einen Vergleich finden Sie hier).
Einführungen und allgemeine Informationen zu SOAP-Webservices finden Sie unter:
Speziellere Infos zu JAX-WS finden Sie unter:
Weiteres zu SOAP-Webservices finden Sie unter:
Das folgende Beispiel zeigt eine minimale Implementierung eines SOAP-Webservices inklusive Server und Client.
Legen Sie ein Projektverzeichnis an (z.B. \MeinWorkspace\JaxWsHelloWorld), darunter die Verzeichnisse bin und src, und unter letzterem miniwebservice:
[\MeinWorkspace\JaxWsHelloWorld] |- [bin] '- [src] '- [miniwebservice]
Legen Sie im src\miniwebservice-Verzeichnis die folgenden vier Java-Dateien an.
Dienst-Interface: HalloWelt.java
package miniwebservice; import javax.jws.*; @WebService public interface HalloWelt { public String hallo( @WebParam( name = "wer" ) String wer ); }
Dienstimplementierung: HalloWeltImpl.java
package miniwebservice; import javax.jws.WebService; @WebService( endpointInterface="miniwebservice.HalloWelt" ) public class HalloWeltImpl implements HalloWelt { public String hallo( String wer ) { return "Hallo " + wer; } }
Webservice-Server: TestWsServer.java
package miniwebservice; import javax.xml.ws.Endpoint; public class TestWsServer { public static void main( final String[] args ) { String url = ( args.length > 0 ) ? args[0] : "http://localhost:4434/miniwebservice"; Endpoint.publish( url, new HalloWeltImpl() ); } }
Webservice-Client: TestWsClient.java
package miniwebservice; import java.net.URL; import javax.xml.namespace.QName; import javax.xml.ws.Service; public class TestWsClient { public static void main( final String[] args ) throws Throwable { String url = ( args.length > 0 ) ? args[0] : "http://localhost:4434/miniwebservice"; Service service = Service.create( new URL( url + "?wsdl" ), new QName( "http://miniwebservice/", "HalloWeltImplService" ) ); HalloWelt halloWelt = service.getPort( HalloWelt.class ); System.out.println( "\n" + halloWelt.hallo( args.length > 1 ? args[1] : "" ) ); } }
Die Projektstruktur sieht jetzt so aus:
cd \MeinWorkspace\JaxWsHelloWorld
tree /F
[\MeinWorkspace\JaxWsHelloWorld] |- [bin] '- [src] '- [miniwebservice] |- HalloWelt.java |- HalloWeltImpl.java |- TestWsClient.java '- TestWsServer.java
Java 8:
Bauen Sie das Projekt und starten Sie den Server mit dem Webservice:
cd \MeinWorkspace\JaxWsHelloWorld
javac -d bin src/miniwebservice/*.java
java -cp bin miniwebservice.TestWsServer http://localhost:4434/miniwebservice
Öffnen Sie ein zweites Kommandozeilenfenster und starten Sie den Webservice-Client:
cd \MeinWorkspace\JaxWsHelloWorld
java -cp bin miniwebservice.TestWsClient http://localhost:4434/miniwebservice ich
Java 9 und Java 10:
Da java.xml.ws zu den Java-EE-Modulen gehört, muss es explizit hinzugefügt werden. Sonst erhalten Sie:
error: package javax.jws is not visible
package javax.xml.ws is declared in module java.xml.ws, which is not in the module graph
Bauen Sie das Projekt und starten Sie den Server mit dem Webservice:
cd \MeinWorkspace\JaxWsHelloWorld
javac --add-modules java.xml.ws -d bin src/miniwebservice/*.java
java --add-modules java.xml.ws -cp bin miniwebservice.TestWsServer http://localhost:4434/miniwebservice
Öffnen Sie ein zweites Kommandozeilenfenster und starten Sie den Webservice-Client:
cd \MeinWorkspace\JaxWsHelloWorld
java --add-modules java.xml.ws -cp bin miniwebservice.TestWsClient http://localhost:4434/miniwebservice ich
Sie erhalten:
Hallo ich
Sehen Sie sich die generierte WSDL-Datei und die generierte Schema-XSD-Datei an:
Sie können auch andere URLs beim TestWsServer- und TestWsClient-Aufruf übergeben, beispielsweise http://localhost:4711/xyz.
Beenden Sie den TestWsServer mit "Strg + C".
Falls Sie das Beispiel um Maven erweitern wollen, sehen Sie sich maven.htm#JAX-WS oder die folgenden Beispiele an.
Wenn Sie Webservices genauer untersuchen wollen, empfiehlt sich
soapUI.
Sehen Sie sich die Webservice-Kommunikation an:
Um die folgenden Programmierbeispiele etwas realistischer als das letzte gestalten zu können, erfinden wir eine Bücherverwaltung, auf die über ein DAO (Data Access Object) zugegriffen wird, welches CRUD-Operationen (Create, Read, Update, Delete) ermöglicht. Statt in einer Datenbank speichern wir die Bücher in einer Map. Außerdem wird Maven als Build-Tool verwendet.
Die im Folgenden vorgestellten Programmierbeispiele können Sie auch als Zipdatei downloaden (außer den Libs).
Öffnen Sie ein Kommandozeilenfenster, wechseln Sie in Ihr Projekte-Verzeichnis (z.B. D:\MeinWorkspace) und erzeugen Sie eine der Maven-Konvention entsprechende Verzeichnisstruktur:
cd \MeinWorkspace
md JaxWsTest
cd JaxWsTest
md src\main\java\de\meinefirma\meinprojekt\dao
md src\main\java\de\meinefirma\meinprojekt\ws
md src\test\java\de\meinefirma\meinprojekt
tree /F
Sie erhalten:
[\MeinWorkspace\JaxWsTest] '- [src] |- [main] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | |- [dao] | '- [ws] '- [test] '- [java] '- [de] '- [meinefirma] '- [meinprojekt]
Erstellen Sie im JaxWsTest-Projektverzeichnis die Maven-Projektkonfiguration: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>JaxWsTest</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>${project.artifactId}</name> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.8.1</version> <configuration> <parallel>methods</parallel> <threadCount>10</threadCount> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.0.1</version> <scope>test</scope> </dependency> </dependencies> </project>
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\dao-Verzeichnis die DAO-Klasse: BuecherCrudDAO.java
package de.meinefirma.meinprojekt.dao; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import de.meinefirma.meinprojekt.ws.*; /** DAO (Data Access Object) fuer CRUD-Operationen (Create, Read, Update, Delete) */ public class BuecherCrudDAO { // Map als Datenbank-Simulation: private Map<Long,BuchDO> buecherPool = new ConcurrentHashMap<Long,BuchDO>(); private static final BuecherCrudDAO INSTANCE = new BuecherCrudDAO(); private static final long DFLT_ID = 4710; private BuecherCrudDAO() { } public static BuecherCrudDAO getInstance() { return INSTANCE; } // Neues Buch hinzufuegen: public BuchDO createBuch( BuchDO bu ) throws DuplicateCreateException { synchronized( buecherPool ) { if( bu.getIsbn() != null ) { if( getBuchByIsbn( bu.getIsbn() ) != null ) throw new DuplicateCreateException( "Fehler: Es gibt bereits ein Buch mit der ISBN " + bu.getIsbn() + ".", null ); } else { long maxIsbn = ( buecherPool.size() > 0 ) ? Collections.max( buecherPool.keySet() ).longValue() : DFLT_ID; bu.setIsbn( Long.valueOf( ++maxIsbn ) ); } buecherPool.put( bu.getIsbn(), bu ); return bu; } } // Finde Buch: public BuchDO getBuchByIsbn( Long isbn ) { return ( isbn == null ) ? null : buecherPool.get( isbn ); } // Finde Buecher: public List<BuchDO> findeBuecher( Long isbn, String titel ) { List<BuchDO> resultList = new ArrayList<BuchDO>(); List<BuchDO> snapshotList; if( isbn == null && isEmpty( titel ) ) return Collections.unmodifiableList( new ArrayList<BuchDO>( buecherPool.values() ) ); if( isbn != null && isEmpty( titel ) ) { BuchDO bu = getBuchByIsbn( isbn ); if( bu != null ) resultList.add( bu ); return resultList; } synchronized( buecherPool ) { snapshotList = new ArrayList<BuchDO>( buecherPool.values() ); } String titelLC = titel.trim().toLowerCase(); for( BuchDO bu : snapshotList ) if( (isbn != null && bu.getIsbn() != null && isbn.equals( bu.getIsbn() )) || (!isEmpty( bu.getTitel() ) && bu.getTitel().trim().toLowerCase().contains( titelLC )) ) resultList.add( bu ); return resultList; } // Daten eines per ISBN definierten Buches aendern: public BuchDO updateBuchByIsbn( Long isbn, String titel, Double preis ) { synchronized( buecherPool ) { BuchDO bu = buecherPool.get( isbn ); if( bu == null ) throw new RuntimeException( "Fehler: Es gibt kein Buch mit der ISBN " + isbn + "." ); bu.setTitel( titel ); bu.setPreis( preis ); buecherPool.put( bu.getIsbn(), bu ); return bu; } } // Per ISBN definiertes Buch loeschen: public BuchDO deleteBuchByIsbn( Long isbn ) { synchronized( buecherPool ) { return buecherPool.remove( isbn ); } } private static boolean isEmpty( String s ) { return s == null || s.trim().length() <= 0; } }
Webservice-relevante Klassen speichern wir im ...ws-Package. Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\ws-Verzeichnis die DO-Klasse: BuchDO.java
package de.meinefirma.meinprojekt.ws; /** Buch-Domain-Objekt */ public class BuchDO { private Long isbn; private String titel; private Double preis; public Long getIsbn() { return isbn; } public String getTitel() { return titel; } public Double getPreis() { return preis; } public void setIsbn( Long isbn ) { this.isbn = isbn; } public void setTitel( String titel ) { this.titel = titel; } public void setPreis( Double preis ) { this.preis = preis; } }
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\ws-Verzeichnis die Exception-Klasse: DuplicateCreateException.java
package de.meinefirma.meinprojekt.ws; /** Exception, falls Create mit vorhandener ISBN versucht wird */ public class DuplicateCreateException extends Exception { private static final long serialVersionUID = 1L; // (der dummy-Parameter wird erst spaeter benoetigt) public DuplicateCreateException( String err, Object dummy ) { super( err ); } }
Erzeugen Sie im src\test\java\de\meinefirma\meinprojekt-Verzeichnis die Testklasse: BuecherCrudDAOTest.java
package de.meinefirma.meinprojekt; import static org.testng.Assert.*; import org.testng.annotations.Test; import java.util.List; import de.meinefirma.meinprojekt.dao.BuecherCrudDAO; import de.meinefirma.meinprojekt.ws.BuchDO; public class BuecherCrudDAOTest { private static final BuecherCrudDAO dao = BuecherCrudDAO.getInstance(); private static final int ANZAHL_BUECHER = 1000; private static final long START_ISBN = 2000000000L; static { for( int i = 0; i < ANZAHL_BUECHER; i++ ) { BuchDO bu = new BuchDO(); bu.setIsbn( Long.valueOf( START_ISBN + i ) ); bu.setTitel( "Buch " + i ); bu.setPreis( new Double( i ) ); try { dao.createBuch( bu ); } catch( Exception ex ) { throw new RuntimeException( ex ); } } } @Test public void testCreateBuchDuplicateCreateException() { try { BuchDO bu = new BuchDO(); bu.setIsbn( Long.valueOf( START_ISBN + ANZAHL_BUECHER - 1 ) ); bu.setTitel( "Buch" ); bu.setPreis( new Double( 0 ) ); dao.createBuch( bu ); fail(); } catch( Exception ex ) { } } @Test public void testGetBuchByIsbn() { BuchDO bu = dao.getBuchByIsbn( null ); assertNull( bu ); bu = dao.getBuchByIsbn( Long.valueOf( -1 ) ); assertNull( bu ); bu = dao.getBuchByIsbn( Long.valueOf( START_ISBN + ANZAHL_BUECHER / 2 ) ); assertNotNull( bu ); } @Test public void testFindeBuecher() { List<BuchDO> buecher = dao.findeBuecher( null, null ); assertTrue( buecher != null && buecher.size() >= ANZAHL_BUECHER ); buecher = dao.findeBuecher( null, "buch" ); assertTrue( buecher != null && buecher.size() >= ANZAHL_BUECHER ); buecher = dao.findeBuecher( Long.valueOf( -1 ), "" ); assertEquals( 0, buecher.size() ); buecher = dao.findeBuecher( Long.valueOf( START_ISBN ), null ); assertEquals( 1, buecher.size() ); } @Test public void testUpdateBuchByIsbn() { Long isbn = Long.valueOf( START_ISBN + ANZAHL_BUECHER / 2 ); BuchDO bu = dao.getBuchByIsbn( isbn ); String titel1 = bu.getTitel(); dao.updateBuchByIsbn( isbn, "Buch mit neuem Titel", new Double( 0 ) ); bu = dao.getBuchByIsbn( isbn ); assertFalse( titel1.equals( bu.getTitel() ) ); } @Test public void testDeleteBuchByIsbn() throws Exception { BuchDO bu = dao.deleteBuchByIsbn( Long.valueOf( -1 ) ); assertNull( bu ); Long isbn = Long.valueOf( START_ISBN + ANZAHL_BUECHER ); bu = new BuchDO(); bu.setIsbn( isbn ); bu.setTitel( "Buch" ); bu.setPreis( new Double( 0 ) ); dao.createBuch( bu ); List<BuchDO> buecher = dao.findeBuecher( null, null ); assertTrue( buecher.size() >= ANZAHL_BUECHER + 1 ); assertNotNull( dao.getBuchByIsbn( isbn ) ); assertNotNull( dao.deleteBuchByIsbn( isbn ) ); assertNull( dao.getBuchByIsbn( isbn ) ); } }
Bitte beachten Sie, dass die Testmethoden so ausgelegt sind, dass sie in beliebiger Reihenfolge und auch parallel ausgeführt werden können.
Die Projektstruktur sieht jetzt so aus:
cd \MeinWorkspace\JaxWsTest
tree /F
[\MeinWorkspace\JaxWsTest] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | |- [dao] | | | '- BuecherCrudDAO.java | | '- [ws] | | |- BuchDO.java | | '- DuplicateCreateException.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | '- BuecherCrudDAOTest.java '- pom.xml
Führen Sie die Unittests zur BuecherCrudDAO-Klasse aus:
cd \MeinWorkspace\JaxWsTest
mvn test
Sie erhalten die Meldung, dass 5 Tests fehlerfrei ausgeführt wurden.
Wenn Sie den BuecherCrudDAOTest in Eclipse als Test ausführen wollen, müssen Sie das TestNG-Eclipse-Plug-in installieren, siehe http://testng.org/doc/eclipse.html und http://beust.com/eclipse/.
Die soeben erstellte Bücherverwaltung soll um einen Webservice erweitert werden.
Fügen Sie im Webservice-Verzeichnis src\main\java\de\meinefirma\meinprojekt\ws folgende drei Klassen hinzu.
Transferobjekt (bitte beachten Sie die @XmlRootElement- und @XmlElement-Annotationen): BuecherTO.java
package de.meinefirma.meinprojekt.ws; import java.util.*; import javax.xml.bind.annotation.*; /** Rueckgabe-Transferobjekt */ @XmlRootElement public class BuecherTO { @XmlElement(nillable = true) private List<BuchDO> results = new ArrayList<BuchDO>(); private String message; private Integer returncode; public List<BuchDO> getResults() { return results; } public String getMessage() { return message; } public Integer getReturncode() { return returncode; } public void setMessage( String message ) { this.message = message; } public void setReturncode( Integer returncode ) { this.returncode = returncode; } }
Dienst-Interface (bitte beachten Sie die @WebService- und @WebParam-Annotationen): BuecherServiceIntf.java
package de.meinefirma.meinprojekt.ws; import javax.jws.*; /** Dienst-Interface */ @WebService public interface BuecherServiceIntf { BuecherTO createBuch( @WebParam( name = "buch" ) BuchDO buch ) throws Exception; BuecherTO getBuchByIsbn( @WebParam( name = "isbn" ) Long isbn ); BuecherTO findeBuecher( @WebParam( name = "buch" ) BuchDO buch ); }
Dienstimplementierung: BuecherServiceImpl.java
package de.meinefirma.meinprojekt.ws; import java.util.List; import javax.jws.WebService; import de.meinefirma.meinprojekt.dao.BuecherCrudDAO; /** Dienstimplementierung */ @WebService( endpointInterface="de.meinefirma.meinprojekt.ws.BuecherServiceIntf" ) public class BuecherServiceImpl implements BuecherServiceIntf { public static final Integer RET_CODE_OK = Integer.valueOf( 0 ); public static final Integer RET_CODE_ERROR = Integer.valueOf( 1 ); private BuecherCrudDAO dao = BuecherCrudDAO.getInstance(); @Override public BuecherTO createBuch( BuchDO bu ) throws DuplicateCreateException { bu = dao.createBuch( bu ); return erzeugeBuecherTO( "Buch hinzugefuegt (mit ISBN " + bu.getIsbn() + ")", bu ); } @Override public BuecherTO getBuchByIsbn( Long isbn ) { return erzeugeBuecherTO( "Buch mit ISBN " + isbn, dao.getBuchByIsbn( isbn ) ); } @Override public BuecherTO findeBuecher( BuchDO bu ) { BuecherTO buecherTO = new BuecherTO(); List<BuchDO> buecherListe = dao.findeBuecher( bu.getIsbn(), bu.getTitel() ); if( buecherListe == null || buecherListe.size() == 0 ) return fehlerBuecherTO( "Keine passenden Buecher gefunden." ); if( bu.getIsbn() == null && isEmpty( bu.getTitel() ) ) { buecherTO.setMessage( buecherListe.size() + " Buecher" ); } else { StringBuffer sb = new StringBuffer(); sb.append( buecherListe.size() + " Ergebnis(se) fuer" ); if( bu.getIsbn() != null ) sb.append( " ISBN = " + bu.getIsbn() ); if( !isEmpty( bu.getTitel() ) ) sb.append( " Titel = " + bu.getTitel() ); buecherTO.setMessage( sb.toString() ); } buecherTO.getResults().addAll( buecherListe ); buecherTO.setReturncode( RET_CODE_OK ); return buecherTO; } private static BuecherTO erzeugeBuecherTO( String msg, BuchDO buchDO ) { if( buchDO == null ) return fehlerBuecherTO( "Fehler: Buchobjekt fehlt (" + msg + ")." ); BuecherTO buecherTO = new BuecherTO(); buecherTO.getResults().add( buchDO ); buecherTO.setMessage( msg ); buecherTO.setReturncode( RET_CODE_OK ); return buecherTO; } private static BuecherTO fehlerBuecherTO( String msg ) { BuecherTO buecherTO = new BuecherTO(); buecherTO.setMessage( msg ); buecherTO.setReturncode( RET_CODE_ERROR ); return buecherTO; } private static boolean isEmpty( String s ) { return s == null || s.trim().length() <= 0; } }
Fügen Sie im src\main\java\de\meinefirma\meinprojekt-Verzeichnis folgende zwei Klassen hinzu.
Wie das obige Beispiel bereits gezeigt hat, kann mit Endpoint.publish() sehr einfach ein kleiner Webserver für einen Webservice erstellt werden.
Testserver für den Webservice: TestWsServer.java
package de.meinefirma.meinprojekt; import javax.swing.JOptionPane; import javax.xml.ws.Endpoint; import de.meinefirma.meinprojekt.ws.BuecherServiceImpl; /** Testserver für den Webservice */ public class TestWsServer { public static void main( final String[] args ) { String url = ( args.length > 0 ) ? args[0] : "http://localhost:4434/buecherservice"; Endpoint ep = Endpoint.publish( url, new BuecherServiceImpl() ); JOptionPane.showMessageDialog( null, "TestWsServer beenden" ); ep.stop(); } }
Wie im obigen Beispiel gezeigt, sind für den Zugriff auf den Webservice nur zwei Zeilen notwendig:
Service service = Service.create( new URL( url + "?wsdl" ), new QName( namespaceURI, localPart ) );
buecherService = service.getPort( BuecherServiceIntf.class );
Die folgende Testclient-Klasse ist trotzdem etwas umfangreicher, weil wir auch ein paar Performancemessungen unterbringen wollen.
Testclient für den Webservice: TestWsClient.java
package de.meinefirma.meinprojekt; import java.net.*; import java.text.DecimalFormat; import java.util.Random; import javax.xml.namespace.QName; import javax.xml.ws.*; import de.meinefirma.meinprojekt.ws.*; /** Testclient für den Webservice */ public class TestWsClient { public static void main( final String[] args ) throws Exception { String url = ( args.length > 0 ) ? args[0] : "http://localhost:4434/buecherservice"; int anzahlBuecher = ( args.length > 1 ) ? Integer.parseInt( args[1] ) : 10; test( "TestWsClient", url, 1000000000L, anzahlBuecher, false ); } public static int test( String testName, String url, long startIsbn, int anzahlBuecher, boolean trace ) throws Exception { // Zugriff auf den Webservice vorbereiten: BuecherServiceIntf buecherService; if( url.equalsIgnoreCase( "direkt" ) ) { buecherService = new BuecherServiceImpl(); } else { System.out.println( testName + ": " + url ); Service service = null; int timeoutSekunden = 20; while( service == null ) { try { service = Service.create( new URL( url + "?wsdl" ), new QName( "http://ws.meinprojekt.meinefirma.de/", "BuecherServiceImplService" ) ); } catch( WebServiceException ex ) { if( timeoutSekunden-- <= 0 ) throw ex; try { Thread.sleep( 1000 ); } catch( InterruptedException e ) {/*ok*/} } } buecherService = service.getPort( BuecherServiceIntf.class ); } // Anlage von Buechern: if( trace ) System.out.println( testName + ": Starte Anlage von " + anzahlBuecher + " Buechern" ); long startZeit = System.nanoTime(); for( int i = 0; i < anzahlBuecher; i++ ) { BuchDO bu = new BuchDO(); bu.setIsbn( Long.valueOf( startIsbn + i ) ); bu.setTitel( "Buch " + i ); bu.setPreis( new Double( i ) ); buecherService.createBuch( bu ); } String s1 = "\nAnlage von " + anzahlBuecher + " Buechern dauert: " + ermittleDauer( startZeit ); // Auslesen von Buechern in einzelnen Lesevorgaengen: if( trace ) System.out.println( testName + ": Starte einzelnes Auslesen" ); startZeit = System.nanoTime(); for( int i = 0; i < anzahlBuecher; i++ ) { Long isbn = Long.valueOf( startIsbn + (new Random()).nextInt( anzahlBuecher ) ); BuecherTO buTO = buecherService.getBuchByIsbn( isbn ); if( buTO == null || buTO.getResults() == null || buTO.getResults().size() != 1 ) { throw new RuntimeException( "Fehler beim Auslesen des Buches mit der ISBN " + isbn ); } } String s2 = "\nEinzelnes Auslesen von " + anzahlBuecher + " Buechern dauert: " + ermittleDauer( startZeit ); // Auslesen aller Buecher in einem Lesevorgang: if( trace ) System.out.println( testName + ": Starte gemeinsames Auslesen" ); startZeit = System.nanoTime(); BuecherTO buTO = buecherService.findeBuecher( new BuchDO() ); String s3 = "\nGemeinsames Auslesen von " + buTO.getResults().size() + " Buechern dauert: " + ermittleDauer( startZeit ); String s0 = ""; if( buTO.getResults().size() < 30 ) s0 = zeigeErgebnis( buTO ); System.out.println( testName + ": " + s0 + s1 + s2 + s3 + "\n" ); return buTO.getResults().size(); } static String zeigeErgebnis( BuecherTO buTO ) { StringBuffer sb = new StringBuffer(); sb.append( "\n" + buTO.getMessage() + "\n" ); for( BuchDO bu : buTO.getResults() ) sb.append( " Buch (ISBN=" + bu.getIsbn() + ", Titel=" + bu.getTitel() + ", Preis=" + bu.getPreis() + ")\n" ); sb.append( " Returncode " + buTO.getReturncode() + "\n" ); return sb.toString(); } static String ermittleDauer( long startZeitNanoSek ) { long dauerMs = (System.nanoTime() - startZeitNanoSek) / 1000 / 1000; if( dauerMs < 1000 ) return "" + dauerMs + " ms"; return (new DecimalFormat( "#,##0.00" )).format( dauerMs / 1000. ) + " s"; } }
Fügen Sie noch eine Unittestklasse im Verzeichnis src\test\java\de\meinefirma\meinprojekt hinzu: BuecherServiceImplTest.java
package de.meinefirma.meinprojekt; import static org.testng.Assert.*; import org.testng.annotations.Test; import de.meinefirma.meinprojekt.ws.*; public class BuecherServiceImplTest { private static final BuecherServiceIntf buecherService = new BuecherServiceImpl(); private static final int ANZAHL_BUECHER = 1000; private static final long START_ISBN = 3000000000L; static { for( int i = 0; i < ANZAHL_BUECHER; i++ ) { BuchDO bu = new BuchDO(); bu.setIsbn( Long.valueOf( START_ISBN + i ) ); bu.setTitel( "Buch " + i ); bu.setPreis( new Double( i ) ); try { buecherService.createBuch( bu ); } catch( Exception ex ) { throw new RuntimeException( ex ); } } } @Test public void testCreateBuchDuplicateCreateException() { try { BuchDO bu = new BuchDO(); bu.setIsbn( Long.valueOf( START_ISBN + ANZAHL_BUECHER - 1 ) ); bu.setTitel( "Buch" ); bu.setPreis( new Double( 0 ) ); buecherService.createBuch( bu ); fail(); } catch( Exception ex ) { } } @Test public void testGetBuchByIsbn() { BuecherTO buTO = buecherService.getBuchByIsbn( null ); assertEquals( 0, buTO.getResults().size() ); buTO = buecherService.getBuchByIsbn( Long.valueOf( -1 ) ); assertEquals( 0, buTO.getResults().size() ); buTO = buecherService.getBuchByIsbn( Long.valueOf( START_ISBN + ANZAHL_BUECHER / 2 ) ); assertEquals( 1, buTO.getResults().size() ); } @Test public void testFindeBuecher() { BuchDO bu = new BuchDO(); BuecherTO buTO = buecherService.findeBuecher( bu ); assertTrue( buTO.getResults() != null && buTO.getResults().size() >= ANZAHL_BUECHER ); bu.setTitel( "buch" ); buTO = buecherService.findeBuecher( bu ); assertTrue( buTO.getResults() != null && buTO.getResults().size() >= ANZAHL_BUECHER ); bu.setTitel( null ); bu.setIsbn( Long.valueOf( -1 ) ); buTO = buecherService.findeBuecher( bu ); assertEquals( 0, buTO.getResults().size() ); bu.setIsbn( Long.valueOf( START_ISBN ) ); buTO = buecherService.findeBuecher( bu ); assertEquals( 1, buTO.getResults().size() ); } }
Die Projektstruktur sieht jetzt so aus:
cd \MeinWorkspace\JaxWsTest
tree /F
[\MeinWorkspace\JaxWsTest] |- [src] | |- [main] | | '- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | |- TestWsClient.java | | |- TestWsServer.java | | |- [dao] | | | '- BuecherCrudDAO.java | | '- [ws] | | |- BuchDO.java | | |- BuecherServiceImpl.java | | |- BuecherServiceIntf.java | | |- BuecherTO.java | | '- DuplicateCreateException.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinprojekt] | |- BuecherCrudDAOTest.java | '- BuecherServiceImplTest.java '- pom.xml
Führen Sie die Unittests zur BuecherServiceImpl-Klasse aus:
cd \MeinWorkspace\JaxWsTest
mvn test
Starten Sie den Server mit dem Webservice in einem neuen Kommandozeilenfenster:
start java -cp target/classes de.meinefirma.meinprojekt.TestWsServer http://localhost:4434/buecherservice
Starten Sie im anderen Kommandozeilenfenster den Webservice-Client:
java -cp target/classes de.meinefirma.meinprojekt.TestWsClient http://localhost:4434/buecherservice 10
Sie erhalten eine Liste von 10 zufällig ausgewählten Büchern.
Sehen Sie sich die generierte WSDL-Datei und die generierte Schema-XSD-Datei an:
Das letzte Programmierbeispiel basiert auf dem simplen Endpoint-Webserver. Es wird jetzt so erweitert, dass der Webservice zusätzlich in beliebigen JEE-Webservern (oder Java EE Application Servern) deployt werden kann. Zu beiden Betriebsarten werden Integrationstests hinzugefügt.
Damit eine WAR-Datei für das Deployment erzeugt wird, und damit parallele Tests mit TestNG und Integrationstests mit Jetty durchgeführt werden können, ersetzen Sie den Inhalt der pom.xml durch:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma.meinprojekt</groupId> <artifactId>JaxWsTest</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>${project.artifactId}</name> <build> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.26</version> <configuration> <scanIntervalSeconds>10</scanIntervalSeconds> <stopPort>9999</stopPort> <stopKey>geheim</stopKey> <connectors> <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"> <port>4433</port> <maxIdleTime>60000</maxIdleTime> </connector> </connectors> </configuration> <executions> <execution> <id>start-jetty</id> <phase>pre-integration-test</phase> <goals> <goal>run</goal> </goals> <configuration> <scanIntervalSeconds>0</scanIntervalSeconds> <daemon>true</daemon> </configuration> </execution> <execution> <id>stop-jetty</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.8.1</version> <configuration> <parallel>methods</parallel> <threadCount>10</threadCount> <excludes> <exclude>**/integrationstest/*Test.java</exclude> </excludes> </configuration> <executions> <execution> <id>integration-tests</id> <phase>integration-test</phase> <goals> <goal>test</goal> </goals> <configuration> <skip>false</skip> <excludes> <exclude>none</exclude> </excludes> <includes> <include>**/integrationstest/*Test.java</include> </includes> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.0.1</version> <scope>test</scope> </dependency> </dependencies> </project>
Erläuterungen zu Integrationstests mit Maven finden Sie unter maven.htm.
Erzeugen Sie im src\main-Verzeichnis das Unterverzeichnis webapp, darunter das Verzeichnis WEB-INF und darunter lib:
md src\main\webapp\WEB-INF\lib
Erzeugen Sie im src\main\webapp\WEB-INF-Verzeichnis die beiden folgenden XML-Dateien:
web.xml
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>Mein Webservice</display-name> <listener> <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class> </listener> <servlet> <servlet-name>JaxWsServlet</servlet-name> <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>JaxWsServlet</servlet-name> <url-pattern>/ws/*</url-pattern> </servlet-mapping> </web-app>
sun-jaxws.xml
<endpoints version="2.0" xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"> <endpoint name="BuecherService" implementation="de.meinefirma.meinprojekt.ws.BuecherServiceImpl" url-pattern="/ws/BuecherService" /> </endpoints>
Erzeugen Sie im src\test\java-Verzeichnis das Unterverzeichnis integrationstest und darin noch zwei Integrationstests:
WsMitEndpointIntegrTest.java
package integrationstest; import static org.testng.Assert.assertEquals; import org.testng.annotations.Test; import javax.xml.ws.Endpoint; import de.meinefirma.meinprojekt.TestWsClient; import de.meinefirma.meinprojekt.ws.BuecherServiceImpl; public class WsMitEndpointIntegrTest { @Test public void testWsMitEndpoint() throws Exception { String url = "http://localhost:4434/buecherverwaltung"; int anzahlBuecher = 100; Endpoint ep = Endpoint.publish( url, new BuecherServiceImpl() ); int anzahlBuecherResult = TestWsClient.test( "WsMitEndpointIntegrTest", url, 4000000000L, anzahlBuecher, true ); ep.stop(); assertEquals( anzahlBuecherResult, anzahlBuecher ); } }
WsMitJettyIntegrTest.java
package integrationstest; import static org.testng.Assert.assertEquals; import org.testng.annotations.Test; import de.meinefirma.meinprojekt.TestWsClient; public class WsMitJettyIntegrTest { @Test public void testWsMitJetty() throws Exception { String url = "http://localhost:4433/JaxWsTest/ws/BuecherService"; int anzahlBuecher = 100; assertEquals( TestWsClient.test( "WsMitJettyIntegrTest", url, 5000000000L, anzahlBuecher, true ), anzahlBuecher ); } }
Die Projektstruktur sieht jetzt so aus:
cd \MeinWorkspace\JaxWsTest
tree /F
[\MeinWorkspace\JaxWsTest] |- [src] | |- [main] | | |- [java] | | | '- [de] | | | '- [meinefirma] | | | '- [meinprojekt] | | | |- TestWsClient.java | | | |- TestWsServer.java | | | |- [dao] | | | | '- BuecherCrudDAO.java | | | '- [ws] | | | |- BuchDO.java | | | |- BuecherServiceImpl.java | | | |- BuecherServiceIntf.java | | | |- BuecherTO.java | | | '- DuplicateCreateException.java | | '- [webapp] | | '- [WEB-INF] | | |- [lib] | | | '- ... (eventuell .jar-Libs aus JAXWS2.2-*.zip) | | |- sun-jaxws.xml | | '- web.xml | '- [test] | '- [java] | |- [de] | | '- [meinefirma] | | '- [meinprojekt] | | |- BuecherCrudDAOTest.java | | '- BuecherServiceImplTest.java | '- [integrationstest] | |- WsMitEndpointIntegrTest.java | '- WsMitJettyIntegrTest.java '- pom.xml
Unittests:
Führen Sie die Unittests aus:
cd \MeinWorkspace\JaxWsTest
mvn test
Manueller Test des Webservices mit Jetty:
Bauen Sie das Projekt (dabei wird JaxWsTest-1.0-SNAPSHOT.war erstellt):
mvn clean package
Starten Sie den Jetty-Webserver mit dem Webservice und warten Sie bis "Started Jetty Server" erscheint:
start cmd /C mvn jetty:run
(Falls Sie eine ClassNotFoundException erhalten: Sehen Sie sich bitte weiter unten den Hinweis hierzu an.)
Starten Sie den Webservice-Client (beispielsweise mit dem Parameter 20):
java -cp target/classes de.meinefirma.meinprojekt.TestWsClient http://localhost:4433/JaxWsTest/ws/BuecherService 20
Sie erhalten eine Liste von 20 zufällig ausgewählten Büchern.
Sehen Sie sich die generierte WSDL-Datei und die generierte Schema-XSD-Datei an:
start http://localhost:4433/JaxWsTest/ws/BuecherService?wsdl
start http://localhost:4433/JaxWsTest/ws/BuecherService?xsd=1
Beenden Sie den Jetty-Webserver:
mvn jetty:stop
Integrationstests:
Führen Sie testweise den Integrationstest mit dem einfachen Endpoint-Server aus:
mvn test -Dtest=integrationstest.WsMitEndpointIntegrTest
Führen Sie testweise den Integrationstest mit dem Jetty-Webserver aus:
start cmd /C mvn jetty:run
mvn test -Dtest=integrationstest.WsMitJettyIntegrTest
mvn jetty:stop
Führen Sie beide Integrationstests automatisiert aus. Dabei wird der Jetty-Webserver automatisch hochgefahren und wieder beendet:
mvn integration-test
Beobachten Sie anhand der Tracing-Meldungen, dass die beiden Integrationstests parallel ausgeführt werden. Dies spart Zeit, allerdings sind dadurch die Zeitmessungen unzuverlässig.
ClassNotFoundException ... WSServletContextListener / WSServlet:
Falls Sie folgende Exceptions erhalten:
java.lang.ClassNotFoundException: com.sun.xml.ws.transport.http.servlet.WSServletContextListener oder
java.lang.ClassNotFoundException: com.sun.xml.ws.transport.http.servlet.WSServlet:
Leider gibt es zwischen bestimmten Versionen von Java SE 6 und JAX-WS 2
Inkompatibilitäten.
Für dieses Beispiel ist die einfachste Lösung, JAX-WS 2.2 explizit hinzuzufügen:
Downloaden Sie die JAX-WS RI 2.2 (Referenzimplementation) von
http://jax-ws.java.net
(z.B. JAXWS2.2-20091203.zip).
Entzippen Sie das Archiv und kopieren Sie die darin im jaxws-ri\lib-Ordner enthaltenen .jar-Libs
in das src\main\webapp\WEB-INF\lib-Verzeichnis.
Cargo, Tomcat, JBoss und WebLogic:
Erläuterungen zu automatisierten Integrationstests mit Maven und Cargo für Tomcat, JBoss und WebLogic finden Sie unter maven.htm.
Beim obigen "Code-First"-Programmierbeispiel wurde der Webservice in Java programmiert und anschließend konnte über eine URL die dazugehörige WSDL- und Schema-XSD-Datei abgerufen werden.
"Contract-First" dagegen bedeutet, dass nicht mit der Programmierung begonnen wird, sondern stattdessen zuerst die WSDL- und Schema-XSD-Dateien erstellt werden. Dies ist zwar für den Java-Programmierer umständlicher und schwieriger, aber kann vorteilhaft sein:
Im folgenden Beispiel "simulieren" wir diese Vorgehensweise. Wir erstellen zuerst die WSDL- und Schema-XSD-Dateien und generieren daraus mit wsimport die erforderlichen Webservice-spezifischen Java-Sourcecodedateien. Eigentlich müsste dann darauf aufbauend die weitere Anwendung entwickelt werden. An dieser Stelle "pfuschen" wir etwas, indem wir einfach Klassen aus dem letzten Programmierbeispiel verwenden, die "zufällig" genau zu den generierten Java-Sourcecodedateien passen.
Genaue Informationen zu WSDL (Web Services Description Language) finden Sie unter http://www.w3.org/TR/wsdl und in der WSDL-Schema-Beschreibung http://schemas.xmlsoap.org/wsdl/. Damit die Webservice-Kommunikation reibungslos und plattformübergreifend funktioniert, erfüllt JAX-WS die Restriktionen von WS-I Basic Profile 1.1 und die WSDL-Datei muss WS-I-konform gestaltet werden.
Übersicht zum Aufbau einer WSDL-Datei und einige (vereinfachte) Erläuterungen:
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" WSDL-Namespace targetNamespace="..."> Target-Namespace <import> ... </import> Importe externer WSDL-Dateien <types> Typdeklarationen: <xsd:schema> ... </xsd:schema> Wahlweise Einbettung von Schema-Informationen </types> oder Importe externer Schema-XSD-Dateien <message ...><part ... /></message> "Messages" sind Parameter und Rückgabewerte (z.B. definiert über XSD) <portType ...> "Port Types" entsprechen Java-Interfaces: <operation ...> "Operations" entsprechen Java-Methoden: <input ... /> Input-Messages (Argumentparameter) <output ... /> Output-Messages (Rückgabewerte) <fault ... /> Fehlernachrichten (z.B. Exceptions) </operation> </portType> <binding ...> "Bindings" sind Verbindungen von "Port Types" zu Protokollen: <soap:binding ... /> Z.B. SOAP-Protokoll über HTTP mit style="document" <operation ...> Pro "Operation" Zusatzangaben: <soap:operation ... /> Z.B. soapAction="" <input> <soap:body ... /></input> Z.B. use="literal" <output><soap:body ... /></output> Z.B. use="literal" <fault ...><soap:fault ... /></fault> Z.B. use="literal" </operation> </binding> <service ...> "Services" sind Gruppen von "Ports" <port ...> "Ports" beschreiben den Zugang zu Diensten <soap:address ... /> Z.B. location="..." (URL des Dienstes) </port> </service> </definitions>
Bitte beachten Sie, dass die meisten der genannten Elemente fehlen dürfen oder mehrfach vorkommen dürfen.
Für den Java-Programmierer sind in der WSDL-Datei hauptsächlich die Definition des Interfaces (<portType ...) und die Typdeklarationen interessant. Die Typdeklarationen können direkt in der WSDL-Datei eingebettet werden oder in einer externen Schema-XSD-Datei definiert werden.
Nach dieser Einleitung beginnen wir mit dem Programmierbeispiel:
Erzeugen Sie im src\main\webapp\WEB-INF-Verzeichnis des letzten Programmierbeispiels das Unterverzeichnis wsdl und darin zwei XML-Dateien.
Die WSDL-Datei: BuecherService.wsdl
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://ws.meinprojekt.meinefirma.de/" targetNamespace="http://ws.meinprojekt.meinefirma.de/" name="BuecherServiceImplService"> <!-- Typdeklarationen: Import einer externen Schema-XSD-Datei --> <types> <xsd:schema> <xsd:import namespace="http://ws.meinprojekt.meinefirma.de/" schemaLocation="BuecherService.xsd" /> </xsd:schema> </types> <!-- Nachrichten: Input-Argument und Output-Response, jeweils Verweise auf Schema-Elemente --> <message name="createBuch"> <part name="parameters" element="tns:createBuch" /></message> <message name="createBuchResponse"> <part name="parameters" element="tns:createBuchResponse" /></message> <message name="getBuchByIsbn"> <part name="parameters" element="tns:getBuchByIsbn" /></message> <message name="getBuchByIsbnResponse"> <part name="parameters" element="tns:getBuchByIsbnResponse" /></message> <message name="findeBuecher"> <part name="parameters" element="tns:findeBuecher" /></message> <message name="findeBuecherResponse"> <part name="parameters" element="tns:findeBuecherResponse" /></message> <!-- Fehlernachricht (Exception) --> <message name="DuplicateCreateException"><part name="fault" element="tns:DCException" /></message> <!-- Interface mit den einzelnen Methoden und Verweisen auf obige Nachrichten --> <portType name="BuecherServiceIntf"> <operation name="createBuch"> <input message="tns:createBuch" /> <output message="tns:createBuchResponse" /> <fault message="tns:DuplicateCreateException" name="DuplicateCreateException" /> </operation> <operation name="getBuchByIsbn"> <input message="tns:getBuchByIsbn" /> <output message="tns:getBuchByIsbnResponse" /> </operation> <operation name="findeBuecher"> <input message="tns:findeBuecher" /> <output message="tns:findeBuecherResponse" /> </operation> </portType> <!-- Protokoll-Binding (SOAP, HTTP, "document/literal") mit Verweis auf obiges Interface --> <binding name="BuecherServiceImplPortBinding" type="tns:BuecherServiceIntf"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" /> <operation name="createBuch"> <soap:operation soapAction="" /> <input> <soap:body use="literal" /></input> <output><soap:body use="literal" /></output> <fault name="DuplicateCreateException"><soap:fault name="DuplicateCreateException" use="literal" /></fault> </operation> <operation name="getBuchByIsbn"> <soap:operation soapAction="" /> <input> <soap:body use="literal" /></input> <output><soap:body use="literal" /></output> </operation> <operation name="findeBuecher"> <soap:operation soapAction="" /> <input> <soap:body use="literal" /></input> <output><soap:body use="literal" /></output> </operation> </binding> <!-- Zugangs-URL zum Dienst mit Verweis auf obiges Protokoll-Binding --> <service name="BuecherServiceImplService"> <port name="BuecherServiceImplPort" binding="tns:BuecherServiceImplPortBinding"> <soap:address location="http://localhost:4433/JaxWsTest/ws/BuecherService" /> </port> </service> </definitions>
Sehen Sie sich an, wie mit fault die DuplicateCreateException eingebunden wird.
Und erzeugen Sie die Schema-XSD-Datei: BuecherService.xsd
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0" xmlns:tns="http://ws.meinprojekt.meinefirma.de/" targetNamespace="http://ws.meinprojekt.meinefirma.de/"> <xs:element name="buecherTO" type="tns:buecherTO" /> <xs:element name="createBuch" type="tns:createBuch" /> <xs:element name="createBuchResponse" type="tns:createBuchResponse" /> <xs:element name="getBuchByIsbn" type="tns:getBuchByIsbn" /> <xs:element name="getBuchByIsbnResponse" type="tns:getBuchByIsbnResponse" /> <xs:element name="findeBuecher" type="tns:findeBuecher" /> <xs:element name="findeBuecherResponse" type="tns:findeBuecherResponse" /> <xs:element name="DCException" type="tns:DCException" /> <xs:complexType name="buchDO"> <xs:sequence> <xs:element name="isbn" type="xs:long" minOccurs="0" /> <xs:element name="preis" type="xs:double" minOccurs="0" /> <xs:element name="titel" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> <xs:complexType name="buecherTO"> <xs:sequence> <xs:element name="results" type="tns:buchDO" nillable="true" minOccurs="0" maxOccurs="unbounded" /> <xs:element name="message" type="xs:string" minOccurs="0" /> <xs:element name="returncode" type="xs:int" minOccurs="0" /> </xs:sequence> </xs:complexType> <xs:complexType name="createBuch"> <xs:sequence> <xs:element name="buch" type="tns:buchDO" minOccurs="0" /> </xs:sequence> </xs:complexType> <xs:complexType name="createBuchResponse"> <xs:sequence> <xs:element name="return" type="tns:buecherTO" minOccurs="0" /> </xs:sequence> </xs:complexType> <xs:complexType name="getBuchByIsbn"> <xs:sequence> <xs:element name="isbn" type="xs:long" minOccurs="0" /> </xs:sequence> </xs:complexType> <xs:complexType name="getBuchByIsbnResponse"> <xs:sequence> <xs:element name="return" type="tns:buecherTO" minOccurs="0" /> </xs:sequence> </xs:complexType> <xs:complexType name="findeBuecher"> <xs:sequence> <xs:element name="buch" type="tns:buchDO" minOccurs="0" /> </xs:sequence> </xs:complexType> <xs:complexType name="findeBuecherResponse"> <xs:sequence> <xs:element name="return" type="tns:buecherTO" minOccurs="0" /> </xs:sequence> </xs:complexType> <xs:complexType name="DCException"> <xs:sequence> <xs:element name="message" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:schema>
In der Schema-XSD-Datei sind insbesondere die Definitionen zu buchDO und buecherTO wichtig.
Generierung der Java-Soucecodedateien mit wsimport
Löschen Sie im src\main\java\de\meinefirma\meinprojekt\ws-Verzeichnis die BuchDO.java, BuecherTO.java, BuecherServiceIntf.java und die DuplicateCreateException.java, so dass nur die BuecherServiceImpl.java übrig bleibt.
Generieren Sie mit wsimport die zu den WSDL- und XSD-Beschreibungen passenden Java-Soucedateien:
cd \MeinWorkspace\JaxWsTest
md target\classes
dir src\main\java\de\meinefirma\meinprojekt\ws
wsimport -keep -d target\classes -s src\main\java -p de.meinefirma.meinprojekt.ws src\main\webapp\WEB-INF\wsdl\BuecherService.wsdl
dir src\main\java\de\meinefirma\meinprojekt\ws
Sehen Sie sich die generierten Klassen an, insbesondere BuchDO.java, BuecherTO.java, BuecherServiceIntf.java und DuplicateCreateException.java.
Integrationstest
Führen Sie die oben genannten Tests aus, also insbesondere die Integrationstests:
mvn integration-test
(Falls Sie eine NoSuchMethodError-Exception erhalten: Sehen Sie sich bitte weiter unten den Hinweis hierzu an.)
Manueller Test mit Jetty
Starten Sie den Jetty-Webserver mit dem Webservice:
start cmd /C mvn jetty:run
Sehen Sie sich an, was als WSDL-Datei und als Schema-XSD-Datei gemeldet wird:
start http://localhost:4433/JaxWsTest/ws/BuecherService?wsdl
start http://localhost:4433/JaxWsTest/ws/BuecherService?xsd=1
Diesmal werden die WSDL- und XSD-Dateien nicht generiert, sondern es werden die von Ihnen angelegten Dateien angezeigt, mit drei Änderungen in der WSDL-Datei:
Beenden Sie den Jetty-Webserver:
mvn jetty:stop
Manueller Test mit dem Endpoint-Server
Starten Sie den Endpoint-Server mit dem Webservice (in einem neuen Kommandozeilenfenster):
start java -cp target/classes de.meinefirma.meinprojekt.TestWsServer http://localhost:4434/buecherservice
Sehen Sie sich die generierte WSDL-Datei und die generierte Schema-XSD-Datei an:
start http://localhost:4434/buecherservice?wsdl
start http://localhost:4434/buecherservice?xsd=1
Anders als über Jetty werden über den Endpoint-Server nicht die originalen, sondern generierte WSDL- und XSD-Dateien angezeigt.
Beenden Sie den Server über den Button in dem "TestWsServer beenden"-Mini-GUI-Dialog.
Steuerung der gelieferten WSDL- und XSD-Dateien
Wie Sie soeben festgestellt haben, verhalten sich die Endpoint- und Jetty/WAR-Versionen unterschiedlich, was die gelieferten WSDL- und XSD-Dateien betrifft. Wenn Sie dies beeinflussen wollen, gibt es mehrere Möglichkeiten:
Exception "NoSuchMethodError"
Falls Sie die Exception "SCHWERWIEGEND: WSSERVLET11: failed to parse runtime descriptor: java.lang.NoSuchMethodError: javax.xml.ws.WebFault.messageName()Ljava/lang/String;" erhalten, gibt es Kompatibilitätsprobleme zwischen Ihrer Java-Version (z.B. 6) und Ihrer JAX-WS-Version (z.B. 2.2). Sehen Sie sich hierzu an: http://metro.java.net/guide/Using_JAX_WS_2_x___Metro_1_x_2_0_with_Java_SE_6.html#Using_Metro_2_0_with_Java_SE_6.
Abhilfe schaffen Sie, indem Sie entweder die webservices-api.jar aus dem verwendeten Metro-Paket oder die jaxws-api.jar aus dem verwendeten JAX-WS-Paket in ein (normalerweise neu zu erstellendes) endorsed-Unterverzeichnis in Ihrem $JRE_HOME/lib-Verzeichnis kopieren. Beachten Sie dabei, dass Sie das "richtige" lib-Verzeichnis wählen müssen: Je nach Konfiguration beispielsweise entweder C:\Program Files\Java\jdk1.6\jre\lib oder C:\Program Files\Java\jre6\lib.
Je nach Windows-Version und Java-Installation kann es genügen, wenn Sie ein Kommando ähnlich zu folgendem in einem Kommandozeilenfenster mit Administratorrechten ausführen:
xcopy D:\MeinWorkspace\JaxWsTest\src\main\webapp\WEB-INF\lib\jaxws-api.jar "C:\Program Files\Java\jdk1.6\jre\lib\endorsed\"
Nachtrag zum Client
In diesem einfachen Programmierbeispiel werden Server und Client im selben Projekt erzeugt. Das entspricht normalerweise nicht der Realität. Aber gerade das "Contract-First"-Verfahren hat den Vorteil, dass keine Java-Interfaces ausgetauscht werden müssen, sondern lediglich die WSDL- und Schema-XSD-Dateien benötigt werden, um eigenständig den Server bzw. Client entwickeln zu können.
Die Vorgehensweise bei einem eigenständigen Client-Projekt ist dieselbe wie oben beschrieben. Es können allerdings einige Dateien (insbesondere im WEB-INF-Verzeichnis) entfallen und die WSDL- und XSD-Dateien müssen nicht unbedingt im WEB-INF\wsdl-Ordner gespeichert werden. Aber die Generierung der benötigten Java-Sourcecodedateien erfolgt wieder über wsimport.
Unter jee-rest.htm#JaxRsBuecherverwaltung finden Sie ein zu obigem "BuecherService" sehr ähnliches Programmierbeispiel, allerdings mit REST statt SOAP.
Im sich anschließenden Beispiel wird sowohl REST als auch SOAP implementiert, so dass Performance-Vergleiche durchgeführt werden können.
Damit die Webservice-Kommunikation reibungslos und plattformübergreifend funktioniert, beachten Sie die im WS-I Basic Profile 1.1 definierten Vorgaben und halten Sie folgende Regeln ein: