JAXenter

Das Portal für Java, Architektur, Cloud & Agile

RESTful Web Services mit JAX-RS

Javas alternatives Web Services API macht fast restlos glücklich

RESTful Web Services mit JAX-RS

Bernhard Löwenstein

Seit einiger Zeit steht mit RESTful Web Services eine zweite Technologie zur Realisierung von Webdiensten zur Verfügung. Genauso wie ihre SOAP-Pendants lassen sich diese Java-Komponenten auf Basis annotierter POJOs implementieren. Wie das konkret funktioniert, zeigt dieser Artikel.

Enterprise Java Web Services

Die Serie "Enterprise Java Web Services" von Bernhard Löwenstein ist erstmals im Java Magazin erschienen und umfasst vier Teile:

  • Teil 1: SOAP Web Services mit JAX-WS
  • Teil 2: RESTful Web Services mit JAX-RS
  • Teil 3: Frameworks zur Entwicklung von SOAP Web Services
  • Teil 4: Frameworks zur Entwicklung von RESTful Web Services

Die Quellcodes zu Teil 2 finden Sie im Java-Magazin-Archiv zum Download.

Im ersten Teil dieser Serie ging es um die Entwicklung von SOAP Web Services mit JAX-WS (Java API for XML-based Web Services). Dieses Mal beschäftigen wir uns mit den speziell in der Web-2.0-Gemeinde sehr beliebten RESTful Web Services. Nach einer theoretischen Einführung in die Materie werden wir uns anhand eines Beispiels ansehen, wie der Entwickler mit JAX-RS (Java API for RESTful Web Services) solche Services erstellen kann.

RESTful Web Services

Der große Overhead bei der Abwicklung eines Methodenaufrufs führte dazu, dass neben den SOAP Web Services eine zweite, leichtgewichtigere Variante von Web Services aufkam: RESTful Web Services. Bei diesen stehen nun nicht mehr Dienste, sondern Ressourcen im Mittelpunkt des Geschehens. Jeder Service kapselt eine bestimmte Ressourcenklasse und bietet dem Nutzer über seine HTTP-Schnittstelle die Möglichkeit, den Zustand einzelner oder mehrerer Ressourcen abzufragen oder zu manipulieren. RESTful Web Services basieren auf dem von Roy Fielding geprägten Architekturstil REST (Representational State Transfer) [1], als dessen bedeutendster Vertreter sich das WWW nennen lässt. Das Web wird als eine Sammlung von Ressourcen angesehen, die untereinander verknüpft sein können. Über einen URI lässt sich jede Ressource eindeutig adressieren. Zum Transfer der Daten wird das zustandslose HTTP(S) verwendet. Die Operation, die serverseitig auf der adressierten Ressource ausgeführt wird, ist mitunter von der gewählten HTTP-Methode abhängig. Über den zurückgelieferten HTTP-Statuscode informiert der Web Service seinen Aufrufer über den Erfolg der Aktion. Gemäß Konvention kommt den HTTP-Methoden folgende Bedeutung zu:

  • GET liefert eine oder mehrere Ressourcen in einer bestimmten Repräsentation zurück
  • POST erzeugt eine Ressource
  • PUT aktualisiert eine bereits vorhandene Ressource
  • DELETE löscht eine Ressource

Jede Ressource kann verschiedene Repräsentationen aufweisen. In Verbindung mit RESTful Web Services trifft man die Formate JSON (JavaScript Object Notation) und XML häufig an. Der Nutzer kann über die Attribute Content-Type und Accept im HTTP-Header Angaben bezüglich des für die Anfrage gewählten bzw. für die Antwort akzeptierten Datenformats machen. Die Beschreibung der Web-Service-Schnittstelle erfolgt in Form der WADL (Web Application Description Language).

JAX-RS

JAX-RS [2] ermöglicht dem Java-Entwickler die Realisierung von RESTful Web Services mittels annotierter POJOs. Sie ist Teil der Java-Plattform EE 6, kann jedoch auch problemlos mit der Standardedition genutzt werden. Lediglich die fehlenden Bibliotheken müssen dann dem Klassenpfad hinzugefügt werden. Für die aktuell in der Version 1.1 vorliegende Spezifikation ist der JSR 311 verantwortlich. Jersey [3] setzt diese als Referenzimplementierung um. Die wichtigsten API-Klassen findet der Programmierer im Package javax.ws.rs.* vor.

Begleitendes Beispiel: Distributed Books

Den meisten Lesern ist wahrscheinlich das Problem bekannt, dass sich der Bücherbestand einer Softwarefirma schon recht bald auf unzählige Büros verteilt. Zur Erfassung aller Bücher und ihrer Aufbewahrungsorte wollen wir deshalb einen RESTful Web Service entwickeln. Auf das Frontend werden wir nur am Rande eingehen. Der nächste Ferienpraktikant soll schließlich auch noch etwas zu tun haben. Der Bücherdienst soll die typischen CRUD-Operationen [4] bereitstellen, da diese hinsichtlich des Formats mit XML verarbeitet werden können. Zusätzlich soll der Benutzer alle Bücher im JSON-Format abrufen sowie über einen Suchbegriff nach bestimmten Werken suchen können. In Verbindung mit den Suchergebnissen kann er zwischen den Formaten JSON und XML wählen.

Implementierung eines Web Service

Bei der Umsetzung eines RESTful Web Service reicht grundsätzlich eine einfache Klasse aus, trotzdem empfiehlt sich die Verwendung eines Interface, um dort alle Annotationen hineinzupacken. Der Entwickler hat jedenfalls alle Möglichkeiten: Er kann die Annotationen im Interface (sofern er eines verwendet) in der Klasse oder auf beide verteilt notieren. Die JAX-RS-Runtime ist dafür verantwortlich, zu einem eingehenden HTTP-Request die zugehörige Web-Service-Methode ausfindig zu machen, diese auszuführen und an den Aufrufer das Ergebnis im gewünschten Format zurückzuliefern. Als wesentliche Elemente eines solchen Requests lassen sich folgende ausmachen:

  • Request-URI (z. B. http://localhost:8080/resources/books)
  • HTTP-Methode (z. B. POST)
  • Medientyp der übermittelten Anfragedaten (z. B. application/xml)
  • Anfragedaten (z. B. <book title="Mule in Action" ... />)
  • akzeptierte Medientypen für die zu übermittelnden Antwortdaten (z. B. application/json)

Die Aufgabe des Programmierers besteht darin, die Methoden seines RESTful Web Service mit den von JAX-RS bereitgestellten Annotationen auszuzeichnen. Beim Eingehen eines Requests kann die Runtime durch einen Vergleich der Request-Teile mit den Annotationswerten erkennen, welche Web-Service-Methode aufgerufen werden soll.

Welche Annotationen stehen nun zur Verfügung? @Path, auf das Interface (oder die Klasse) angewendet, verwandelt das POJO in einen RESTful Web Service. Hierüber erfolgt außerdem die Angabe des URI-Pfades, wobei dieser relativ zum Kontext des Web Service Deployments interpretiert wird. Die Annotation @Path lässt sich auch direkt auf Methoden anwenden. Die Interpretation des Pfades erfolgt dabei relativ zur Pfadangabe des Interface (oder der Klasse). Zu jeder Methode wird auf diese Weise ein Pfad(muster) festgelegt.

Mithilfe der Annotationen @GET, @POST, @PUT und @DELETE kann man die Web-Service-Methoden in Relation zu den HTTP-Methoden setzen.

Die Medientypen, die der Web Service oder eine seiner Methoden verarbeiten kann, lassen sich über @Consumes festlegen. @Produces stellt das Gegenstück dar und dient zur Angabe der Formate, in denen der Web Service oder eine seiner Methoden die Ergebnisdaten zurückliefern kann.

Sehen wir uns den Sachverhalt nun anhand unseres Beispiels an. Wir benötigen einerseits das Interface BookResource (Listing 1), in das alle Annotationen kommen, und andererseits die Klasse BookResourceImpl, die dieses Interface implementiert.

Listing 1
...
@Path("/books")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public interface BookResource {
  @POST
  public Response create(@Context UriInfo uriInfo, Book book);
  
  @GET
  @Path("/{bookid}")
  public Book read(@PathParam("bookid") long bookID);
  
  @PUT
  @Path("/{bookid}")
  public void update(@PathParam("bookid") long bookID, Book book);

  @DELETE
  @Path("/{bookid}")
  public void delete(@PathParam("bookid") long bookID) throws 
                                                BookNotFoundException;

  @GET
  @Path("/all")
  @Produces(MediaType.APPLICATION_JSON)
  public String getAll(@QueryParam("maxnumber") @DefaultValue("50")
                                                         int maxNumber);
  
  @GET
  @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
  @Path("/search")
  public Collection search(@QueryParam("query") String query);
}

Durch @Path-Annotationen legt der Entwickler für jede Methode ihren zugehörigen URI fest. Annotiert er sie nicht derartig, wird automatisch der Pfad des Interface (oder der Klasse) zugeteilt. Die Methode create()wird z. B. unter .../books gemappt. Annotiert er sie mit @Path, wird dieser Wert relativ zum Pfad des Interface (oder der Klasse) interpretiert. Die Methode getAll()wird demnach z. B. unter .../books/all zur Verfügung gestellt. In jeder Pfadangabe können außerdem beliebig viele Platzhalter, so genannte Pfadparameter, angeführt werden. Diese erkennt man daran, dass sie innerhalb spitzer Klammern stehen. Via @PathParam lassen sich solche Parameter als Argumente beim Methodenaufruf injizieren. So führt z. B. der GET-Request .../books/123 intern zur Ausführung von read(123) .

Auch die im Rahmen der Request-URI übergebenen Query-Parameter lassen sich mittels @QueryParam als Methodenargumente injizieren. @DefaultValue erlaubt die Angabe eines Standardwerts für den Fall, dass der entsprechende Parameter in der Query fehlt. Der GET-Request .../books/all?maxnumber=10 würde intern z. B. den Aufruf von getAll(10) bewirken und .../books/all zur Ausführung von getAll(50) führen, weil eben 50 als Standardwert definiert wurde.

Mit @CookieParam, @FormParam, @HeaderParam und @MatrixParam stehen weitere Annotationen zum Injizieren von Methodenargumenten zur Verfügung. Darüber hinaus kann man sich mit @Context verschiedene Kontextvariablen übergeben lassen, die innerhalb der Methode wichtige Informationen bzw. Funktionalitäten bereitstellen [5]. Wir nutzen diese Möglichkeit z. B. bei create(), um eine Kontextvariable vom Typ UriInfo zu injizieren.

In Verbindung mit @Consumes und @Produces gilt: Werden solche am Interface (oder an der Klasse) notiert, gelten sie für alle Methoden, die diesbezüglich keine eigene Regelung treffen. So können z. B. all unsere CRUD-Methoden XML konsumieren und produzieren, während getAll()die Daten ausschließlich in JSON und search(), die Suchergebnisse aber in beiden Formaten zurückliefern kann.

 

Kommentare

Ihr Kommentar zum Thema

Als Gast kommentieren:

Gastkommentare werden nach redaktioneller Prüfung freigegeben (bitte Policy beachten).