Der Begriff "REST" (Representational State Transfer) wurde im Jahr 2000 von Roy Thomas Fielding geprägt und definiert allgemeine Grundlagen eines Architekturstils, der auf identifizierbaren Ressourcen, verschiedenen Repräsentationen, Hypermedia und einheitlichen Schnittstellen basiert. Dieser Architekturstil ist nicht auf HTTP beschränkt, aber das auf HTTP basierende Web ist die bekannteste Implementierung. In Konkurrenz zu SOA (Service Oriented Architecture) wird auch manchmal von "ROA" (Resource Oriented Architecture) gesprochen. Eine Einführung zu REST finden Sie im englischsprachigen Wikipedia.
Während REST sehr allgemeine Grundlagen definiert, steht der Begriff "RESTful Web Services" für konkretere Definitionen für auf REST basierender Kommunikation, zum Beispiel das im JSR 311 definierte JAX-RS: The Java API for RESTful Web Services.
Java EE 6 beinhaltet JAX-RS 1.1. Die Referenzimplementierung hierfür ist Jersey 1.1.5 von Sun.
RESTful Web Services konkurrieren mit SOAP Web Services.
Insbesondere im SOA-Umfeld wird oft zwischen folgenden Architekturstilen unterschieden, obwohl die Grenzen fließend sind:
Vergleiche zwischen REST und anderen Architekturstilen oder Technologien sind nicht einfach und schwer objektivierbar. Es gibt sicherlich nicht eine pauschal "bessere" Technologie, sondern es kommt auf den Einsatzfall an. Auch der folgende Vergleich ist sicherlich diskussionswürdig:
| RESTful Web Services | SOAP Web Services | |
|---|---|---|
| Basis-Standard | REST | W3C u.a. |
| Java-Standard | JAX-RS (JSR 311) | JAX-WS (JSR 224) |
| Beispiel für Java-Implementierung | Jersey | Metro |
| Architekturstil | "ROA" (Resource Oriented Architecture), ressourcenorientiert mit generischer uniformer Schnittstelle (GET, PUT, POST, DELETE) | eher "SOA" (Service Oriented Architecture), schnittstellen-/nachrichtenorientiert |
| Bevorzugtes Anwendungsgebiet | datenorientierte synchrone kurz laufende Services | sowohl datenorientierte als auch lang laufende prozessorientierte Services, synchron und asynchron |
| Serverseitiger Zustand/Status | zustandslos | eher zustandslos, kann aber auch zustandsbehaftet sein |
| Formale syntaktische Schnittstellenbeschreibung | nur zum Teil standardisiert: Nachrichtenformate können durch XML Schema Definition und WADL beschrieben werden | vollständig durch WSDL |
| Nachrichtenformat, Repräsentation | Text, HTML, XML, JSON, binär, ... | XML (plus Attachments) |
| Nachrichtenprotokoll, Anwendungsprotokoll | REST, HTTP | SOAP |
| Transportprotokoll | HTTP | HTTP, SMTP, JMS, ... |
| Asynchrone Kommunikation | nicht direkt (nur über Umwege simuliert) | ja, per JMS und WS-Notification (WSN) |
| Transaktion, sichere Zustellung | keine Unterstützung, stattdessen kann GET, PUT und DELETE idempotent gestaltet werden; POST kann eventuell durch POST Once Exactly (POE) abgesichert werden | per WS Reliable Messaging (WSRM) und Web Services Transactions Framework (WSTF) |
| Routing | möglich | per WS-Addressing |
| Operationsabhängiger Zugriffsschutz | einfach, per Webserver oder Firewall | per WS-Security (WSS) |
| Bookmarks/Links | ja | nein |
| Caching | einfach | schwierig |
| Skalierbarkeit | optimal | kann schwieriger sein |
| Performance | gut | eher schlechter |
| Lose Kopplung, Interoperabilität, Plattformunabhängigkeit, Internetfähigkeit | ja | ja |
Die vier am häufigsten benutzten HTTP-Verben sind GET, PUT, POST und DELETE. Seltener verwendet werden HEAD und OPTIONS. Die folgende Tabelle zeigt übliche Assoziationen zu CRUD (Create, Read, Update, Delete) und Beispiele für REST-konforme Verwendungen:
| HTTP | CRUD | Beispiel-URL und -Bedeutung | Idempotenz, Sicherheit |
|---|---|---|---|
| GET | Read | http://xyz.de/Artikel/Buecher --> Liste aller Bücher; http://xyz.de/Artikel/Buecher/4711 --> Informationen zu dem per ID ausgewählten Buch; http://xyz.de/Artikel/Buecher?isbn=1234567890 --> Informationen zu dem per Suchkriterium ausgewählten Buch |
idempotent, ohne Seiteneffekte, cachefähig |
| POST | Create | http://xyz.de/Artikel/Buecher --> Neuen Artikel hinzufügen (mit neuer ID) (dabei wird üblicherweise die automatisch vergebene ID returniert) |
nicht idempotent |
| PUT | Update, Create |
http://xyz.de/Artikel/Buecher/4711 --> Update (oder Create) des per ID identifizierten Artikels |
idempotent |
| DELETE | Delete | http://xyz.de/Artikel/Buecher/4711 --> Diesen per ID identifizierten Artikel löschen |
idempotent |
Jersey ist die JAX-RS (JSR 311) Reference Implementation von Sun und wird in den folgenden Programmierbeispielen verwendet.
Das folgende Beispiel zeigt eine minimale Implementierung eines RESTful-Webservices mit JAX-RS inklusive Server und Client.
JAX-RS ist in Java EE 6 enthalten. Aber hier wollen wir uns auf Java SE 6 beschränken, wo JAX-RS nicht enthalten ist. Deshalb wird eine JAX-RS-Implementierung benötigt: Wir wählen die Sun-Referenzimplementierung Jersey.
Als besonders einfacher Webserver wird Grizzly verwendet.
In anderen Beispielen werden benötigte Bibliotheken automatisch über Maven hinzugefügt, aber in diesem ersten Beispiel soll alles manuell ohne Maven erfolgen.
Legen Sie ein Projektverzeichnis an (z.B. D:\MeinWorkspace\JaxRsHelloWorld), darunter die Verzeichnisse bin, lib und src, und unter letzterem minirestwebservice:
[\MeinWorkspace\JaxRsHelloWorld]
|- [bin]
|- [lib]
'- [src]
'- [minirestwebservice]
Downloaden Sie jersey-archive-1.1.5.1.zip von https://jersey.dev.java.net oder von http://download.java.net/maven/2/com/sun/jersey/jersey-archive/1.1.5.1.
Entzippen Sie das Jersey-Archiv in ein temporäres Verzeichnis und kopieren Sie die .jar-Libraries asm-3.1.jar, jersey-client-1.1.5.1.jar, jersey-core-1.1.5.1.jar, jersey-server-1.1.5.1.jar und jsr311-api-1.1.1.jar (oder alle Libs) aus dem jersey-archive-1.1.5.1/lib-Verzeichnis in das JaxRsHelloWorld/lib-Verzeichnis.
Downloaden Sie den Grizzly-Server von http://download.java.net/maven/2/com/sun/grizzly/grizzly-servlet-webserver (z.B. grizzly-servlet-webserver-1.9.18.jar).
Kopieren Sie die Grizzly-jar-Lib in das JaxRsHelloWorld/lib-Verzeichnis.
Legen Sie im src\minirestwebservice-Verzeichnis die folgenden drei Java-Dateien an.
Dienstimplementierung: HalloWeltService.java
package minirestwebservice;
import javax.ws.rs.*;
@Path( "/helloworld" )
public class HalloWeltService
{
@GET @Produces( "text/plain" )
public String halloText( @QueryParam("name") String name )
{
return "Hallo " + name;
}
@GET @Produces( "text/html" )
public String halloHtml( @QueryParam("name") String name )
{
return "<html><title>HelloWorld</title><body><h2>Html: Hallo " + name + "</h2></body></html>";
}
}
Wenn Sie nicht verschiedene Repräsentationen (Ausgabeformate, hier: text/plain und text/html) unterstützen wollen,
genügt nur eine der beiden GET-Methoden.
Falls Sie weitere Repräsentationen benötigen (z.B. text/xml oder application/xml), können Sie weitere Methoden hinzufügen.
Sehen Sie sich die Bedeutung der @Path-, @GET-, @Produces- und @QueryParam-Annotationen an unter:
https://jsr311.dev.java.net/nonav/releases/1.1/javax/ws/rs/package-summary.html
RESTful-Webservice-Server: HalloWeltTestServer.java
package minirestwebservice;
import com.sun.grizzly.http.SelectorThread;
import com.sun.jersey.api.container.grizzly.GrizzlyServerFactory;
public class HalloWeltTestServer
{
public static void main( String[] args ) throws Exception
{
String url = ( args.length > 0 ) ? args[0] : "http://localhost:4434";
String sec = ( args.length > 1 ) ? args[1] : "10";
SelectorThread srv = GrizzlyServerFactory.create( url );
System.out.println( "URL: " + url );
Thread.sleep( 1000 * Integer.parseInt( sec ) );
srv.stopEndpoint();
}
}
Sehen Sie sich die GrizzlyServerFactory-Klasse an. Bei der hier verwendeten besonders simplen create()-Methode werden alle Klassen im Classpath (inkl. jars) nach JAX-RS-annotierten Klassen durchsucht. Sehen Sie sich auch die anderen create()-Methoden an, über die Sie genauer die zu verwendenden Klassen und Eigenschaften definieren können.
RESTful-Webservice-Client: HalloWeltTestClient.java
package minirestwebservice;
import com.sun.jersey.api.client.*;
public class HalloWeltTestClient
{
public static void main( String[] args )
{
String url = ( args.length > 0 ) ? args[0] : "http://localhost:4434";
String nam = ( args.length > 1 ) ? args[1] : "ich";
url = url + "/helloworld?name=" + nam;
System.out.println( "URL: " + url );
WebResource wrs = Client.create().resource( url );
System.out.println( "\nTextausgabe:" );
System.out.println( wrs.accept( "text/plain" ).get( String.class ) );
System.out.println( "\nHTML-Ausgabe:" );
System.out.println( wrs.accept( "text/html" ).get( String.class ) );
}
}
Sehen Sie sich die Client- und WebResource-Klassen an. Beachten Sie, dass auch asynchrone Clients erstellt werden können.
Die Projektstruktur sieht jetzt so aus:
cd \MeinWorkspace\JaxRsHelloWorld
tree /F
[\MeinWorkspace\JaxRsHelloWorld]
|- [bin]
|- [lib]
| |- asm-3.1.jar
| |- grizzly-servlet-webserver-1.9.18.jar
| |- jersey-client-1.1.5.1.jar
| |- jersey-core-1.1.5.1.jar
| |- jersey-server-1.1.5.1.jar
| '- jsr311-api-1.1.1.jar
'- [src]
'- [minirestwebservice]
|- HalloWeltService.java
|- HalloWeltTestClient.java
'- HalloWeltTestServer.java
Öffnen Sie ein Kommandozeilenfenster ('Windows-Taste' + 'R', 'cmd'), bauen Sie das Projekt und starten Sie den RESTful-Webservice-Server und den -Client in verschiedenen Kommandozeilenfenstern (ersetzen Sie ich durch Ihren Namen):
cd \MeinWorkspace\JaxRsHelloWorld
javac -cp bin;lib/* -d bin src/minirestwebservice/*.java
start java -cp bin;lib/* minirestwebservice.HalloWeltTestServer http://localhost:4434 30
java -cp bin;lib/* minirestwebservice.HalloWeltTestClient http://localhost:4434 ich
start http://localhost:4434/helloworld?name=ich
start http://localhost:4434/application.wadl
Die letzten vier Kommandos müssen kurz hintereinander (z.B. in einer Batchdatei) innerhalb der angegebenen Zeit (im Beispiel: 30 Sekunden) ausgeführt werden, weil sich anschließend der TestServer beendet.
Sie erhalten im Kommandozeilenfenster:
URL: http://localhost:4434/helloworld?name=ich Textausgabe: Hallo ich HTML-Ausgabe: <html><title>HelloWorld</title><body><h2>Html: Hallo ich</h2></body></html>
Im ersten Webbrowser-Fenster erscheint das Ergebnis der GET-Methode: Mozilla Firefox 3.6 zeigt die HTML-Repräsentation "Html: Hallo ich", aber MS Internet Explorer 8 zeit die Text-Repräsentation "Hallo ich".
Im zweiten Webbrowser-Fenster bietet MS Internet Explorer den Download der WADL-Datei an, während Firefox sie direkt anzeigt:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<application xmlns="http://research.sun.com/wadl/2006/10">
<doc xmlns:jersey="http://jersey.dev.java.net/" jersey:generatedBy="Jersey: 1.1.5"/>
<resources base="http://localhost:4434/">
<resource path="/helloworld">
<method name="GET" id="halloText">
<request>
<param xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string" style="query" name="name"/>
</request>
<response>
<representation mediaType="text/plain"/>
</response>
</method>
<method name="GET" id="halloHtml">
<request>
<param xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string" style="query" name="name"/>
</request>
<response>
<representation mediaType="text/html"/>
</response>
</method>
</resource>
</resources>
</application>
WADL (Web Application Description Language) ist ein XML-basiertes Dateiformat zur Beschreibung von Schnittstellen von HTTP-basierten Anwendungen (besonders RESTful-Webservices) in maschinenlesbarer Form (teilweise vergleichbar mit WSDL).
Sie können leicht die Beschreibung der zwei implementierten GET-Methoden halloText() und halloHtml() erkennen.
Falls Sie die WADL-Datei um weitere Informationen ergänzen wollen, sehen Sie sich Adding more information to the generated WADL an.
Sie können auch andere URLs beim HalloWeltTestServer- und HalloWeltTestClient-Aufruf übergeben, auch inklusive eines Web-Root-ContextPath-Anteils, beispielsweise http://localhost:4711/xyz.
Wenn Sie eine lange Laufzeit vorgegeben haben, können Sie den HalloWeltTestServer auch einfach vorzeitig mit "Strg + C" beenden.
cURL ist ein Kommandozeilentool zum Downloaden von Dateien. Doku finden Sie unter http://curl.haxx.se/docs/manual.html.
Öffnen Sie im curl-7.19.5-win32-nossl-sspi\curl-7.19.5-Verzeichnis ein Kommandozeilenfenster und sehen Sie sich die Liste der vielfältigen Kommandozeilenoptionen an:
curl --help
Starten Sie obigen HalloWeltTestServer mit langer Laufzeit (z.B. 300 Sekunden) und lesen Sie folgendermaßen die Ausgabe des HelloWorld-Beispiels:
cd \MeinWorkspace\JaxRsHelloWorld
start java -cp bin;lib/* minirestwebservice.HalloWeltTestServer http://localhost:4434 300
curl "http://localhost:4434/helloworld?name=ich"
Sie erhalten:
Hallo ich
Mit der Option "-i" geben Sie auch die empfangenen Header-Informationen aus:
curl -i "http://localhost:4434/helloworld?name=ich"
Sie erhalten u.a.:
HTTP/1.1 200 OK Content-Type: text/plain Hallo ich
Mit der Option "-H" können Sie Header-Informationen zum Server senden und so den Content-Type vorgeben:
curl -H "Accept:text/plain" "http://localhost:4434/helloworld?name=ich"
-->
Hallo ich
und
curl -i -H "Accept:text/html" "http://localhost:4434/helloworld?name=ich"
-->
HTTP/1.1 200 OK Content-Type: text/html <html><title>HelloWorld</title><body><h2>Html: Hallo ich</h2></body></html>
Lassen Sie sich die WADL-Datei ausgeben:
curl -i "http://localhost:4434/application.wadl"
Beachten Sie den im Header ausgegebenen Content-Type: application/vnd.sun.wadl+xml: Das "vnd" steht für "Vendor-spezifisch" (also proprietär).
Wie Sie mit cURL PUT-, POST- und DELETE-Kommandos absetzen können, finden Sie weiter unten erläutert.
Ein anderes Kommandozeilentool zum Downloaden von Dateien ist Wget.
Lesen Sie folgendermaßen die Ausgabe des obigen HelloWorld-Beispiels (durch die Option "-O -" wird keine Datei angelegt, sondern nur auf StdOut ausgegeben):
cd \MeinWorkspace\JaxRsHelloWorld
start java -cp bin;lib/* minirestwebservice.HalloWeltTestServer http://localhost:4434 300
wget -O - "http://localhost:4434/helloworld?name=ich"
Sie erhalten:
Hallo ich
Mit der Option "--save-headers" geben Sie auch die empfangenen Header-Informationen aus:
wget --save-headers -O - "http://localhost:4434/helloworld?name=ich"
Sie erhalten u.a.:
HTTP/1.1 200 OK Content-Type: text/plain Hallo ich
Falls Sie statt der Kommandozeilentools grafische Tools bevorzugen, sollten Sie sich die weiter unten gezeigten Firefox-Plug-ins RESTClient, Poster und Firebug ansehen.
Das folgende Programmierbeispiel erweitert obiges HelloWorld-Programmierbeispiel eines RESTful-Webservices mit Grizzly um das Build-Tool Maven.
Die im Folgenden vorgestellten Programmierbeispiele können Sie auch als Zipdatei downloaden (außer den Libs).
Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. D:\MeinWorkspace), in dem sich auch das JaxRsHelloWorld-Projekt befindet, und führen Sie folgende Kommandos aus:
cd \MeinWorkspace
mvn archetype:generate -DinteractiveMode=false -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=minirestwebservice -DartifactId=JaxRsMitMaven
cd JaxRsMitMaven
tree /F
rd src\main\resources
md src\main\java\minirestwebservice
md src\test\java\minirestwebservice
xcopy ..\JaxRsHelloWorld\src\minirestwebservice\*.java src\main\java\minirestwebservice\
tree /F
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>minirestwebservice</groupId>
<artifactId>JaxRsMitMaven</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>${artifactId}</name>
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.1.5.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.1.5.1</version>
</dependency>
<dependency>
<groupId>com.sun.grizzly</groupId>
<artifactId>grizzly-servlet-webserver</artifactId>
<version>1.9.18</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.12.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Falls Sie fehlende Libs direkt uebers Internet nachladen, benoetigen Sie folgende Repository-Angaben
(bitte entfernen, falls Sie einen Repository-Manager wie Archiva konfiguriert haben): -->
<repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Maven 2 Repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
<repository>
<id>maven-repository.dev.java.net</id>
<name>Java.net Maven 1 Repository (legacy)</name>
<url>http://download.java.net/maven/1</url>
<layout>legacy</layout>
</repository>
</repositories>
</project>
Ersetzen Sie im src\main\webapp\WEB-INF-Verzeichnis den Inhalt der web.xml durch:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
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"
id="WebApp_ID" version="2.5">
<display-name>REST</display-name>
<servlet>
<display-name>JAX-RS REST Servlet</display-name>
<servlet-name>REST-Servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>REST-Servlet</servlet-name>
<url-pattern>/rs/*</url-pattern>
</servlet-mapping>
</web-app>
Fügen Sie im src\test\java\minirestwebservice-Testverzeichnis die Unittestklasse HalloWeltServiceTest.java hinzu:
package minirestwebservice;
import junit.framework.TestCase;
import com.sun.grizzly.http.SelectorThread;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.container.grizzly.GrizzlyServerFactory;
public class HalloWeltServiceTest extends TestCase
{
public void testRESTfulWebService() throws Exception
{
String urlSrvr = "http://localhost:4434";
String urlClnt = urlSrvr + "/helloworld?name=MeinName";
// Testserver:
SelectorThread srv = GrizzlyServerFactory.create( urlSrvr );
try {
// Testclient:
WebResource wrs = Client.create().resource( urlClnt );
String txt = wrs.accept( "text/plain" ).get( String.class );
String htm = wrs.accept( "text/html" ).get( String.class );
// Pruefungen:
assertEquals( "Hallo MeinName", txt );
assertEquals( "<html><title>HelloWorld</title><body><h2>Html: Hallo MeinName</h2></body></html>", htm );
} finally {
// Testserver beenden:
srv.stopEndpoint();
}
}
}
Bitte beachten Sie, dass Sie diesmal nicht manuell Libs zum Projekt hinzukopieren müssen, weil sich darum Maven kümmert.
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JaxRsMitMaven] |- [src] | |- [main] | | |- [java] | | | '- [minirestwebservice] | | | |- HalloWeltService.java | | | |- HalloWeltTestClient.java | | | '- HalloWeltTestServer.java | | |- [resources] | | '- [webapp] | | |- [WEB-INF] | | | '- web.xml | | '- index.jsp | '- [test] | '- [java] | '- [minirestwebservice] | '- HalloWeltServiceTest.java '- pom.xml
Führen Sie den Unittest aus:
mvn test
Beachten Sie, dass Jersey automatisch nach JAX-RS-annotierten Klassen sucht und meldet: "INFO: Root resource classes found: class minirestwebservice.HalloWeltService".
Falls Sie Eclipse einsetzen wollen, bereiten Sie Eclipse vor wie beschrieben unter maven.htm#Eclipse, führen Sie folgendes Kommando aus, laden Sie das JaxRsMitMaven-Projekt in Eclipse und führen Sie den Unittest innerhalb von Eclipse aus:
mvn eclipse:eclipse
Sie können auch weiterhin den HalloWeltTestServer und den HalloWeltTestClient über die Kommandozeile betreiben:
cd \MeinWorkspace\JaxRsMitMaven
mvn package
start java -cp target/JaxRsMitMaven/WEB-INF/classes;target/JaxRsMitMaven/WEB-INF/lib/* minirestwebservice.HalloWeltTestServer http://localhost:4434 30
java -cp target/JaxRsMitMaven/WEB-INF/classes;target/JaxRsMitMaven/WEB-INF/lib/* minirestwebservice.HalloWeltTestClient http://localhost:4434 ich
start http://localhost:4434/helloworld?name=ich
start http://localhost:4434/application.wadl
Die letzten vier Kommandos müssen kurz hintereinander (z.B. in einer Batchdatei) innerhalb der angegebenen Zeit (im Beispiel: 30 Sekunden) ausgeführt werden, weil sich anschließend der TestServer beendet.
Auch cURL können Sie weiter verwenden (bei laufendem HalloWeltTestServer):
curl -i -H "Accept:text/plain" "http://localhost:4434/helloworld?name=ich"
curl -i -H "Accept:text/html" "http://localhost:4434/helloworld?name=ich"
curl -i "http://localhost:4434/application.wadl"
Die WAR-Datei im target-Verzeichnis können Sie in beliebige Java EE Webserver oder Application Server deployen (z.B. durch Kopieren ins autodeploy-Verzeichnis). Beispielsweise im Falle von WebLogic erreichen Sie das Ergebnis über:
http://localhost:7001/JaxRsMitMaven/rs/helloworld?name=ich
http://localhost:7001/JaxRsMitMaven/rs/application.wadl
curl -i -H "Accept:text/plain" "http://localhost:7001/JaxRsMitMaven/rs/helloworld?name=ich"
curl -i -H "Accept:text/html" "http://localhost:7001/JaxRsMitMaven/rs/helloworld?name=ich"
curl -i "http://localhost:7001/JaxRsMitMaven/rs/application.wadl"
Mit RESTful-Webservices können Java-Objekte nicht direkt, aber zum Beispiel als XML-Repräsentation übertragen werden, sowohl als Input-Argument, als auch als returniertes Ergebnis. Das Marshalling und Unmarshalling erfolgt vorzugsweise mit JAXB. Dies demonstriert das folgende Beispiel, welches der Einfachheit halber auf dem letzten aufbaut.
Erstellen Sie im JaxRsMitMaven-Projekt im Verzeichnis src\main\java das neue Unterverzeichnis xmljaxb und darin folgende drei Java-Klassen.
Input-Klasse: InputTO.java
package xmljaxb;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class InputTO
{
public int i;
public String s;
}
Ergebnis-Klasse: ResultTO.java
package xmljaxb;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class ResultTO
{
public int i;
public String s;
}
Service-Klasse: XmlJaxbService.java
package xmljaxb;
import javax.ws.rs.*;
@Path( "/xmljaxb" )
public class XmlJaxbService
{
@POST
@Consumes( "text/xml" )
@Produces( "text/xml" )
public ResultTO doXmlJaxbService( InputTO inp )
{
ResultTO res = new ResultTO();
res.i = inp.i * 2;
res.s = inp.s + " - ret";
return res;
}
}
Erstellen Sie im Testverzeichnis src\test\java das neue Unterverzeichnis xmljaxb und darin die Unittestklasse: XmlJaxbServiceTest.java
package xmljaxb;
import junit.framework.TestCase;
import com.sun.grizzly.http.SelectorThread;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.container.grizzly.GrizzlyServerFactory;
public class XmlJaxbServiceTest extends TestCase
{
public void testXmlJaxbService() throws Exception
{
String urlSrvr = "http://localhost:4434";
String urlClnt = urlSrvr + "/xmljaxb";
// Testserver:
SelectorThread srv = GrizzlyServerFactory.create( urlSrvr );
try {
// Testclient:
WebResource wrs = Client.create().resource( urlClnt );
// Mit JAXB und mit bequemen Java-Objekten:
InputTO inpTO = new InputTO();
inpTO.i = 42;
inpTO.s = "abc xyz";
ResultTO resTO = wrs.type( "text/xml" ).accept( "text/xml" ).post( ResultTO.class, inpTO );
assertEquals( 84, resTO.i );
assertEquals( "abc xyz - ret", resTO.s );
// Ohne JAXB und mit XML-Strings:
String resXml = wrs.type( "text/xml" ).accept( "text/xml" ).post(
String.class, "<inputTO><i>42</i><s>abc xyz</s></inputTO>" );
assertEquals( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<resultTO><i>84</i><s>abc xyz - ret</s></resultTO>", resXml );
} finally {
// Testserver beenden:
srv.stopEndpoint();
}
}
}
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JaxRsMitMaven] |- [src] | |- [main] | | |- [java] | | | |- [minirestwebservice] | | | | |- HalloWeltService.java | | | | |- HalloWeltTestClient.java | | | | '- HalloWeltTestServer.java | | | '- [xmljaxb] | | | |- InputTO.java | | | |- ResultTO.java | | | '- XmlJaxbService.java | | |- [resources] | | '- [webapp] | | |- [WEB-INF] | | | '- web.xml | | '- index.jsp | '- [test] | '- [java] | |- [minirestwebservice] | | '- HalloWeltServiceTest.java | '- [xmljaxb] | '- XmlJaxbServiceTest.java '- pom.xml
Führen Sie die Unittests aus:
mvn test
Falls Sie vor dem Stoppen des Testservers eine Warteschleife hinzufügen (z.B. Thread.sleep( 20000 );), können Sie den REST-Service auch mit cURL ansprechen:
curl -i -H Content-type:text/xml --request POST -d "<inputTO><i>42</i><s>abc xyz</s></inputTO>" "http://localhost:4434/xmljaxb"
Sie erhalten:
HTTP/1.1 200 OK Content-Type: text/xml Content-Length: 105 Date: Sat, 10 Apr 2010 11:14:15 GMT <?xml version="1.0" encoding="UTF-8" standalone="yes"?><resultTO><i>84</i><s>abc xyz - ret</s></resultTO>
Bitte beachten Sie, dass beim cURL-Kommando auf der Kommandozeile hinter "POST -d" eine XML-Datei übergeben wird und das Resultat ebenfals eine XML-Datei ist ("<?xml ... >").
Falls Sie nicht mit Java-Dateien beginnen wollen, sondern mit Schema-XSD-Dateien ("Contract-First"), dann können Sie die Java-Datentransferobjektklassen auch mit xjc generieren lassen: Sehen Sie sich hierzu das folgende Beispiel an.
Bei den bisherigen Programmierbeispielen wurde ohne explizite Schnittstellendefinition direkt mit der Java-Programmierung begonnen ("Code-First").
"Contract-First" dagegen bedeutet, dass nicht mit der Programmierung begonnen wird, sondern stattdessen zuerst Schnittstellenbeschreibungen inklusive der Schema-XSD-Dateien erstellt werden. Dies ist zwar für den Java-Programmierer umständlicher und schwieriger, aber kann vorteilhaft sein:
Ähnlich wie bei SOAP-Webservices können auch für RESTFul-Webservices Java-Klassen aus Schema-XSD-Dateien generiert werden.
Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. D:\MeinWorkspace) und führen Sie folgende Kommandos aus:
cd \MeinWorkspace
mvn archetype:generate -DinteractiveMode=false -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=contractfirstservice -DartifactId=JaxRsContractFirst
cd JaxRsContractFirst
tree /F
rd src\main\resources
md src\main\java\contractfirstservice
md src\test\java\contractfirstservice
md xsd
tree /F
Ersetzen Sie im JaxRsContractFirst-Projektverzeichnis den Inhalt der pom.xml durch den bereits in obiger pom.xml gezeigten Inhalt. Ersetzen Sie in der pom.xml die Zeile "<artifactId>JaxRsMitMaven</artifactId>" durch "<artifactId>JaxRsContractFirst</artifactId>".
Ersetzen Sie im src\main\webapp\WEB-INF-Verzeichnis den Inhalt der web.xml durch den bereits in obiger web.xml gezeigten Inhalt.
Erstellen Sie im xsd-Verzeichnis folgende Contract-First-Schema-XSD-Datei: MeinInpResSchema.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="mein.ns"
targetNamespace="mein.ns"
elementFormDefault="qualified">
<xs:element name="inputTO" type="InputTO" />
<xs:element name="resultTO" type="ResultTO" />
<xs:complexType name="InputTO">
<xs:sequence>
<xs:element name="i" type="xs:int" />
<xs:element name="s" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="ResultTO">
<xs:sequence>
<xs:element name="i" type="xs:int" />
<xs:element name="s" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:schema>
Bitte beachten Sie, dass die Schema-XSD-Datei den Namespace mein.ns enthält (der in realen Anwendungen mit Ihrer umgekehrten Domain-Adresse beginnen sollte).
Generieren Sie mit dem beim Java-6-JDK mitgelieferten JAXB-xjc-Tool aus der Schema-XSD-Datei Java-Klassen:
cd \MeinWorkspace\JaxRsContractFirst
xjc -d src/main/java -p contractfirstgenerated xsd/MeinInpResSchema.xsd
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JaxRsContractFirst] |- [src] | |- [main] | | |- [java] | | | |- [contractfirstgenerated] | | | | |- InputTO.java | | | | |- ObjectFactory.java | | | | |- package-info.java | | | | '- ResultTO.java | | | '- [contractfirstservice] | | '- [webapp] | | |- [WEB-INF] | | | '- web.xml | | '- index.jsp | '- [test] | '- [java] | '- [contractfirstservice] |- [xsd] | '- MeinInpResSchema.xsd '- pom.xml
Sehen Sie sich die generierten Klassen im src\main\java\contractfirstgenerated-Verzeichnis an. Beachten Sie, dass package-info.java keine Java-Klasse enthält, sondern nur Package-bezogene Annotationen mit XML- und Namespace-Informationen, und beachten Sie die beiden JAXBElement-create...()-Methoden in ObjectFactory.java, mit denen Objekte erzeugt, werden können, die zusätzlich zum Transferobjekt auch XML- und Namespace-Informationen enthalten.
Fügen Sie im src\main\java\contractfirstgenerated-Verzeichnis in der generierten Klasse ResultTO.java vor dem Klassennamen "public class ResultTO" folgende Zeile hinzu:
@javax.xml.bind.annotation.XmlRootElement
Erstellen Sie im Verzeichnis src\main\java\contractfirstservice die Service-Klasse: ContractfirstService.java
package contractfirstservice;
import javax.ws.rs.*;
import contractfirstgenerated.*;
@Path( "/contractfirst" )
public class ContractfirstService
{
@POST
@Consumes( "text/xml" )
@Produces( "text/xml" )
public ResultTO doContractfirstService( InputTO inp )
{
ResultTO res = new ResultTO();
res.setI( inp.getI() * 2 );
res.setS( inp.getS() + " - ret" );
return res;
}
}
Erstellen Sie im Testverzeichnis src\test\java\contractfirstservice die Unittestklasse: ContractfirstServiceTest.java
package contractfirstservice;
import javax.xml.bind.JAXBElement;
import junit.framework.TestCase;
import com.sun.grizzly.http.SelectorThread;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.container.grizzly.GrizzlyServerFactory;
import contractfirstgenerated.*;
public class ContractfirstServiceTest extends TestCase
{
public void testContractfirstService() throws Exception
{
String urlSrvr = "http://localhost:4434";
String urlClnt = urlSrvr + "/contractfirst";
// Testserver:
SelectorThread srv = GrizzlyServerFactory.create( urlSrvr );
try {
// Testclient:
WebResource wrs = Client.create().resource( urlClnt );
// Mit JAXB und mit bequemen Java-Objekten:
InputTO inpTO = new InputTO();
inpTO.setI( 42 );
inpTO.setS( "abc xyz" );
JAXBElement<InputTO> inpJaxb = (new ObjectFactory()).createInputTO( inpTO );
ResultTO resTO = wrs.type( "text/xml" ).accept( "text/xml" ).post( ResultTO.class, inpJaxb );
assertEquals( 84, resTO.getI() );
assertEquals( "abc xyz - ret", resTO.getS() );
// Ohne JAXB und mit XML-Strings (Namespace beachten):
String resXml = wrs.type( "text/xml" ).accept( "text/xml" ).post(
String.class, "<inputTO xmlns='mein.ns'><i>42</i><s>abc xyz</s></inputTO>" );
assertEquals( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<resultTO xmlns=\"mein.ns\"><i>84</i><s>abc xyz - ret</s></resultTO>", resXml );
} finally {
// Testserver beenden:
srv.stopEndpoint();
}
}
}
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JaxRsContractFirst] |- [src] | |- [main] | | |- [java] | | | |- [contractfirstgenerated] | | | | |- InputTO.java | | | | |- ObjectFactory.java | | | | |- package-info.java | | | | '- ResultTO.java | | | '- [contractfirstservice] | | | |- ContractfirstService.java | | '- [webapp] | | |- [WEB-INF] | | | '- web.xml | | '- index.jsp | '- [test] | '- [java] | '- [contractfirstservice] | '- ContractfirstServiceTest.java |- [xsd] | '- MeinInpResSchema.xsd '- pom.xml
Führen Sie die Unittests aus:
mvn test
Falls Sie in der Unittestklasse ContractfirstServiceTest.java vor dem Stoppen des Testservers eine Warteschleife hinzufügen (z.B. Thread.sleep( 20000 );), können Sie den REST-Service auch mit cURL ansprechen:
curl -i -H Content-type:text/xml --request POST -d "<inputTO xmlns='mein.ns'><i>42</i><s>abc xyz</s></inputTO>" "http://localhost:4434/contractfirst"
Sie erhalten:
HTTP/1.1 200 OK Content-Type: text/xml Content-Length: 121 <?xml version="1.0" encoding="UTF-8" standalone="yes"?><resultTO xmlns="mein.ns"><i>84</i><s>abc xyz - ret</s></resultTO>
Bitte beachten Sie, dass Sie diesmal den korrekten Namespace xmlns='mein.ns' angeben müssen.
Sie können mit mvn package eine WAR-Datei im target-Verzeichnis erzeugen und in einen Java EE Webserver oder Application Server deployen.
Beispielsweise im Falle von WebLogic können Sie dann für einen Test in der ContractfirstServiceTest-Testklasse setzen:
urlClnt = "http://localhost:7001/JaxRsContractFirst/rs/contractfirst";
Oder Sie verwenden cURL:
curl -i -H Content-type:text/xml --request POST -d "<inputTO xmlns='mein.ns'><i>42</i><s>abc xyz</s></inputTO>" "http://localhost:7001/JaxRsContractFirst/rs/contractfirst"
Falls Sie nicht das gewünschte Ergebnis erhalten, prüfen Sie, ob Ihr Input-XML-Format wirklich dem XSD-Schema entspricht:
Führen Sie eine Validierung durch, beispielsweise mit:
XsdValidation.java.
Alternativ können Sie auch das inputTO-XML-Rootelement erweitern um die Attribute
"xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='mein.ns MeinInpResSchema.xsd'":
Dann können Sie auch in Eclipse validieren (mit der rechten Maustaste).
Falls Sie eine vollständige WADL-Datei haben, können Sie daraus Client-seitige Stubs mit wadl2java generieren.
Falls Sie keine Schema-XSD-Datei haben, aber eine WSDL-Beschreibung, können Sie die Java-Klassen statt mit xjc auch mit wsimport generieren.
Im folgenden Beispiel wird für den Server statt Grizzly der embedded GlassFish verwendet. Sie brauchen hierfür GlassFish nicht zu installieren. Innerhalb des JUnit-Tests wird embedded GlassFish gestartet, das Deployment durchgeführt, Tests ausgeführt und embedded GlassFish wieder heruntergefahren.
Das Beispiel erweitert obiges JaxRsContractFirst-Beispiel.
Führen Sie in Ihrem Projekte-Workspace-Verzeichnis folgende Kommandos aus:
cd \MeinWorkspace
xcopy JaxRsContractFirst JaxRsEmbeddedGlassFish\ /S
cd JaxRsEmbeddedGlassFish
Ersetzen Sie im JaxRsEmbeddedGlassFish-Projektverzeichnis 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>minirestwebservice</groupId>
<artifactId>JaxRsEmbeddedGlassFish</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>${artifactId}</name>
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.1.5.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.1.5.1</version>
</dependency>
<dependency>
<!-- Falls Sie zusaetzlich zum Webcontainer weitere GlassFish-Module benoetigen:
"glassfish-embedded-all" statt "glassfish-embedded-web" verwenden: -->
<groupId>org.glassfish.extras</groupId>
<artifactId>glassfish-embedded-web</artifactId>
<version>3.0.1-b02</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.12.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Falls Sie fehlende Libs direkt uebers Internet nachladen, benoetigen Sie folgende Repository-Angaben
(bitte entfernen, falls Sie einen Repository-Manager wie Archiva konfiguriert haben): -->
<repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Maven 2 Repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
<repository>
<id>maven-repository.dev.java.net</id>
<name>Java.net Maven 1 Repository (legacy)</name>
<url>http://download.java.net/maven/1</url>
<layout>legacy</layout>
</repository>
<repository>
<id>glassfish-repository</id>
<name>Java.net Repository for Glassfish</name>
<url>http://download.java.net/maven/glassfish</url>
</repository>
</repositories>
</project>
Ersetzen Sie im src\main\webapp\WEB-INF-Verzeichnis den Inhalt der web.xml durch:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
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"
id="WebApp_ID" version="2.5">
<display-name>REST</display-name>
<servlet>
<display-name>JAX-RS REST Servlet</display-name>
<servlet-name>REST-Servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>contractfirstservice</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>REST-Servlet</servlet-name>
<url-pattern>/rs/*</url-pattern>
</servlet-mapping>
</web-app>
Ersetzen Sie im src\test\java\contractfirstservice-Verzeichnis den Inhalt der ContractfirstServiceTest.java durch:
package contractfirstservice;
import java.io.File;
import java.net.URI;
import javax.xml.bind.JAXBElement;
import junit.framework.TestCase;
import org.glassfish.api.deployment.DeployCommandParameters;
import org.glassfish.api.embedded.*;
import com.sun.jersey.api.client.*;
import contractfirstgenerated.*;
public class ContractfirstServiceTest extends TestCase
{
private Server glassfish;
private EmbeddedDeployer deployer;
private WebResource webResource; // (WebResource ist thread safe)
@Override protected void setUp() throws Exception
{
super.setUp();
// Embedded GlassFish Server:
final URI BASE_URI = new URI( "http://localhost:4434/JaxRsEmbeddedGlassFish/rs" );
final int PORT = 4434;
final String NAME = "JaxRsEmbeddedGlassFish";
glassfish = (new Server.Builder( NAME )).build();
glassfish.addContainer( glassfish.createConfig( ContainerBuilder.Type.web ) );
glassfish.createPort( PORT );
glassfish.start();
// Deploy WAR:
ScatteredArchive.Builder war = new ScatteredArchive.Builder( NAME, new File( "target/" ) );
war.resources( new File( "src/main/webapp" ) );
war.addMetadata( new File( "src/main/webapp/WEB-INF/web.xml" ) );
war.addClassPath( new File( "target/classes" ).toURI().toURL() );
DeployCommandParameters params = new DeployCommandParameters();
params.name = NAME;
deployer = glassfish.getDeployer();
deployer.deploy( war.buildWar(), params );
// Client:
webResource = Client.create().resource( BASE_URI );
}
@Override protected void tearDown() throws Exception
{
super.tearDown();
deployer.undeployAll();
glassfish.stop();
}
public void testContractfirstServiceMitEmbeddedGlassFish()
{
// Mit JAXB und mit bequemen Java-Objekten:
InputTO inpTO = new InputTO();
inpTO.setI( 42 );
inpTO.setS( "abc xyz" );
JAXBElement<InputTO> inpJaxb = (new ObjectFactory()).createInputTO( inpTO );
ResultTO resTO = webResource.path( "contractfirst" ).type( "text/xml" ).accept( "text/xml" ).post(
ResultTO.class, inpJaxb );
assertEquals( 84, resTO.getI() );
assertEquals( "abc xyz - ret", resTO.getS() );
// Ohne JAXB und mit XML-Strings (Namespace beachten):
String resXml = webResource.path( "contractfirst" ).type( "text/xml" ).accept( "text/xml" ).post(
String.class, "<inputTO xmlns='mein.ns'><i>42</i><s>abc xyz</s></inputTO>" );
assertEquals( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<resultTO xmlns=\"mein.ns\"><i>84</i><s>abc xyz - ret</s></resultTO>", resXml );
}
}
Führen Sie den JUnit-Test aus:
mvn test
Es erscheint:
... INFO: GlassFish v3 ... startup ... ... INFO: Loading application JaxRsEmbeddedGlassFish at /JaxRsEmbeddedGlassFish ... com.sun.jersey.api.core.PackagesResourceConfig init INFO: Scanning for root resource and provider classes in the packages: contractfirstservice ... INFO: Root resource classes found: class contractfirstservice.ContractfirstService ... Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 ... [INFO] BUILD SUCCESSFUL
Falls Sie in der tearDown()-Methode vor dem Undeploy eine Warteschleife hinzufügen (z.B. Thread.sleep( 20000 );), können Sie den REST-Service auch mit cURL ansprechen:
curl -i -H Content-type:text/xml --request POST -d "<inputTO xmlns='mein.ns'><i>42</i><s>abc xyz</s></inputTO>" "http://localhost:4434/JaxRsEmbeddedGlassFish/rs/contractfirst"
curl -i "http://localhost:4434/JaxRsEmbeddedGlassFish/rs/application.wadl"
Im folgenden Beispiel wird das JaxRsContractFirst-Beispiel um Authentifizierung und um einen Integrationstest mit Tomcat erweitert.
Installieren Sie Tomcat 6, zum Beispiel wie beschrieben unter jsp-install.htm#InstallationUnterWindows.
Führen Sie in Ihrem Projekte-Workspace-Verzeichnis folgende Kommandos aus:
cd \MeinWorkspace
xcopy JaxRsContractFirst JaxRsAuthentication\ /S
cd JaxRsAuthentication
md src\test\java\integrationstest
Ersetzen Sie im JaxRsAuthentication-Projektverzeichnis 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>restwebservice</groupId>
<artifactId>JaxRsAuthentication</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>JaxRsAuthentication: RESTful-Webservice mit Integrationstest</name>
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<configuration>
<container>
<containerId>${appsrv.containerId}</containerId>
<home>${appsrv.srvhome}</home>
</container>
<configuration>
<type>existing</type>
<home>${appsrv.dmnhome}</home>
<!--
<properties>
<cargo.remote.username>${appsrv.usr}</cargo.remote.username>
<cargo.remote.password>${appsrv.pwd}</cargo.remote.password>
</properties>
-->
</configuration>
<wait>false</wait>
</configuration>
<executions>
<execution>
<id>start-container</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-container</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>
<configuration>
<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>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.1.5.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.1.5.1</version>
</dependency>
<dependency>
<groupId>com.sun.grizzly</groupId>
<artifactId>grizzly-servlet-webserver</artifactId>
<version>1.9.18</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.12.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<!-- Falls Sie fehlende Libs direkt uebers Internet nachladen, benoetigen Sie folgende Repository-Angaben
(bitte entfernen, falls Sie einen Repository-Manager wie Archiva konfiguriert haben): -->
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Maven 2 Repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
</repositories>
<properties>
<appsrv.containerId>tomcat6x</appsrv.containerId>
<appsrv.srvhome>\Java\Tomcat</appsrv.srvhome>
<appsrv.dmnhome>${appsrv.srvhome}</appsrv.dmnhome>
</properties>
</project>
Passen Sie den Pfad in <appsrv.srvhome>\Java\Tomcat</appsrv.srvhome> an Ihre Tomcat-Installation an.
Ersetzen Sie im src\main\webapp\WEB-INF-Verzeichnis den Inhalt der web.xml durch:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
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"
id="WebApp_ID" version="2.5">
<display-name>REST</display-name>
<servlet>
<display-name>JAX-RS REST Servlet</display-name>
<servlet-name>REST-Servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>REST-Servlet</servlet-name>
<url-pattern>/rs/*</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>JaxRsAuthentication</web-resource-name>
<url-pattern>/rs/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>manager</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Mein Applikations-Realm</realm-name>
</login-config>
<security-role>
<role-name>manager</role-name>
</security-role>
</web-app>
Erzeugen Sie im src\test\java\integrationstest-Verzeichnis folgende Testklasse: JaxRsAuthenticationIntegrTest.java
package integrationstest;
import javax.xml.bind.JAXBElement;
import junit.framework.TestCase;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import contractfirstgenerated.*;
public class JaxRsAuthenticationIntegrTest extends TestCase
{
public void testJaxRsAuthentication() throws Exception
{
// Testclient:
String url = "http://localhost:8080/JaxRsAuthentication/rs/contractfirst";
String usr = "MeinName";
String pwd = "MeinPasswort";
Client clt = Client.create();
clt.addFilter( new HTTPBasicAuthFilter( usr, pwd ) );
WebResource wrs = clt.resource( url );
// Mit JAXB und mit bequemen Java-Objekten:
InputTO inpTO = new InputTO();
inpTO.setI( 42 );
inpTO.setS( "abc xyz" );
JAXBElement<InputTO> inpJaxb = (new ObjectFactory()).createInputTO( inpTO );
ResultTO resTO = wrs.type( "text/xml" ).accept( "text/xml" ).post( ResultTO.class, inpJaxb );
assertEquals( 84, resTO.getI() );
assertEquals( "abc xyz - ret", resTO.getS() );
// Ohne JAXB und mit XML-Strings (Namespace beachten):
String resXml = wrs.type( "text/xml" ).accept( "text/xml" ).post(
String.class, "<inputTO xmlns='mein.ns'><i>42</i><s>abc xyz</s></inputTO>" );
assertEquals( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<resultTO xmlns=\"mein.ns\"><i>84</i><s>abc xyz - ret</s></resultTO>", resXml );
}
}
Beachten Sie, dass diesmal innerhalb der Integrationstestklasse kein Server gestartet wird, weil durch die Maven-Integrationstest-Konfiguration Tomcat gestartet wird.
Fügen Sie in Ihrer tomcat-users.xml in Ihrem Tomcat-conf-Verzeichnis (z.B. \Java\Tomcat\conf) die benötigten Benutzer und Rollen hinzu, zum Beispiel so:
<?xml version='1.0' encoding='utf-8'?> <tomcat-users> <role rolename="admin"/> <role rolename="manager"/> <user username="MeinName" password="MeinPasswort" roles="admin,manager"/> </tomcat-users>
Starten Sie den Integrationstest:
mvn integration-test
Beachten Sie, dass Tomcat automatisch hoch- und wieder heruntergefahren wird.
Falls Sie in der Integrationstestklasse JaxRsAuthenticationIntegrTest.java eine Warteschleife hinzufügen (z.B. Thread.sleep( 20000 );), oder alternativ die WAR-Datei normal deployen, können Sie den REST-Service auch mit cURL ansprechen:
curl -u "MeinName:MeinPasswort" -i -H Content-type:text/xml --request POST -d "<inputTO xmlns='mein.ns'><i>42</i><s>abc xyz</s></inputTO>" "http://localhost:8080/JaxRsAuthentication/rs/contractfirst"
Bitte beachten Sie, dass Sie diesmal für die HTTP Basic Authentication mit -u den Benutzernamen und das Kennwort angeben müssen.
Alle obigen Beispiele, die Maven verwenden, können Sie auch mit Eclipse verwenden: Bereiten Sie Eclipse vor wie unter maven.htm#Eclipse beschrieben, führen Sie das Kommando "mvn eclipse:eclipse" aus, und laden Sie die Projekte in Eclipse.
Falls Sie nicht Maven verwenden wollen, können Sie auch wie im Folgenden beschrieben die Projekte ohne Maven und mit Eclipse (und Tomcat) aufsetzen.
Starten Sie in Eclipse ein neues Web-Projekt über:
'File' | 'New' | 'Project...' | '[+] Web' | 'Dynamic Web Project' | 'Next >'.
Tragen Sie ein:
| Project name: | JaxRsMitEclipse |
| Use default: | Ja |
| Target Runtime: | Apache Tomcat v6.0 |
| Configurations: | Default Configuration for Apache Tomcat v6.0 |
| Add project to an EAR: | Nein |
'Next >'
Falls der Project Facets Dialog erscheint: Bei 'Java' sollte mindestens '5.0' eingetragen sein.
'Next >'
Tragen Sie in den folgenden Dialogen ein:
| Java Source folders: | src |
| Java Default output folder: | build\classes |
| Context Root: | JaxRsMitEclipse |
| Content Directory: | WebContent |
| Generate Deployment Descriptor: | Ja |
'Finish'
Downloaden Sie jersey-archive-1.1.5.1.zip von https://jersey.dev.java.net oder von http://download.java.net/maven/2/com/sun/jersey/jersey-archive/1.1.5.1.
Entzippen Sie das Jersey-Archive in ein temporäres Verzeichnis und kopieren Sie entweder alle .jar-Libraries oder die Libs asm-3.1.jar, jersey-client-1.1.5.1.jar, jersey-core-1.1.5.1.jar, jersey-server-1.1.5.1.jar und jsr311-api-1.1.1.jar aus dem jersey-archive-1.1.5.1/lib-Verzeichnis in das JaxRsMitEclipse/WebContent/WEB-INF/lib-Verzeichnis.
Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf den 'JaxRsMitEclipse'-Projektnamen und wählen Sie 'Refresh' (oder betätigen Sie 'F5'), damit Eclipse die .jar-Libraries registriert.
Öffnen Sie im Eclipse Project Explorer das 'JaxRsMitEclipse'-Projekt und darunter den 'WebContent'-Zweig und das 'WEB-INF'-Verzeichnis. Öffnen Sie die web.xml, schalten Sie eventuell von der 'Design'-Ansicht auf die 'Source'-Ansicht um, und ersetzen Sie den Inhalt durch:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
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"
id="WebApp_ID" version="2.5">
<display-name>JaxRsMitEclipse</display-name>
<servlet>
<display-name>JAX-RS REST Servlet</display-name>
<servlet-name>REST-Servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>REST-Servlet</servlet-name>
<url-pattern>/rs/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf den 'JaxRsMitEclipse'-Projektnamen und wählen Sie 'New' | 'HTML' | 'File name' = 'index.html' | 'Finish'.
Ändern Sie den Inhalt von index.html zu:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Meine RESTful-Webservice-Startseite</title> </head> <body> <h3> Meine RESTful-Webservice-Startseite </h3> <p> <a href="/JaxRsMitEclipse/rs/application.wadl">/JaxRsMitEclipse/rs/application.wadl</a> </p> <p> <a href="/JaxRsMitEclipse/rs/helloworld?name=ich">/JaxRsMitEclipse/rs/helloworld?name=ich</a> </p> </body> </html>
Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf den 'JaxRsMitEclipse'-Projektnamen, wählen Sie 'New' | 'Package' und tragen Sie ein:
| Source folder: | JaxRsMitEclipse/src |
| Name: | minirestwebservice |
'Finish'
Öffnen Sie im Eclipse Project Explorer das 'JaxRsMitEclipse'-Projekt und darunter den 'Java Resources: src'-Zweig. Klicken Sie mit der rechten Maustaste auf den Package-Namen 'minirestwebservice', wählen Sie 'New' | 'Class' und tragen Sie ein:
| Source folder: | JaxRsMitEclipse/src |
| Package: | minirestwebservice |
| Name: | HalloWeltService |
'Finish'
Ändern Sie den Inhalt von HalloWeltService.java zu:
package minirestwebservice;
import javax.ws.rs.*;
@Path( "/helloworld" )
public class HalloWeltService
{
@GET @Produces( "text/plain" )
public String halloText( @QueryParam("name") String name )
{
return "Hallo " + name;
}
@GET @Produces( "text/html" )
public String halloHtml( @QueryParam("name") String name )
{
return "<html><title>HelloWorld</title><body><h2>Html: Hallo " + name + "</h2></body></html>";
}
}
Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf den 'JaxRsMitEclipse'-Projektnamen und wählen Sie 'Run As' | 'Run on Server'.
Falls der Dialog 'Define a New Server' oder 'Run On Server' erscheint:
Wählen Sie 'Tomcat v6.0 Server at localhost' und
aktivieren Sie die Checkbox 'Always use this server when running this project'.
'Finish'.
Falls Sie danach gefragt werden: Aktivieren Sie 'Update context root ...'.
Falls Tomcat nicht automatisch gestartet wurde:
Klicken Sie unten auf den 'Servers'-Tabulatorreiter und starten Sie Tomcat
(rechte Maustaste auf 'Tomcat v6.0 Server at localhost'-Eintrag oder über den grünen Kreis-Button).
Lassen Sie sich über http://localhost:8080/JaxRsMitEclipse/rs/application.wadl die WADL-Schnittstellenbeschreibung anzeigen (Web Application Description Language). Da der HalloWeltService-Dienst identisch zu obigem HalloWeltService vom Grizzly-HelloWorld-Beispiel ist, ist auch die WADL-Datei sehr ähnlich zu obiger WADL-Datei.
Lesen Sie die HelloWorld-Ausgabe:
http://localhost:8080/JaxRsMitEclipse/rs/helloworld?name=ich
Je nach Lese-Tool bzw. Browser erhalten Sie entweder die Plain-Text- oder die HTML-Ausgabe.
Falls Sie die URL-Bestandteile ändern wollen:
"JaxRsMitEclipse" können Sie ändern über: Rechtsklick im Eclipse Project Explorer auf den 'JaxRsMitEclipse'-Projektnamen, 'Properties', 'Web Project Settings', 'Context Root'.
"rs" können Sie ändern über 'url-pattern' in:
<MeinWorkspace>\JaxRsMitEclipse\WebContent\WEB-INF\web.xml.
"helloworld" können Sie ändern über '@Path( "/helloworld" )' in:
<MeinWorkspace>\JaxRsMitEclipse\src\minirestwebservice\HalloWeltService.java.
Mit Jersey lassen sich sehr einfach Java-Clients für RESTful-Webservices programmieren. Das folgende Beispiel liest sowohl die Text- als auch die HTML-Ausgabe des obigen HelloWorld-Beispiels.
Erzeugen Sie im Package minirestwebservice die Java-Klasse HalloWeltClient.java:
package minirestwebservice;
import com.sun.jersey.api.client.*;
public class HalloWeltClient
{
public static void main( String[] args )
{
String url = ( args.length > 0 ) ? args[0] : "http://localhost:8080/JaxRsMitEclipse/rs";
String nam = ( args.length > 1 ) ? args[1] : "ich";
url = url + "/helloworld?name=" + nam;
System.out.println( "URL: " + url );
WebResource wrs = Client.create().resource( url );
System.out.println( "\nTextausgabe:" );
System.out.println( wrs.accept( "text/plain" ).get( String.class ) );
System.out.println( "\nHTML-Ausgabe:" );
System.out.println( wrs.accept( "text/html" ).get( String.class ) );
}
}
Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf die neu erstellte Java-Klasse HalloWeltClient.java und wählen Sie: 'Run As' | 'Java Application'.
Die Ausgabe sieht folgendermaßen aus:
Textausgabe: Hallo ich HTML-Ausgabe: <html><title>HelloWorld</title><body><h2>Html: Hallo ich</h2></body></html>
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JaxRsMitMaven]
|- [build]
| '- [classes]
| '- ...
|- [src]
| '- [minirestwebservice]
| |- HalloWeltClient.java
| '- HalloWeltService.java
'- [WebContent]
|- [META-INF]
| '- MANIFEST.MF
|- [WEB-INF]
| '- [lib]
| | |- asm-3.1.jar
| | |- jersey-client-1.1.5.1.jar
| | |- jersey-core-1.1.5.1.jar
| | |- jersey-server-1.1.5.1.jar
| | '- jsr311-api-1.1.1.jar
| '- web.xml
'- index.html
Natürlich können Sie auch für dieses Beispiel wieder cURL, Wget und die Firefox-Plug-ins RESTClient, Poster und Firebug einsetzen.
Installieren Sie Tomcat, zum Beispiel wie beschrieben unter jsp-install.htm#InstallationUnterWindows.
Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. D:\MeinWorkspace) und führen Sie folgende Kommandos aus:
cd \MeinWorkspace
mvn archetype:generate -DinteractiveMode=false -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=de.meinefirma.meinprojekt -DartifactId=JaxRsBuecherverwaltung
cd JaxRsBuecherverwaltung
tree /F
del src\main\webapp\index.jsp
rd src\main\resources
md src\main\java\de\meinefirma\meinprojekt\buecher
md src\main\java\de\meinefirma\meinprojekt\dao
md src\main\java\de\meinefirma\meinprojekt\rest
md src\main\java\de\meinefirma\meinprojekt\client
tree /F
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>buecher</groupId>
<artifactId>JaxRsBuecherverwaltung</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>${artifactId}</name>
<build>
<finalName>${artifactId}</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>de.meinefirma.meinprojekt.client.BuecherRestClient</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.1.5.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.1.5.1</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.12.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Falls Sie fehlende Libs direkt uebers Internet nachladen, benoetigen Sie folgende Repository-Angaben
(bitte entfernen, falls Sie einen Repository-Manager wie Archiva konfiguriert haben): -->
<repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Maven 2 Repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
<repository>
<id>maven-repository.dev.java.net</id>
<name>Java.net Maven 1 Repository (legacy)</name>
<url>http://download.java.net/maven/1</url>
<layout>legacy</layout>
</repository>
</repositories>
</project>
Ersetzen Sie im src\main\webapp\WEB-INF-Verzeichnis den Inhalt der web.xml durch:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
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"
id="WebApp_ID" version="2.5">
<display-name>JaxRsBuecherverwaltung</display-name>
<servlet>
<display-name>JAX-RS REST Servlet</display-name>
<servlet-name>REST-Servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>REST-Servlet</servlet-name>
<url-pattern>/rs/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\buecher-Verzeichnis die Buch-Domainobjekt-Klasse: BuchDO.java
package de.meinefirma.meinprojekt.buecher;
import javax.xml.bind.annotation.XmlRootElement;
/** Buch-Domainobjekt */
@XmlRootElement
public class BuchDO
{
private Long id;
private String isbn;
private String titel;
private Double preis;
public Long getId() { return id; }
public String getIsbn() { return isbn; }
public String getTitel() { return titel; }
public Double getPreis() { return preis; }
public void setId( Long id ) { this.id = id; }
public void setIsbn( String 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\buecher-Verzeichnis die Rueckgabe-Transferobjekt-Klasse: BuecherTO.java
package de.meinefirma.meinprojekt.buecher;
import java.util.*;
import javax.xml.bind.annotation.*;
/** Rueckgabe-Transferobjekt */
@XmlRootElement
public class BuecherTO
{
private Integer returncode;
private String message;
@XmlElement(nillable = true)
private List<BuchDO> results = new ArrayList<BuchDO>();
public Integer getReturncode() { return returncode; }
public String getMessage() { return message; }
public List<BuchDO> getResults() { return results; }
public void setReturncode( Integer returncode ) { this.returncode = returncode; }
public void setMessage( String message ) { this.message = message; }
}
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\dao-Verzeichnis die CRUD-DAO-Klasse: BuchDoDAO.java
package de.meinefirma.meinprojekt.dao;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import de.meinefirma.meinprojekt.buecher.BuchDO;
/** DAO (Data Access Object) fuer CRUD-Operationen (Create, Read, Update, Delete) */
public class BuchDoDAO
{
// Map als Datenbank-Simulation:
private Map<Long,BuchDO> buecherPool = new ConcurrentHashMap<Long,BuchDO>();
private static final BuchDoDAO INSTANCE = new BuchDoDAO();
private static final long DFLT_ID = 4710;
private BuchDoDAO()
{
}
public static BuchDoDAO getInstance()
{
return INSTANCE;
}
// Neues Buch hinzufuegen:
public BuchDO createBuch( BuchDO bu )
{
synchronized( buecherPool ) {
if( bu.getId() != null ) {
if( getBuchById( bu.getId() ) != null )
throw new RuntimeException(
"Fehler: Es gibt bereits ein Buch mit der ID " + bu.getId() + "." );
} else {
long maxId = ( buecherPool.size() > 0 )
? Collections.max( buecherPool.keySet() ).longValue() : DFLT_ID;
bu.setId( Long.valueOf( ++maxId ) );
}
buecherPool.put( bu.getId(), bu );
return bu;
}
}
// Finde Buch mit ID:
public BuchDO getBuchById( Long id )
{
return ( id == null ) ? null : buecherPool.get( id );
}
// Finde Buecher nach Suchkriterien:
public List<BuchDO> findeBuecher( Long id, String isbn, String titel )
{
List<BuchDO> resultList = new ArrayList<BuchDO>();
List<BuchDO> snapshotList;
if( id == null && isEmpty( isbn ) && isEmpty( titel ) )
return Collections.unmodifiableList( new ArrayList<BuchDO>( buecherPool.values() ) );
if( id != null && isEmpty( isbn ) && isEmpty( titel ) ) {
BuchDO bu = getBuchById( id );
if( bu != null ) resultList.add( bu );
return resultList;
}
synchronized( buecherPool ) {
snapshotList = new ArrayList<BuchDO>( buecherPool.values() );
}
String isbnLC = ( isbn == null ) ? null : isbn.trim().toLowerCase();
String titelLC = ( titel == null ) ? null : titel.trim().toLowerCase();
for( BuchDO bu : snapshotList )
if( (id != null && bu.getId() != null && id.equals( bu.getId() )) ||
(!isEmpty( bu.getIsbn() ) && !isEmpty( isbnLC ) &&
bu.getIsbn().trim().toLowerCase().contains( isbnLC )) ||
(!isEmpty( bu.getTitel() ) && !isEmpty( titelLC ) &&
bu.getTitel().trim().toLowerCase().contains( titelLC )) )
resultList.add( bu );
return resultList;
}
// Daten eines per ID definierten Buches aendern:
public BuchDO updateBuchById( BuchDO bu )
{
synchronized( buecherPool ) {
BuchDO buAlt = buecherPool.get( bu.getId() );
if( buAlt == null )
throw new RuntimeException( "Fehler: Es gibt kein Buch mit der ID " + bu.getId() + "." );
buecherPool.put( bu.getId(), bu );
return bu;
}
}
// Per ID definiertes Buch loeschen:
public BuchDO deleteBuchById( Long id )
{
synchronized( buecherPool ) {
return buecherPool.remove( id );
}
}
private static boolean isEmpty( String s )
{
return s == null || s.trim().length() <= 0;
}
}
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\dao-Verzeichnis die Util-Klasse: BuecherUtil.java
package de.meinefirma.meinprojekt.dao;
import java.util.List;
import de.meinefirma.meinprojekt.buecher.BuchDO;
import de.meinefirma.meinprojekt.buecher.BuecherTO;
public class BuecherUtil
{
public static final Integer RET_CODE_OK = new Integer( 0 );
public static final Integer RET_CODE_ERROR = new Integer( -1 );
// Finde Buecher nach Suchkriterien:
public static BuecherTO findeBuecher( Long id, String isbn, String titel )
{
BuecherTO buecherTO = new BuecherTO();
List<BuchDO> buecherListe = BuchDoDAO.getInstance().findeBuecher( id, isbn, titel );
if( buecherListe == null )
return fehlerBuecherTO();
if( id == null && isEmpty( isbn ) && isEmpty( titel ) ) {
buecherTO.setMessage( buecherListe.size() + " Buecher" );
} else {
StringBuffer sb = new StringBuffer();
sb.append( buecherListe.size() + " Ergebnis(se) fuer" );
if( id != null ) sb.append( " ID = " + id );
if( !isEmpty( isbn ) ) sb.append( " ISBN = " + isbn );
if( !isEmpty( titel ) ) sb.append( " Titel = " + titel );
buecherTO.setMessage( sb.toString() );
}
buecherTO.getResults().addAll( buecherListe );
buecherTO.setReturncode( RET_CODE_OK );
return buecherTO;
}
public static BuchDO erzeugeBuchDO( Long id, String isbn, String titel, Double preis )
{
BuchDO buchDO = new BuchDO();
buchDO.setId( id );
buchDO.setIsbn( isbn );
buchDO.setTitel( titel );
buchDO.setPreis( preis );
return buchDO;
}
public static BuecherTO erzeugeBuecherTO( String msg, BuchDO buchDO )
{
if( buchDO == null ) return fehlerBuecherTO();
BuecherTO buecherTO = new BuecherTO();
buecherTO.getResults().add( buchDO );
buecherTO.setMessage( msg );
buecherTO.setReturncode( RET_CODE_OK );
return buecherTO;
}
public static BuecherTO fehlerBuecherTO()
{
BuecherTO buecherTO = new BuecherTO();
buecherTO.setMessage( "Parameterfehler" );
buecherTO.setReturncode( RET_CODE_ERROR );
return buecherTO;
}
public static boolean isEmpty( String s )
{
return s == null || s.trim().length() <= 0;
}
}
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\rest-Verzeichnis die Java-Klasse BuecherRestService.java, in welcher die vier HTTP-Verben GET, PUT, POST und DELETE als RESTful-Webservice REST-konform implementiert werden:
package de.meinefirma.meinprojekt.rest;
import javax.ws.rs.*;
import de.meinefirma.meinprojekt.buecher.*;
import de.meinefirma.meinprojekt.dao.*;
/** RESTful-Webservice */
@Produces( "text/xml" )
@Path( "/Artikel/Buecher" )
public class BuecherRestService
{
private BuchDoDAO dao = BuchDoDAO.getInstance();
// Per ID definiertes Buch ausgeben:
@GET @Path("{id}")
public BuecherTO getBuchById(
@PathParam("id") String id )
{
BuchDO bu = dao.getBuchById( longFromString( id ) );
return BuecherUtil.erzeugeBuecherTO( "Buch mit ID " + id, bu );
}
// Liste von ueber Suchkriterien gefundener Buecher ausgeben:
@GET
public BuecherTO getBuecherListe(
@QueryParam("id") String id,
@QueryParam("isbn") String isbn,
@QueryParam("titel") String titel )
{
return BuecherUtil.findeBuecher( longFromString( id ), isbn, titel );
}
// Daten eines per ID definierten Buches aendern:
@PUT @Path("{id}")
public BuecherTO updateBuchById(
@PathParam("id") String id,
@FormParam("isbn") String isbn,
@FormParam("titel") String titel,
@FormParam("preis") String preis )
{
BuchDO bu = BuecherUtil.erzeugeBuchDO( longFromString( id ), isbn, titel, doubleFromString( preis ) );
return BuecherUtil.erzeugeBuecherTO( "Buchdaten geaendert", dao.updateBuchById( bu ) );
}
// Neues Buch hinzufuegen (ueber Formular):
@POST
public BuecherTO createBuch(
@FormParam("isbn") String isbn,
@FormParam("titel") String titel,
@FormParam("preis") String preis )
{
BuchDO bu = BuecherUtil.erzeugeBuchDO( null, isbn, titel, doubleFromString( preis ) );
return BuecherUtil.erzeugeBuecherTO( "Buch hinzugefuegt", dao.createBuch( bu ) );
}
// Neues Buch hinzufuegen (ueber XML):
@POST @Consumes( "text/xml" )
public BuecherTO createBuch( BuchDO bu )
{
return BuecherUtil.erzeugeBuecherTO( "Buch hinzugefuegt", dao.createBuch( bu ) );
}
// Per ID definiertes Buch loeschen:
@DELETE @Path("{id}")
public BuecherTO deleteBuchById(
@PathParam("id") String id )
{
BuchDO bu = dao.deleteBuchById( longFromString( id ) );
return BuecherUtil.erzeugeBuecherTO( "Buch geloescht", bu );
}
private static Long longFromString( String s )
{
if( !BuecherUtil.isEmpty( s ) ) try { return new Long( s.trim() ); } catch( NumberFormatException ex ) {}
return null;
}
private static Double doubleFromString( String s )
{
if( !BuecherUtil.isEmpty( s ) ) try { return new Double( s.trim() ); } catch( NumberFormatException ex ) {}
return null;
}
}
Erzeugen Sie im src\main\webapp-Verzeichnis die HTML-Seite: index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Meine RESTful-Webservice-Startseite</title>
</head>
<body>
<h3> Meine RESTful-Webservice-Startseite </h3>
<h4><u> WADL </u></h4>
<p> <a href="/JaxRsBuecherverwaltung/rs/application.wadl">/JaxRsBuecherverwaltung/rs/application.wadl</a> </p>
<h4><u> Buecher-Verwaltung </u></h4>
<p> Zwei erste Bücher anlegen (alle Felder müssen ausgefüllt sein): </p>
<form method="POST" action="/JaxRsBuecherverwaltung/rs/Artikel/Buecher" enctype="application/x-www-form-urlencoded">
ISBN: <input type="text" name="isbn" value="1234567891" maxlength=20>
Titel: <input type="text" name="titel" value="MeinTitel1" maxlength=80>
Preis: <input type="text" name="preis" value="12.34" maxlength=20>
<input type="submit" value="Erstes Buch anlegen">
</form>
<form method="POST" action="/JaxRsBuecherverwaltung/rs/Artikel/Buecher" enctype="application/x-www-form-urlencoded">
ISBN: <input type="text" name="isbn" value="1234567892" maxlength=20>
Titel: <input type="text" name="titel" value="MeinTitel2" maxlength=80>
Preis: <input type="text" name="preis" value="22.34" maxlength=20>
<input type="submit" value="Zweites Buch anlegen">
</form>
<p> Abfragen (bitte vorher obige zwei Test-Bücher anlegen): </p>
<p> <a href="/JaxRsBuecherverwaltung/rs/Artikel/Buecher">/JaxRsBuecherverwaltung/rs/Artikel/Buecher</a> (alle Bücher) </p>
<p> <a href="/JaxRsBuecherverwaltung/rs/Artikel/Buecher/4711">/JaxRsBuecherverwaltung/rs/Artikel/Buecher/4711</a> (Buch mit ID 4711) </p>
<p> <a href="/JaxRsBuecherverwaltung/rs/Artikel/Buecher?isbn=1234567892">/JaxRsBuecherverwaltung/rs/Artikel/Buecher?isbn=1234567892</a> (Buch mit ISBN 1234567892) </p>
<p> <a href="/JaxRsBuecherverwaltung/rs/Artikel/Buecher/?titel=MeinTitel2">/JaxRsBuecherverwaltung/rs/Artikel/Buecher/?titel=MeinTitel2</a> (Buch mit Titel 'MeinTitel2') </p>
<p> <a href="/JaxRsBuecherverwaltung/rs/Artikel/Buecher/?isbn=1234567891&titel=MeinTitel2">/JaxRsBuecherverwaltung/rs/Artikel/Buecher/?isbn=1234567891&titel=MeinTitel2</a> (Bücher mit ISBN 1234567892 oder Titel 'MeinTitel2') </p>
<p> Weiteres neues Buch anlegen (alle Felder müssen ausgefüllt werden): </p>
<form method="POST" action="/JaxRsBuecherverwaltung/rs/Artikel/Buecher" enctype="application/x-www-form-urlencoded">
ISBN: <input type="text" name="isbn" value="1472583693" maxlength=20>
Titel: <input type="text" name="titel" value="Neuer Titel X" maxlength=80>
Preis: <input type="text" name="preis" value="123.45" maxlength=20>
<input type="submit" value="Neues Buch anlegen">
</form>
<p> Bücher finden (es genügt einzelne Felder zu füllen; als ISBN und Titel genügen Teilstrings zum Finden mehrerer passender Bücher): </p>
<form method="GET" action="/JaxRsBuecherverwaltung/rs/Artikel/Buecher/" enctype="application/x-www-form-urlencoded">
ISBN: <input type="text" name="isbn" maxlength=20>
Titel: <input type="text" name="titel" maxlength=80>
ID: <input type="text" name="id" maxlength=40>
<input type="submit" value="Bücher finden">
</form>
</body>
</html>
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JaxRsBuecherverwaltung] |- [src] | '- [main] | |- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | |- [buecher] | | | |- BuchDO.java | | | '- BuecherTO.java | | |- [client] | | |- [dao] | | | |- BuchDoDAO.java | | | '- BuecherUtil.java | | '- [rest] | | '- BuecherRestService.java | '- [webapp] | |- [WEB-INF] | | '- web.xml | '- index.html '- pom.xml
Die Package-Struktur ist etwas ungewöhnlich, aber so können zum einen leichter die DO- und TO-Klassen aus Schema-XSD-Dateien generiert werden ("Contract-First") und zum anderen kann leichter zusätzlich ein SOAP Webservice hinzugefügt werden.
Testen Sie den RESTful-Webservice (passen Sie die Pfade an):
cd /D \Java\Tomcat\bin
startup.bat
cd /D \MeinWorkspace\JaxRsBuecherverwaltung
mvn package
copy target\JaxRsBuecherverwaltung.war \Java\Tomcat\webapps
Klicken Sie auf der JaxRsBuecherverwaltung-Webseite auf die verschiedenen Links und sehen Sie sich die jeweiligen XML-Ergebnisse an.
Studieren Sie die
WADL-Beschreibung.
Fügen Sie über "Neues Buch anlegen" verschiedene neue Bücher hinzu.
Finden Sie Bücher über "Bücher finden".
Dabei genügen in den ISBN- und Titel-Feldern auch Teil-Strings (z.B. ISBN '123' oder Titel 'meintit'),
so dass mit einer Anfrage mehrere passende Bücher gefunden werden können.
Beenden Sie Tomcat:
cd /D \Java\Tomcat\bin
shutdown.bat
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\client-Verzeichnis die RESTful-Webservice-Client-Klasse: BuecherRestClient.java
package de.meinefirma.meinprojekt.client;
import javax.ws.rs.core.MultivaluedMap;
import com.sun.jersey.api.client.*;
import com.sun.jersey.core.util.MultivaluedMapImpl;
/** RESTful-Webservice-Client */
public class BuecherRestClient
{
public static void main( String[] args )
{
Client client = Client.create();
WebResource webResource = client.resource( "http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher" );
if( args.length > 1 && args[0].toLowerCase().equals( "getbuchbyid" ) ) {
System.out.println( "\nBuch mit ID " + args[1] + ":" );
System.out.println( getBuchById( webResource, args[1] ) );
}
else if( args.length > 2 && args[0].toLowerCase().equals( "findebuecher" ) ) {
System.out.println( "\nFinde Buecher mit ISBN " + args[1] + " oder Titel '" + args[2] + "':" );
System.out.println( findeBuecher( webResource, null, args[1], args[2] ) );
}
else if( args.length > 4 && args[0].toLowerCase().equals( "putbuch" ) ) {
System.out.println( "\nAendere Daten zum Buch mit der ID " + args[1] + ":" );
System.out.println( putBuch( webResource, args[1], "isbn=" + args[2] + "&titel=" + args[3] + "&preis=" + args[4] ) );
}
else if( args.length > 3 && args[0].toLowerCase().equals( "postbuch" ) ) {
System.out.println( "\nNeues Buch anlegen:" );
ClientResponse response = postBuch( webResource, args[1], args[2], new Double( args[3] ) );
System.out.println( response.getStatus() + " " + response.getClientResponseStatus() );
System.out.println( response.getEntity( String.class ) );
}
else if( args.length > 1 && args[0].toLowerCase().equals( "deletebuch" ) ) {
System.out.println( "\nLoesche Buch mit ID " + args[1] + ":" );
System.out.println( deleteBuch( webResource, args[1] ) );
}
}
static String getBuchById( WebResource webResource, String id )
{
return webResource.path( id ).get( String.class );
}
static String findeBuecher( WebResource webResource, String id, String isbn, String titel )
{
MultivaluedMap<String,String> queryParams = new MultivaluedMapImpl();
if( id != null ) queryParams.add( "id", id );
if( isbn != null ) queryParams.add( "isbn", isbn );
if( titel != null ) queryParams.add( "titel", titel );
return webResource.queryParams( queryParams ).get( String.class );
}
static ClientResponse putBuch( WebResource webResource, String id, String data )
{
return webResource.path( id ).put( ClientResponse.class, data );
}
static ClientResponse postBuch( WebResource webResource, String isbn, String titel, Double preis )
{
MultivaluedMap<String,String> formData = new MultivaluedMapImpl();
formData.add( "isbn", isbn );
formData.add( "titel", titel );
formData.add( "preis", "" + preis );
return webResource.type( "application/x-www-form-urlencoded" ).post( ClientResponse.class, formData );
}
static ClientResponse deleteBuch( WebResource webResource, String id )
{
return webResource.path( id ).delete( ClientResponse.class );
}
}
Speichern Sie folgende Kommandos in einer Batchdatei, passen Sie darin die Pfade an und führen Sie die Batchdatei aus:
cls set TOMCAT_HOME=\Java\Tomcat set _MEIN_PROJEKT_NAME=JaxRsBuecherverwaltung del %TOMCAT_HOME%\webapps\%_MEIN_PROJEKT_NAME%.war rd %TOMCAT_HOME%\webapps\%_MEIN_PROJEKT_NAME% /S /Q pushd . cd /D %TOMCAT_HOME%\bin call startup.bat @echo on popd call mvn clean package @echo on copy target\%_MEIN_PROJEKT_NAME%.war %TOMCAT_HOME%\webapps call mvn assembly:single @echo on @ping -n 12 127.0.0.1 >nul @echo. @echo ................................................................. java -jar target\%_MEIN_PROJEKT_NAME%-jar-with-dependencies.jar postBuch 1234567891 "MeinTitel1" 12.34 java -jar target\%_MEIN_PROJEKT_NAME%-jar-with-dependencies.jar postBuch 1234567892 "MeinTitel2" 22.34 start http://localhost:8080/%_MEIN_PROJEKT_NAME%/rs/Artikel/Buecher @ping -n 2 127.0.0.1 >nul java -jar target\%_MEIN_PROJEKT_NAME%-jar-with-dependencies.jar getBuchById 4711 java -jar target\%_MEIN_PROJEKT_NAME%-jar-with-dependencies.jar findeBuecher 1234567891 "MeinTitel2" java -jar target\%_MEIN_PROJEKT_NAME%-jar-with-dependencies.jar putBuch 4712 1234567898 "Client-PUT-Titel" 111 java -jar target\%_MEIN_PROJEKT_NAME%-jar-with-dependencies.jar postBuch 9876543210 "Client-POST-Titel" 222 java -jar target\%_MEIN_PROJEKT_NAME%-jar-with-dependencies.jar deleteBuch 4711 @echo. @echo ................................................................. start http://localhost:8080/%_MEIN_PROJEKT_NAME%/rs/Artikel/Buecher @ping -n 4 127.0.0.1 >nul pushd . cd /D %TOMCAT_HOME%\bin call shutdown.bat popd
Sehen Sie sich die Ausgaben von BuecherRestClient auf der Konsole an, und vergleichen Sie die beiden Webseiten (vorher bzw. nachher). Die ping-Kommandos dienen nur dazu, um kurze Pausen einzulegen.
Das Programmierbeispiel ist etwas unsystematisch und inkonsistent: Die Übergabeparameter sind mal einzeln und mal als zusammengefügter String, der Rückgabewert ist mal der XML-String und mal das ClientResponse-Objekt, mal wird der Status ausgegeben und mal nicht etc. Der einzige Grund hierfür ist: Es sollen verschiedene Möglichkeiten demonstriert werden.
Die REST-konforme Verwendungen von GET, PUT, POST und DELETE ist oben unter GET, PUT, POST und DELETE erläutert.
Installieren Sie cURL wie oben beschrieben.
Lassen Sie sich nach jeder Änderung die Liste aller Bücher anzeigen über:
http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher.
Starten Sie Tomcat:
cd /D \Java\Tomcat\bin
startup.bat
POST: Fügen Sie zwei neue Bücher hinzu:
curl --request POST -d "isbn=1234567891&titel=MeinTitel1&preis=12.34" "http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher"
curl --request POST -d "isbn=1234567892&titel=MeinTitel2&preis=22.34" "http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher"
Die neu angelegten Bücher werden mit den neu erzeugten Buch-IDs returniert:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><buecherTO> <message>Buch hinzugefuegt</message> <results><id>4711</id><isbn>1234567891</isbn><preis>12.34</preis><titel>MeinTitel1</titel></results> <returncode>0</returncode></buecherTO>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><buecherTO> <message>Buch hinzugefuegt</message> <results><id>4712</id><isbn>1234567892</isbn><preis>22.34</preis><titel>MeinTitel2</titel></results> <returncode>0</returncode></buecherTO>
GET per ID: Laden Sie das Buch mit der ID 4711:
curl -i "http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher/4711"
Sie erhalten:
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: text/xml Content-Length: 239 <?xml version="1.0" encoding="UTF-8" standalone="yes"?><buecherTO> <message>Buch mit ID 4711</message> <results><id>4711</id><isbn>1234567891</isbn><preis>12.34</preis><titel>MeinTitel1</titel></results> <returncode>0</returncode></buecherTO>
GET mit Suchparametern: Suchen Sie nach Titeln:
curl -i "http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher?titel=MeinTitel"
Sie erhalten:
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: text/xml Content-Length: 360 <?xml version="1.0" encoding="UTF-8" standalone="yes"?><buecherTO> <message>2 Ergebnis(se) fuer Titel = MeinTitel</message> <results><id>4711</id><isbn>1234567891</isbn><preis>12.34</preis><titel>MeinTitel1</titel></results> <results><id>4712</id><isbn>1234567892</isbn><preis>22.34</preis><titel>MeinTitel2</titel></results> <returncode>0</returncode></buecherTO>
PUT: Ändern Sie die Informationen eines vorhandenen Buches:
curl --request PUT -d "isbn=1234567899&titel=PUT-Titel&preis=111" "http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher/4712"
Das geänderte Buch wird returniert:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><buecherTO> <message>Buchdaten geaendert</message> <results><id>4712</id><isbn>1234567899</isbn><preis>111.0</preis><titel>PUT-Titel</titel></results> <returncode>0</returncode></buecherTO>
DELETE: Löschen Sie ein vorhandenes Buch:
curl --request DELETE "http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher/4711"
Media Type: Falls Sie bei anderen REST-Diensten den Fehler "415 Unsupported Media Type" erhalten, probieren Sie verschiedene Mediatypen. Falls Sie Daten (z.B. per XML über POST) übergeben, sollte auch "-H Content-type:..." korrekt gesetzt werden. "-H Accept:..." definiert nur den Mediatyp des erwarteten Ergebnisses. Bitte beachten Sie auch, dass bei XML manchmal text/xml und manchmal application/xml erwartet wird (siehe hierzu auch text/xml vs application/xml).
Falls Sie einen POST-Request per Java, aber ohne Jersey, verschicken wollen, sehen Sie sich das Programmierbeispiel PostToUrl an. Zum Beispiel folgendermaßen können Sie damit ein neues Buch anlegen:
java PostToUrl http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher "isbn=9876543213&titel=PostToUrl-Titel&preis=333"
Das Programmierbeispiel ist auch auf GET, PUT und DELETE erweiterbar.
Im Folgenden wird das letzte Beispiel (mit REST-Webservice) um einen SOAP-Webservice (mit JAX-WS) erweitert, um vergleichende Performance-Messungen durchführen zu können.
Legen Sie eine Kopie in einem neuen Verzeicnis an:
cd \MeinWorkspace
xcopy JaxRsBuecherverwaltung JaxRsJaxWsPerformance\ /S
cd JaxRsJaxWsPerformance
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\buecher-Verzeichnis das SOAP-Webservice-Interface: BuecherSoapServiceIntf.java
package de.meinefirma.meinprojekt.buecher;
import javax.jws.*;
/** Dienst-Interface */
@WebService
public interface BuecherSoapServiceIntf
{
BuecherTO createBuch( @WebParam( name = "buch" ) BuchDO buch ) throws Exception;
BuecherTO getBuchById( @WebParam( name = "id" ) Long id );
BuecherTO findeBuecher( @WebParam( name = "buch" ) BuchDO buch );
}
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\buecher-Verzeichnis die SOAP-Webservice-Implementierung: BuecherSoapServiceImpl.java
package de.meinefirma.meinprojekt.buecher;
import javax.jws.WebService;
import de.meinefirma.meinprojekt.dao.*;
/** Dienstimplementierung */
@WebService( endpointInterface="de.meinefirma.meinprojekt.buecher.BuecherSoapServiceIntf" )
public class BuecherSoapServiceImpl implements BuecherSoapServiceIntf
{
private BuchDoDAO dao = BuchDoDAO.getInstance();
@Override public BuecherTO createBuch( BuchDO bu ) throws Exception
{
bu = dao.createBuch( bu );
return BuecherUtil.erzeugeBuecherTO( "Buch hinzugefuegt", bu );
}
@Override public BuecherTO getBuchById( Long id )
{
BuchDO bu = dao.getBuchById( id );
return BuecherUtil.erzeugeBuecherTO( "Buch mit ID " + id, bu );
}
@Override public BuecherTO findeBuecher( BuchDO bu )
{
return BuecherUtil.findeBuecher( bu.getId(), bu.getIsbn(), bu.getTitel() );
}
}
Ersetzen Sie im src\main\webapp\WEB-INF-Verzeichnis den Inhalt der web.xml durch:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
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"
id="WebApp_ID" version="2.5">
<display-name>JaxRsJaxWsPerformance</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>
<display-name>JAX-RS REST Servlet</display-name>
<servlet-name>REST-Servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>JaxWsServlet</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>REST-Servlet</servlet-name>
<url-pattern>/rs/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
Erzeugen Sie im src\main\webapp\WEB-INF-Verzeichnis den JAX-WS-Deskriptor: sun-jaxws.xml
<endpoints version="2.0" xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime">
<endpoint name="BuecherSoapService"
implementation="de.meinefirma.meinprojekt.buecher.BuecherSoapServiceImpl"
url-pattern="/ws/BuecherSoapService" />
</endpoints>
Erzeugen Sie im src\main\java\de\meinefirma\meinprojekt\client-Verzeichnis die für REST und SOAP gemeinsame Performance-Test-Klasse: BuecherRestAndSoapPerfTestClient.java
package de.meinefirma.meinprojekt.client;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.Random;
import javax.xml.namespace.QName;
import javax.xml.ws.*;
import com.sun.jersey.api.client.*;
import de.meinefirma.meinprojekt.buecher.*;
import de.meinefirma.meinprojekt.dao.BuecherUtil;
/** Performance-Testclient sowohl fuer den REST- als auch fuer den SOAP-Webservice */
public class BuecherRestAndSoapPerfTestClient
{
public static void main( final String[] args ) throws Exception
{
int anzahlBuecher = 100;
String urlBasis = "http://localhost:8080/JaxRsBuecherverwaltung";
switch( Math.min( 2, args.length ) ) {
case 2: urlBasis = args[1]; // $FALL-THROUGH$
case 1: anzahlBuecher = Integer.parseInt( args[0] );
}
test( new SoapSut(), "SOAP", urlBasis + "/ws/BuecherSoapService", anzahlBuecher, true );
test( new RestSut(), "REST", urlBasis + "/rs/Artikel/Buecher", anzahlBuecher, true );
}
public static BuecherTO test( SutIntf sut, String testName, String url, int anzahlBuecher, boolean trace ) throws Exception
{
System.out.println( "\n" + testName + ": " + url + "\n" );
Long[] ids = new Long[anzahlBuecher];
sut.initialize( url );
System.gc();
// Anlage von Buechern:
if( trace ) System.out.println( "\n" + testName + ": Starte Anlage von " + anzahlBuecher + " Buechern" );
long startZeit = System.nanoTime();
for( int i = 0; i < anzahlBuecher; i++ ) {
BuchDO bu = BuecherUtil.erzeugeBuchDO( null, "" + (1000000000L + i), "Buch " + i, new Double( i ) );
BuecherTO bueTO = sut.createBuch( bu );
ids[i] = bueTO.getResults().get( 0 ).getId();
}
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 id = ids[(new Random()).nextInt( anzahlBuecher )];
BuecherTO bueTO = sut.getBuchById( id );
if( bueTO == null || bueTO.getResults() == null || bueTO.getResults().size() != 1 ) {
throw new RuntimeException( "Fehler beim Auslesen des Buches mit der ID " + id );
}
}
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 bueTO = sut.findeBuecher( new BuchDO() );
String s3 = "\nGemeinsames Auslesen von " + bueTO.getResults().size() + " Buechern dauert: " + ermittleDauer( startZeit );
// Ausgabe:
System.out.println( "\n" + testName + ": " + s1 + s2 + s3 + "\n" );
return bueTO;
}
static String zeigeErgebnis( BuecherTO bueTO )
{
StringBuffer sb = new StringBuffer();
sb.append( "\n" + bueTO.getMessage() + "\n" );
for( BuchDO bu : bueTO.getResults() )
sb.append( " Buch (ID=" + bu.getId() + ", ISBN=" + bu.getIsbn() + ", Titel=" + bu.getTitel() + ", Preis=" + bu.getPreis() + ")\n" );
sb.append( " Returncode " + bueTO.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";
}
}
class RestSut implements SutIntf
{
WebResource webResource;
@Override public void initialize( String url ) throws Exception
{
webResource = Client.create().resource( url );
webResource.get( BuecherTO.class );
Thread.sleep( 1000 );
}
@Override public BuecherTO createBuch( BuchDO bu ) throws Exception
{
return webResource.type( "text/xml" ).accept( "text/xml" ).post( BuecherTO.class, bu );
}
@Override public BuecherTO getBuchById( Long id )
{
return webResource.path( "" + id ).get( BuecherTO.class );
}
@Override public BuecherTO findeBuecher( BuchDO bu )
{
return webResource.get( BuecherTO.class );
}
}
class SoapSut implements SutIntf
{
BuecherSoapServiceIntf buecherService;
@Override public void initialize( String url ) throws Exception
{
// Zugriff auf den Webservice vorbereiten:
if( url.startsWith( "direkt" ) ) {
buecherService = new BuecherSoapServiceImpl();
} else {
Service service = null;
int timeoutSekunden = 20;
while( service == null ) {
try {
service = Service.create(
new URL( url + "?wsdl" ),
new QName( "http://buecher.meinprojekt.meinefirma.de/", "BuecherSoapServiceImplService" ) );
} catch( WebServiceException ex ) {
if( timeoutSekunden-- <= 0 ) throw ex;
try { Thread.sleep( 1000 ); } catch( InterruptedException e ) {}
}
}
buecherService = service.getPort( BuecherSoapServiceIntf.class );
}
}
@Override public BuecherTO createBuch( BuchDO bu ) throws Exception
{
return buecherService.createBuch( bu );
}
@Override public BuecherTO getBuchById( Long id )
{
return buecherService.getBuchById( id );
}
@Override public BuecherTO findeBuecher( BuchDO bu )
{
return buecherService.findeBuecher( bu );
}
}
// "System under Test":
interface SutIntf
{
void initialize( String url ) throws Exception;
BuecherTO createBuch( BuchDO bu ) throws Exception;
BuecherTO getBuchById( Long id );
BuecherTO findeBuecher( BuchDO bu );
}
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JaxRsJaxWsPerformance] |- [src] | '- [main] | |- [java] | | '- [de] | | '- [meinefirma] | | '- [meinprojekt] | | |- [buecher] | | | |- BuchDO.java | | | |- BuecherSoapServiceImpl.java | | | |- BuecherSoapServiceIntf.java | | | '- BuecherTO.java | | |- [client] | | | |- BuecherRestAndSoapPerfTestClient.java | | | '- BuecherRestClient.java | | |- [dao] | | | |- BuchDoDAO.java | | | '- BuecherUtil.java | | '- [rest] | | '- BuecherRestService.java | '- [webapp] | |- [WEB-INF] | | |- [lib] | | | '- ... (eventuell .jar-Libs aus JAXWS2.2-*.zip) | | |- sun-jaxws.xml | | '- web.xml | '- index.html |- pom.xml |- run-Client.bat |- run-Curl.bat |- run-RestSoapPerf.bat '- run-Web.bat
Wenn Sie wie gezeigt die SOAP-Service-Klassen im buecher-Package speichern, können Sie das Beispiel leicht für generierte Klassen erweitern ("Contract-First"):
Falls Sie eine Schema-XSD-Datei haben, können Sie BuchDO.java und BuecherTO.java mit xjc generieren.
Falls Sie eine WSDL-Datei haben, können Sie BuchDO.java, BuecherTO.java und BuecherSoapServiceIntf.java mit wsimport generieren.
Die Batchdateien run-*.bat sind im Source-Download enthalten.
Starten Sie Tomcat, kopieren Sie die WAR-Datei in das Tomcat-webapps-Verzeichnis und starten Sie den Test (siehe auch run-RestSoapPerf.bat):
mvn package
copy target\JaxRsBuecherverwaltung.war \Java\Tomcat\webapps
mvn assembly:single
java -cp target/JaxRsBuecherverwaltung-jar-with-dependencies.jar de.meinefirma.meinprojekt.client.BuecherRestAndSoapPerfTestClient 1000 http://localhost:8080/JaxRsBuecherverwaltung
Falls Sie eine Exception erhalten (z.B. javax.xml.ws.WebServiceException: Failed to access ...)
suchen Sie in der \Java\Tomcat\logs\localhost.*.log-Datei die ursprüngliche Exception.
Falls Sie folgende Exception finden:
java.lang.ClassNotFoundException: com.sun.xml.ws.transport.http.servlet.WSServletContextListener:
Leider gibt es zwischen bestimmten Versionen von Java SE 6, JAX-WS 2 und Tomcat 6
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
https://jax-ws.dev.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.
Je nach Geschwindigkeit Ihres PCs bzw. Ihres Netzwerks erhalten Sie sehr unterschiedliche Ergebnisse. Aber die Unterschiede zwischen REST und SOAP sind gering, weshalb die folgende Tabelle nur verschiedene Anbindungen zeigt:
| Anbindung | Anlage von 1000 Büchern | Einzelnes Auslesen von 1000 Büchern | Gemeinsames Auslesen von 1000 Büchern |
|---|---|---|---|
| direkt (ohne REST/SOAP) | 30 ms | 5 ms | 0,1 ms |
| REST/SOAP auf lokalem PC ("localhost") | 0,9 s | 0,5 s | 10 ms |
| REST/SOAP über Netzwerk | 2 s | 1,2 s | 30 ms |
Falls Sie Mozilla Firefox als Webbrowser verwenden, können Sie Firefox um das RESTClient-Plug-in erweitern. Klicken Sie dazu unter https://addons.mozilla.org/en-US/firefox/addon/9780/ auf die Schaltfläche "Zu Firefox hinzufügen" bzw. "Add to Firefox".
Starten Sie Tomcat, wählen Sie in Firefox "Extras" | "REST Client", tragen Sie unter REST Request "http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher" und bei Request Body "isbn=9876543219&titel=RESTClient-Titel&preis=777" ein, wählen Sie als Method "POST" und klicken Sie auf den "Send"-Button.
Sie erhalten in etwa Folgendes:

