SOAP Web Services mit JAX-WS

+ andere TechDocs
+ SOA
+ SOAP
+ XML
+ XSD (Schema)
+ REST mit JAX-RS
+


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).



Inhalt

  1. Infos zu SOAP-Webservices und JAX-WS
  2. Minimaler SOAP-Webservice mit JAX-WS
  3. Vorbereitung für die weiteren Programmierbeispiele
  4. Webservice per Code-First
  5. Deployment des Webservices im Webserver und Integrationstest
  6. Webservice per Contract-First (mit wsimport aus WSDL)
  7. Bücher-Programmierbeispiel mit REST statt SOAP
  8. Empfehlungen für größtmögliche Kompatibilität und WS-I Basic Profile 1.1


Infos zu SOAP-Webservices und JAX-WS

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:



Minimaler SOAP-Webservice mit JAX-WS

Das folgende Beispiel zeigt eine minimale Implementierung eines SOAP-Webservices inklusive Server und Client.

  1. Installieren Sie ein aktuelles Java SE JDK (mindestens Version 6).
  2. Legen Sie ein Projektverzeichnis an (z.B. \MeinWorkspace\JaxWsHelloWorld), darunter die Verzeichnisse bin und src, und unter letzterem miniwebservice:

    [\MeinWorkspace\JaxWsHelloWorld]
     |- [bin]
     '- [src]
         '- [miniwebservice]
    
  3. 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] : "" ) );
       }
    }
    
  4. Die Projektstruktur sieht jetzt so aus:

    cd \MeinWorkspace\JaxWsHelloWorld

    tree /F

    [\MeinWorkspace\JaxWsHelloWorld]
     |- [bin]
     '- [src]
         '- [miniwebservice]
             |- HalloWelt.java
             |- HalloWeltImpl.java
             |- TestWsClient.java
             '- TestWsServer.java
    
  5. Öffnen Sie ein Kommandozeilenfenster ('Windows-Taste' + 'R', 'cmd'), und führen Sie je nach verwendeter Java-Version aus:
  6. Sie erhalten:

    Hallo ich
    
  7. Sehen Sie sich die generierte WSDL-Datei und die generierte Schema-XSD-Datei an:

    start http://localhost:4434/miniwebservice?wsdl

    start http://localhost:4434/miniwebservice?xsd=1

  8. Sie können auch andere URLs beim TestWsServer- und TestWsClient-Aufruf übergeben, beispielsweise http://localhost:4711/xyz.

  9. Beenden Sie den TestWsServer mit "Strg + C".

  10. Falls Sie das Beispiel um Maven erweitern wollen, sehen Sie sich maven.htm#JAX-WS oder die folgenden Beispiele an.

  11. Wenn Sie Webservices genauer untersuchen wollen, empfiehlt sich soapUI.
    Sehen Sie sich die Webservice-Kommunikation an:

    soapUI-Screenshot


Vorbereitung für die weiteren Programmierbeispiele

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).

  1. Sie benötigen ein aktuelles Java SE JDK und ein installiertes Maven.
  2. Ö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]
    
  3. 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>
    
  4. 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;
       }
    }
    
  5. 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; }
    }
    
  6. 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 );
       }
    }
    
  7. 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.

  8. 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
    
  9. 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.

  10. 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/.



Webservice per Code-First

Die soeben erstellte Bücherverwaltung soll um einen Webservice erweitert werden.

  1. 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; }
    }
    
  2. 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 );
    }
    
  3. 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;
       }
    }
    
  4. 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();
       }
    }
    
  5. 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";
       }
    }
    
  6. 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() );
       }
    }
    
  7. 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
    
  8. Führen Sie die Unittests zur BuecherServiceImpl-Klasse aus:

    cd \MeinWorkspace\JaxWsTest

    mvn test

  9. 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.

  10. Wiederholen Sie das letzte Kommando: Sie erhalten eine in eine SOAPFaultException verpackte DuplicateCreateException, weil versucht wird, mit einer bereits vorhandenen ISBN erneut ein Buch anzulegen.
  11. 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

  12. Beenden Sie den Server über den Button in dem "TestWsServer beenden"-Mini-GUI-Dialog.


Deployment des Webservices im Webserver und Integrationstest

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.

  1. 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.

  2. Erzeugen Sie im src\main-Verzeichnis das Unterverzeichnis webapp, darunter das Verzeichnis WEB-INF und darunter lib:

    md src\main\webapp\WEB-INF\lib

  3. 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>
    
  4. 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 );
       }
    }
    
  5. 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
    
  6. Unittests:

    Führen Sie die Unittests aus:

    cd \MeinWorkspace\JaxWsTest

    mvn test

  7. 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

  8. 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.

  9. 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.

  10. 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.



Webservice per Contract-First (mit wsimport aus WSDL)

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.)

  5. 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

  6. 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.

  7. 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:

  8. 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\"

  9. 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.



Bücher-Programmierbeispiel mit REST statt SOAP

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.



Empfehlungen für größtmögliche Kompatibilität und WS-I Basic Profile 1.1

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:





Weitere Themen: andere TechDocs | XML | XSD (Schema) | REST mit JAX-RS
© 2009-2010 Torsten Horn, Aachen