Falls Sie Mozilla Firefox als Webbrowser verwenden, können Sie Firefox um das Poster-Plug-in erweitern. Klicken Sie dazu unter https://addons.mozilla.org/de/firefox/addon/2691 auf die Schaltfläche "Zu Firefox hinzufügen" bzw. "Add to Firefox". Nach der Installation und dem Neustart sehen Sie unten rechts ein gelb hinterlegtes P.
Starten Sie Tomcat, klicken Sie auf das P, tragen Sie bei URL "http://localhost:8080/JaxRsBuecherverwaltung/rs/Artikel/Buecher", bei Content Type "text/plain" und bei Content to Send "isbn=9876543217&titel=Poster-Titel&preis=777" ein, wählen Sie bei Actions "POST" und klicken Sie auf das "GO" neben "POST".
Sie erhalten in etwa Folgendes:

Falls Sie Mozilla Firefox als Webbrowser verwenden, können Sie Firefox um das Firebug-Plug-in erweitern. Klicken Sie dazu unter https://addons.mozilla.org/de/firefox/addon/1843 auf die Schaltfläche "Zu Firefox hinzufügen" bzw. "Add to Firefox". Nach der Installation und dem Neustart sehen Sie unten rechts einen kleinen Käfer.
Starten Sie Tomcat, öffnen Sie http://localhost:8080/JaxRsBuecherverwaltung, klicken Sie auf den Firebug-Käfer, aktivieren Sie unter 'Netzwerk' die 'Netzwerk'-Checkbox, und klicken Sie auf der Bücherverwaltungs-Webseite auf den Button 'Neues Buch anlegen'.
Klicken Sie auf die drei Tabulatorreiter 'Header', 'Post' und 'Antwort', um in etwa Folgendes angezeigt zu bekommen:
