Datei Upload mit Spring 3

Eingetragen bei: Java, spring | 0

Mein letzter Artikel beschäftigte sich mit der Erstellung eines Spring Web MVC Projektes ( http://martinzimmermann1979.wordpress.com/2012/05/23/in-10-minuten-ein-spring-web-mvc-projekt-erstellen/ ). Hier stand ein Controller zur Behandlung von GET- und POST-Requests zur Verfügung. Ersterer lieferte eine Benutzerliste, die in einer entsprechenden View angezeigt wurde. Zweiterer war für das Hinzufügen von weiteren Benutzern zuständig. Die bereits bestehenden Funktionalitäten sollen nun um einen Datei-Upload erweitert werden.

Zusätzlich zu den bereits verwendeten Spring-Libraries, wird folgende Maven dependency benötigt:

<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>

In einem neu zu erstellenden FileController wird die Methode handleUpload benötigt. Da die Datei per POST-Request hochgeladen wird, erhält das Attibute method des RequestMappings den entsprechenden Wert. In der Methoden-Signatur wird diesmal kein
ModelAttribute verwendet, da kein Mapping zwischen Model und Formular-Daten stattfinden muss. Die Binär-Daten sind direkt im Request enthalten und per RequestParam zugreifbar.
Die Datei wird vom Client als Multipart-Request zum Server gesendet, welche in Spring von der Klasse MultipartFile repräsentiert wird. Über die getInputStream-Methode erhält man Zugriff auf den InputStream, der nun verarbeitet werden kann.

@RequestMapping(value = „/file“, method = RequestMethod.POST)
public String handleUpload(@RequestParam(„file“) MultipartFile file) {
if (file.isEmpty()) {
return „redirect:/uploadFailure“;
}
try {
InputStream inputStream = file.getInputStream();
// do with the InputStream, what you want to do
} catch (IOException e) {
return „redirect:/uploadFailure“;
}
return „redirect:/uploadSuccess“;
}

Die obigen Redirects auf die Ressourcen uploadSuccess und uploadFailure führen natürlich noch ins Leere. Wie diese Umzusetzen sind, kann meinem letzten Artikel entnommen werden.

Um per HTLM-Formular eine Datei hochzuladen muss das form-Attribut enctype auf multipart/form-data gesetzt werden. Außerdem wird über die action auf die handleUpload-Ressource verwiesen. Da der RequestParam MultipartFile im RequestParam mit file benannt wurde, muss auch das input-Tag diesen Namen erhalten. Das Attribut type wird ebenfalls auf file gesetzt, damit sich im Browser der Dateiauswahl-Dialog öffnet.

<form action=“file“ method=“post“ enctype=“multipart/form-data“>
<p>Datei:<br>
<input name=“file“ type=“file“>
<input type=“submit“ value=“Absenden“>
</p>
</form>

Die Spring Konfiguration, die für das Hochladen von Dateien notwendig ist, ist recht übersichtlich. Es muss lediglich ein CommonsMultipartResolver definiert werden, damit Spring mit dem MultipartFile umgehen kann:

<bean id=“multipartResolver“
class=“org.springframework.web.multipart.commons.CommonsMultipartResolver“ />

Anschließend können Dateien über das oben definierte Formular auf den Server hochgeladen und dort verarbeitet werden.

In 10 Minuten ein Spring Web MVC Projekt erstellen

Eingetragen bei: Java, spring | 0

Bereits einen vorangegangenen Artikel widmete ich dem Thema REST Services mit Spring 3 (http://martinzimmermann1979.wordpress.com/2012/02/29/rest-services-mit-spring-3) und auch der folgende Beitrag beschäftigt sich mit Spring Web MVC. Diesmal wird allerdings eine Web-Seite realisiert und als Views JSPs (Java Server Pages) verwendet.

Innerhalb von maximal 10 Minuten soll ein vollständiges Spring Web MVC Projekt erstellt werden, mit dem sich eine Benutzerliste darstellen und über ein Formular weitere Benutzer hinzufügen lassen. Zum Einsatz kommt die aktuelle Spring Version 3.1.0. Da ich als Entwicklungsumgebung Eclipse verwende, werden zusätzlich ein paar Eclipse spezifische Details erläutert, grundsätzlich kann das Beispiel aber aus jeder IDE heraus nachvollzogen werden.

Zunächst wird in Eclipse ein Maven Projekt erstellt. Sofern das Eclipse WTP-Plugin (Web Tool Platform – http://www.eclipse.org/webtools/) installiert ist, kann man anschließend über die Projekt Einstellungen, die Project Facets konfigurieren. Hier wird nun das Häkchen bei Dynamic Web Module gesetzt.

Natürlich kann man auch andersherum vorgehen und zuerst ein Web Projekt erstellen und es anschließend zu einem Maven Projekt konvertieren.

Nun werden die drei Spring Libraries Core, Spring-Web und Spring-WebMVC als Maven Dependencys in die pom.xml eingetragen:

<properties>
<spring.version>3.1.0.RELEASE</spring.version>
</properties>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

Um mit Maven ein Web-Archive (WAR-Datei) erstellen zu können, muss das maven-war-plugin in die Maven-Konfiguration eingetragen und das Packaging von jar auf war umgestellt werden:

<packaging>war</packaging>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
</plugin>
</plugins>
</build>

Sobald das Maven-Packaging geändert wird, erscheint in der Problem View von Eclipse folgender Fehler:

Project configuration is not up-to-date with pom.xml. Run Maven->Update Project Configuration or use Quick Fix.

Wie in der Meldung beschrieben, muss lediglich die Projekt-Konfiguration aktualisiert werden, um das Problem zu beheben.

Wer bereits mit WTP gearbeitet hat, ist gewohnt, dass das Web-Verzeichnis unter WebContent zu finden ist. Beim Aktivieren des Facets Dynamic Web Module wurde dieses Verzeichnis irreführender weise angelegt. Da unser Beispiel-Projekt allerdings mit Maven gebaut wird, ist dessen Standardverhalten entscheidend. Daher können die Verzeichnisse WebContent/WEB-INF getrost gelöscht und stattdessen src/main/webapp/WEB-INF angelegt werden. Hier wird vom maven-war-plugin die web.xml Datei erwartet – ist diese nicht vorhanden bricht der Build-Prozess mit einer Fehlermeldung ab.

In der web.xml Datei wird nun das DispatcherServlet definiert, welches alle eingehenden Requests annimmt und an die verantwortlichen Controller delegiert. Das DispatcherServlet ist Bestandteil von Springs IoC-Container und ermöglicht die Nutzung aller von Spring zur Verfügung gestellten Features:

<web-app>
<servlet>
<servlet-name>myWebApp</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>myWebApp</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

Der Spring-Dispatcher scannt die unter Spring-Context stehenden Klassen nach Controller Annotationen und erkennt für enthaltene Methoden, ob ein RequestMapping angegeben wurde. Unter dem hier angegebenen Pfad ist die Ressource dann später erreichbar, da das DispatcherServlet entsprechende Requests an die Controller-Methoden delegiert.

Wenden wir uns nun dem UserController zu, der im Package de.mz.server erstellt und mit @Controller annotiert wird. Für die Darstellung der Benutzerliste ist die Methode getUsers verantwortlich, die ein RequestMapping unter dem Pfad users erhält. Da in der web.xml als url-pattern der Root-Pfad angegeben wurde, ist die Ressource unter http://<ip-addresse>:<port>/de.mz.server/users erreichbar. Als Rückgabewert dient der Methode getUsers ein ModelAndView-Objekt. Wie der Name schon sagt, beinhaltet das Objekt die anzuzeigende View und als Model die Benutzerliste:

@Controller
public class UserController {

@RequestMapping(„/users“)
public ModelAndView getUsers() {
List<User> users = loadUsers();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName(„users“);
modelAndView.addObject(„users“, users);
return modelAndView;
}

// add loadUsers-method
}

Widmen wir uns nun der Spring-Konfiguration, die im WEB-INF-Verzeichnis platziert wird. Der Name der Datei beginnt mit dem in der web.xml definierten Servlet-Namen ergänzt um „-servlet.xml“ – also myWebApp-servlet.xml. Damit der UserController von Spring generiert wird, wird das Component-Scanning für das Package de.mz.server aktiviert:

<context:component-scan base-package=“de.mz.server“/>

Im Controller wurde dem ModelAndView-Objekt der View Name users gesetzt. Um den Namen auf eine View zu mappen, muss ein ViewResolver definiert werden. Abhängig von der View stellt Spring hier verschiedene Strategien zur Verfügung. In diesem Beispiel wird der UrlBasedViewResolver verwendet, der ohne explizites Mapping auskommt, da die Namen der Views automatisch auf die View-Ressourcen gemappt werden:

<bean id=“viewResolver“ class=“org.springframework.web.servlet.view.UrlBasedViewResolver“>
<property name=“viewClass“ value=“org.springframework.web.servlet.view.JstlView“/>
<property name=“prefix“ value=“/WEB-INF/jsp/“/>
<property name=“suffix“ value=“.jsp“/>
</bean>

Als viewClass wird die JstlView gewählt, es werden also JSPs unterstützt, welche die JSP Standard Tag Library verwenden können. Per Präfix wird definiert, dass sich die JSP-Seiten in dem Unterverzeichnis jsp befinden und das Suffix gibt an, dass Views die gleichnamige Datei-Endung besitzen. Die im UserController referenzierte View users befindet sich also unter WEB-INF/jsp/users.jsp und hat folgenden Inhalt:

<%@taglib uri=“http://java.sun.com/jsp/jstl/core“ prefix=“c“%>
<html>
<head><title>Users</title></head>
<body>

<h3>Users</h3>
<table>
<tr>
<th>Vorname</th>
<th>Nachname</th>
</tr>

<c:forEach var=“user“ items=“${users}“>
<tr>
<td>${user.forename}</td>
<td>${user.lastname}</td>
</c:forEach>
</table>

</body>
</html>

Ohne die explizite Angabe der Tag-Library in der ersten Zeile, können die c-Tags nicht verwendet werden. Mit Hilfe des forEach-Befehls wird über die Liste der Benutzer iterieren. Dem items-Attribut wird die Liste der Benutzer zugewiesen, indem auf das im ModelAndView-Objekt hinterlegte Model verwiesen wird. Im var-Feld wird eine temporäre Variable definiert, über die während der Schleifendurchläufe auf die Benutzer zugegriffen werden kann. Vor- und Nachnamen der Benutzer können so in separate Spalten der Tabelle geschrieben werden. Natürlich müssen hierfür die Eigenschaften forename und lastname im Model vorhanden und mittels getter-Methoden zugreifbar sein:

public class User {

private String forename;
private String lastname;

// getter and setter

}

Sofern die Web-Applikation in einem Tomcat-Server laufen soll, muss die JSTL-Library als Maven Dependency eingebunden werden, da diese nicht im Tomcat integriert ist:

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

Im Anschluss kann die Web-Applikation mit Maven gebaut werden. Dies geschieht über den Kommandozeilen-Aufruf mvn package, oder aus der Eclipse IDE heraus. Egal welchen Weg man wählt, nach erfolgreichem Build-Prozess befindet sich die frisch erzeugte WAR-Datei im Target-Verzeichnis und kann deployed werden.

Im zweiten Schritt sollen weitere Benutzer über ein Formular hinzufügbar sein. Hierzu wird der UserController um die Methode addUser erweitert. Über das RequestMapping wird der Pfad auf user und POST als Request-Art gesetzt. Als Übergabe-Parameter kann direkt das Model – also die User-Klasse – verwendet werden, da sich Request-Parameter bequem mit Objekt-Eigenschaften verknüpfen lassen. Hierzu reicht es aus, den Übergabe-Parameter mit der Annotation ModelAttribute zu versehen. Der hier vergebene Name userForm muss später auch im HTML-Formular verwendet werden. Nach erfolgreichem Speichern des Benutzers, soll anschließend ein redirect auf die users-Ressource stattfinden, damit die vervollständigte Benutzerliste betrachtet werden kann. Daher ist der Rückgabe-Wert diesmal ein String und kein ModelAndView-Objekt:

@RequestMapping(value=“/user“, method = RequestMethod.POST)
public String addUser(@ModelAttribute(„userForm“) User user) {
// save user
return „redirect:/users“;
}

Widmen wir uns dem HTML-Formular. Die Input-Tags im HTML-Formular müssen die gleichen Namen verwenden, wie die entsprechenden Eigenschaften in der User-Klasse und es müssen die dazugehörigen setter-Methoden existieren. Das Form-Attribut modelAttribute erhält denselben Namen, wie die gleichnamige Annotation im Controller, um das Mapping zwischen Input-Feldern und Eigenschaften der User-Klasse zu ermöglichen. Als action wird der Pfad zur addUser-Methode und als method POST angegeben, was den RequestMapping-Einstellungen des Controllers entspricht:

<form action=“user“ modelAttribute=“userForm“ method=“POST“>
<p>Vorname:<br><input name=“forename“ type=“text“></p>
<p>Nachname:<br><input name=“lastname“ type=“text“></p>
<p><input type=“submit“ value=“Absenden“></p>
</form>

Nach einem Redeployment können wie in der Zielsetzung beschrieben, nun weitere Benutzer hinzugefügt werden.

Innerhalb kürzester Zeit wurde mit Spring 3.x eine Web-Applikation erstellt, die aus Controller und View zur Administrierung von Benutzern besteht. Hierfür wurde das DispatcherServlet in die web.xml-Datei eingetragen und für die Auswahl der richtigen View ein ViewResolver verwendet, der den View-Namen auf eine JSP-Datei mappt. Alle weiteren Einstellungen ließen sich bequem per Annotation direkt im Controller vornehmen.

Android – Kommunikation mit der Außenwelt

Eingetragen bei: Android | 0

Nachdem ich in einem vorangegangenen Artikel die Verwendung einer SQLite Datenbank in einer Android-Anwendung beschrieben habe (siehe http://martinzimmermann1979.wordpress.com/2011/11/17/verwendung-einer-sqlite-datenbank-in-einer-android-app/), möchte ich mich in diesem Artikel der Kommunikation mit einem REST-Service widmen. Hierfür kann die im Android-SDK integrierte Apache HttpClient Library verwendet werden. Als REST-Schnittstelle soll das Beispiel aus meinem letzten Artikel (siehe http://martinzimmermann1979.wordpress.com/2012/02/29/rest-services-mit-spring-3/) verwendet werden. Hier wurde exemplarisch gezeigt wie man auf der REST-Ressource MyBean die Operationen GET, POST, PUT und DELETE ausführt. Neben XML- wurden auch JSON-Inhalte ausgetauscht. Letzteres Datenformat wird im folgenden Beispiel benötigt. Hier nun die Java-Repräsentation der MyBean-Ressource:

public class MyBean {
private String id;
private String message;
// getter and setter for id and message
}

Im ersten Schritt soll die Android-Anwendung per GET-Request alle MyBean-Einträge erfragen und den erhaltenen JSON-Inhalt in MyBean-Objekte transformieren. Anschließend wird die Funktionalität sukzessive um die REST-Operationen POST, PUT und DELETE erweitert, wobei der POST- und PUT-Request entsprechenden JSON-Inhalt mitliefern muss.

Bevor es an die REST-Kommunikation geht, muss die Android-Anwendung die Erlaubnis zum Zugriff aufs Internet und damit folgenden Eintrag im Android-Manifest erhalten:

<uses-permission android:name=“android.permission.INTERNET“ />

Anschließend wird eine Hilfsklasse erstellt, welche die Verbindung zum REST-Service aufbaut, den erhaltenden InputStream in einen String umwandelt und für alle Arten von REST-Operationen genutzt werden kann:

public String connect(HttpRequestBase httpRequest) {
HttpClient httpclient = new DefaultHttpClient();
httpRequest.addHeader(„Accept“, „application/json“);
try {
HttpResponse response = httpclient.execute(httpRequest);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = entity.getContent();
String result = convertStreamToString(inputStream);
inputStream.close();
return result;
}
} catch (Exception e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
return „“;
}

Die übergebene HttpRequestBase abstrahiert die jeweilige Http-Operation, die bereits die URI der Rest-Ressource und gegebenenfalls weitere Inhalte enthält. Die Kommunikation sieht bei allen Operationen gleich aus. Zunächst wird ein HttpClient benötigt – für dieses Beispiel reicht der Standard-HttpClient aus. Da der Http-Response vom Server JSON enthalten soll, wird die Header-Information Accept: application/json hinzugefügt. Sofern der Server außer dem Status – response.getStatusLine() – mit weiteren Daten antwortet, befinden sich diese in der HttpEntity in Form eines InputStreams. Da auf mobilen Endgeräten nur begrenzt Speicher vorhanden ist, können nicht beliebig Libraries verwendet werden. So kommt man nicht Drumherum, den InputStream selbstständig in einen String zu transformieren:

private String convertStreamToString(InputStream inputStream) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder(50);
String line = null;
try {
while ((line = reader.readLine()) != null) {
stringBuilder.append(line + „n“);
}
} catch (IOException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
} finally {
try {
inputStream.close();
} catch (IOException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
}
return stringBuilder.toString();
}

Der InputStreamReader dient dem InputStream als wrapper, der wiederrum von einem BufferedReader gewrappt wird. Letzterer kann zeilenweise ausgelesen und der Inhalt einem StringBuilder hinzugefügt werden. Sofern die Antwort des Servers voraussichtlich mehr als 16 Zeichen beträgt, empfiehlt es sich den StringBuilder mit einem höheren Wert zu initialisieren. Andernfalls muss der interne Buffer des StringBuilders sukzessive erhöht werden.

Nun soll die GET-Operation erstellt und die oben gezeigte connect-Methode aufgerufen werden:

HttpRequestBase httpGet = new HttpGet(REST_RESSOURCE_URI);
String result = connect(httpGet);

Anschließend enthält das result einen JSON-String folgender Art:

{„object“:{„myview“:[{„id“:“1″,“message“:“Some text“},{„id“:“2″,“message“:“Some more text“}]}}

Dieser String soll  nun geparst und der JSON-Inhalt in entsprechende MyBean-Objekte transformiert werden. Hierfür wird der komplette String zunächst einem JSONTokener übergeben. In unserem Fall liefert dessen nextValue-Methode ein JSONObject zurück, grundsätzlich wären aber auch ein JSONArray, String, NULL oder weitere Java-Typen möglich. Das so erhaltene JSONObject enthält wiederrum ein JSONObject mit dem Schlüsselwert „object“. Erst jetzt kommt man an das JSONArray „myview“, welches die gesammelten MyBean-Einträge enthält. Jeder dieser Einträge ist in einem JSONObject hinterlegt, welches die MyBean-Properties id und message enthält. Diese Werte können nun einem neu zu erzeugenden MyBean-Objekt übergeben werden:

.JSONTokener token = new JSONTokener(jsonContent);
while (token.more()) {
Object object = token.nextValue();
if (object instanceof JSONObject) {
JSONObject jsonRoot = (JSONObject) object;
try {
JSONObject jsonObject = jsonRoot.getJSONObject(„object“);
if (jsonObject != null) {
JSONArray jsonBeans = jsonObject.getJSONArray(„myview“);
if (jsonBeans != null) {
for (int i = 0; i < jsonBeans.length(); i++) {
JSONObject jsonBean = jsonBeans.getJSONObject(i);
int id = jsonBean.getInt(„id“);
String subject = jsonBean.getString(„message“);
MyBean myBean = new MyBean(id, message);
// adding myBean to a myBeans list
}
}
}
} catch (JSONException e) {
// do nothing, wrong JSON content
}
}

Soll nun eine POST- oder PUT-Operation getätigt werden, muss der umgekehrte Weg gegangen werden. Es wird nicht empfangener JSON-Inhalt deserialisiert, sondern ein MyBean-Objekt in JSON-Inhalt umgewandelt und an den Server gesendet. Hierfür wird eine Implementierung des HttpEntity-Interfaces benötigt:

public class MyHttpEntity implements HttpEntity {

private JSONObject jsonObject;

public MyHttpEntity(MyBean myBean) {
try {
jsonObject = new JSONObject();
jsonObject.put(„id“, myBean.getId());
jsonObject.put(„message“, myBean.getMessage());
} catch (JSONException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
}

@Override
public InputStream getContent() throws IOException, IllegalStateException {
return new ByteArrayInputStream(jsonObject.toString().getBytes());
}

@Override
public long getContentLength() {
return jsonObject.toString().length();
}

@Override
public Header getContentType() {
return new BasicHeader(„Content-Type“, „application/json“);
}

@Override
public void writeTo(OutputStream outstream) throws IOException {
outstream.write(jsonObject.toString().getBytes());
}

// … further methods, which are not required
//    for this example …
}

Im Konstruktor wird das zu transformierende MyBean-Objekt übergeben und dessen Eigenschaften in einem zu erstellenden JSONObject hinterlegt. Anschließend sind folgende Methoden zu erstellen:

  • getContent – Liefert den JSON-Inhalt als InputStream
  • getContentLength – Enthält die Länge des JSON-Inhaltes
  • getContentType – Fügt die Header Information „Content-Type: application/json“ hinzu. Hierfür wird ein BasicHeader verwendet.
  • writeTo – Der JSON-Inhalt wird in einen OutputStream gespeichert.

Das Interface HttpEntity enthält noch weitere zu implementierende Methoden, deren Funktionalität spielt für dieses Beispiel aber keine Rolle. Nachdem der Serialisierung einer MyBean nichts mehr im Wege steht, kann jetzt die eigentliche POST-Operation ausgeführt werden:

HttpPost post = new HttpPost(REST_RESSOURCE_URI);
post.setEntity(new MyHttpEntity(myBean));
String result = connect(post);

Äquivalent hierzu ist der PUT-Request, nur dass dieser auf einer bestehenden MyBean getätigt wird. Daher wird in der URI noch eine id zur Identifikation der MyBean übergeben:

HttpPut put = new HttpPut(REST_RESSOURCE_URI + „/myBean/“ + myBean.getId());
put.setEntity(new MyHttpEntity(myBean));
connect(put);

Einfach gestaltet sich die DELETE-Operation, da der Request außer der zu löschenden Ressource keine weiteren Informationen benötigt. Die id der zum Löschen auserwählten MyBean wird im Pfad übergeben:

HttpDelete delete = new HttpDelete(REST_RESSOURCE_URL + „/myBean/“ + id);
connect(delete);

Ist die Hilfsklasse für die Kommunikation erst einmal geschrieben, sind die einzelnen REST-Operationen schnell umgesetzt. Lediglich bei der Serialisierung bzw. Deserialisierung von und nach JSON handelt es sich um lästige Fleißarbeit. Zumal hier jeder Fehler dank des recht langsamen Android-Emulators zusätzlich Zeit kostet.

REST Services mit Spring 3

Eingetragen bei: Java, spring | 0

Ging vor ein paar Jahren der Trend bei der Übertragung von Informationen zwischen Softwaresystemen noch in Richtung Web-Services, so geht mittlerweile die Tendenz klar in Richtung REST.

REST selber basiert auf dem HTTP-Protokoll und ist zustandslos, da alle benötigten Informationen Bestandteil des jeweiligen HTTP-Requests sind. Die einzelnen Ressourcen die ein REST-Service zur Verfügung stellt, sind über eindeutige URIs erreichbar. Auf den Ressourcen können unter anderem folgende Operationen ausgeführt werden:

  • GET – Abfrage einer Ressource
  • POST- Zur aufgerufenen Ressource wird eine Sub-Ressource erzeugt
  • PUT – Verändern einer bestehende Ressource
  • DELETE – Löschen einer Ressource

Aufgrund der Entwicklung der vergangenen Jahren und der immer weiteren Verbreitung von REST wurde JAX-RS mit der Version 1.1 in den Java EE 6 Standard aufgenommen. Aktuell wird an der Version 2.0 gearbeitet, welche in den Java EE 7 Standard einfließen soll. Die gängigsten JAX-RS Implementierungen sind:

  • Jersey – Referenzimplementierung von Oracle
  • RESTEasy von JBoss
  • Restlet

Alle diese JAX-RS Implementierungen unterstützen Spring, was auch der Grund dafür ist, dass Spring selber keine eigene JAX-RS Implementierung anbietet. Man ist daher also auf eines der oben genannten Frameworks angewiesen.

Da ich mich bereits mit Restlet beschäftigt habe (siehe http://martinzimmermann1979.wordpress.com/2011/11/01/restlet-rest-im-osgi-kontext/), habe ich mich im folgenden Beispiel für dieses REST-Framework entschieden. Restlet kann unter http://www.restlet.org/ in der aktuellen Version 2.0.11 heruntergeladen werden.

Natürlich gibt es auch die Möglichkeit Restlet als Maven Dependency zu laden. Hier kann man zusätzlich die Restlet-Spring-Extension verwenden, die Spring in der Version 3.0.1 verwendet. Will man eine neuere Spring Version einbinden – aktuell ist Spring 3.1.0 verfügbar – so muss man dies explizit in der pom.xml definieren.

Hier die Maven Dependencys für Restlet:

<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet.ext.spring</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-oxm-tiger</artifactId>
<version>1.5.9</version>
</dependency>

Die Library spring-oxm-tiger beinhaltet den Jaxb2Marshaller, der zum marshalling bzw. unmarshalling von Java Objekten in XML und umgekehrt benötigt wird.

Für die Ergebnis-Darstellung wird Spring MVC verwendet. Unter Spring 3.x wurden weitere Views, wie die MarshallingView zur Darstellung von XML-Inhalten und die MappingJacksonJsonView für JSON-Inhalte, hinzugefügt.

Im folgenden Beispiel soll ein REST-Service umgesetzt werden, der die bekannten GET, POST, PUT und DELETE Operationen auf der Ressource MyBean zur Verfügung stellt. Im ersten Schritt findet der Datentransfer ausschließlich mittels XML statt, im zweiten Schritt soll zusätzlich JSON unterstützt werden. D.h. der Service kann XML und JSON verarbeiten und abhängig vom unterstützten Datenformat des Clients findet die Antwort des Servers in XML oder JSON statt.

Starten wir mit der Spring-Konfiguration. Zunächst muss die MVC Java Konfiguration aktiviert werden:

<mvc:annotation-driven />

Weiterhin wird das Component-Scanning auf Package-Ebene aktiviert. Hiermit erspart man sich, jede Bean einzeln in der Konfigurationsdatei anzugeben:

<context:component-scan base-package=“de.mz.rest“ />

Nun wird das RequestMapping auf Type- und Methoden-Ebene aktiviert:

<bean class=“org.spring…DefaultAnnotationHandlerMapping“/>
<bean class=“org.spring…AnnotationMethodHandlerAdapter“ />

Anschließend wird der Jaxb2Marshaller definiert, der fürs marshalling bzw. unmarshalling von Objekten in XML und umgekehrt zuständig ist. Unter classesToBeBound sind alle Objekte aufgelistet, die der Marshaller in irgendeiner Form umwandeln soll:

<bean id=“jaxbMarshaller“ class=“org.springframework.oxm.jaxb.Jaxb2Marshaller“>
<property name=“classesToBeBound“>
<list>
<value>de.mz.rest.data.MyBean</value>
</list>
</property>
</bean>

Für die Darstellung der vom Jaxb2Marshaller aufbereiteten XML-Inhalte ist die MarshallingView zuständig:

<bean id=“viewName“ class=“org.springframework.web.servlet.view.xml.MarshallingView“>
<constructor-arg ref=“jaxbMarshaller“ />
</bean>

Zu guter Letzt wird noch ein ViewResolver benötigt, der die Requests auf die entsprechenden Controller weiterleitet. Hier wird der BeanNameViewResolver verwendet. Über das RequestMapping im Controller selber, wird  das Mapping später aber noch mal explizit gesetzt:

<bean  class=“org.spring…BeanNameViewResolver“ id=“viewResolver“ />

Nun wird die Klasse de.mz.rest.data.MyBean erstellt, bei der es sich um die Bean handelt, die vom Jaxb2Marshaller in XML umgewandelt werden soll. Hierfür werden JAXB (Java Architecture for XML Binding) Annotationen verwendet:

@XmlRootElement
@XmlType
public class MyBean {
private String id;
private String message;
// getter and setter for id and message
}

Die Annotation @XmlRootElement definiert MyBean als ein Top Level Element. @XmlType hingegen kann dazu verwendet werden, um für die Attribute von MyBean eine bestimmte Reihenfolge zu definieren. Eigentlich ist diese Annotation laut JAXB-Spezifikation nicht zwingend nötig, allerdings benötigt der Jaxb2Marshaller diese Information. Andernfalls erachtet sich der Jaxb2Marshaller für die Klasse MyBean als nicht zuständig und beim Versuch diese Klasse in XML zu transformieren wird folgende, irreführende Fehlermeldung geworfen:

javax.servlet.ServletException: Unable to locate object to be marshalled in model

Wenden wir uns nun der REST-Ressource zu, die die gewohnte Spring MVC Annotation @Controller verwendet:

@Controller
@RequestMapping(„/myPath“)
public class MyResource extends ServerResource {

@RequestMapping(value=“/{id}“, method=RequestMethod.GET)
public ModelAndView get(@PathVariable String id)  {
return new ModelAndView(„viewName“, „object“, new MyBean());
}
}

Wie man in obigem Beispiel sieht, kommt die @RequestMapping Annotation zweimal zum Einsatz. Zum einen wird über die annotierte Klasse MyResource der Pfad definiert, unter dem die komplette REST-Ressource erreichbar ist, zum anderen werden die Methoden-Details definiert, wie Übergabe-Parameter und die jeweiligen Operationen (GET, POST, …) für die die Methode zuständig ist. Um innerhalb der Methode Zugriff auf die id aus dem Pfad zu erlangen, wird diese einfach als Übergabe-Parameter angegeben und mit der Annotation @PathVariable versehen. Nun muss dem ModelAndView-Element noch der ViewName der MarshallingView und die gewünschte MyBean übergeben werden. Die obige REST-Ressource ist über einen GET-Request auf die URI http://<server>:<port>/<webapp>/myPath/<id> aufrufbar.

Die letzten Einstellungen die noch fehlen, sind der ContextLoaderListener und das DispatcherServlet in der web.xml Datei:

<listener>
<listener-class>org.spring…ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>servletName</servlet-name>
<servlet-class>org.spring…DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

Bislang liefert die REST-Ressource nur ein einziges MyBean-Element zurück, welches über die id spezifiziert wird. Will man die vollständige Liste an MyBeans erhalten, wird ein Wrapper für eine Liste von MyBeans benötigt. Auch dieser Wrapper, die MyBeanList, ist wie gehabt mit dem JAXB @XmlRootElement zu versehen:

@XmlRootElement
@XmlType
public class MyBeanList {

private List<MyBean> myBeans;

public List<MyBean> getMyBeans() {
return myBeans;
}
public void setMyBeans(List<MyBean> myBean) {
this.myBean = myBean;
}
}

Anschließend kann die MyBean-Ressource um folgende Methode ergänzt werden:

@RequestMapping(method = RequestMethod.GET)
public ModelAndView getMyBeans() {
List<MyBean> myBeans = new ArrayList<MyBean>();
// add MyBeans to list
MyBeanList beanList = new MyBeanList();
beanList.setMyBeans(myBeans);
return new ModelAndView(„viewName“, „object“, beanList);
}

Es ist zu beachten, dass auch die MyBeanList in der Spring Konfiguration des Jaxb2Marshaller unter classesToBeBound hinzugefügt werden muss.

Diese Ressource ist mittels eines GET-Requests unter der URI http://<server>:<port>/<webapp>/myPath erreichbar.

Die GET-Requests aus den vorangegangenen Beispielen sind recht simpel zu testen, da diese Ressourcen über einen Web-Browser aufrufbar sind. Die vom Jaxb2Marshaller serialisierten Daten werden im Browser als XML dargestellt. Zusätzlich sollen aber noch die Operationen POST, PUT und DELETE umgesetzt werden. Der POST und PUT Request beinhaltet Daten, die von der REST-Ressource deserialisiert werden müssen, um das Objekt MyBean zu erhalten. Hier nun die Umsetzung der POST-Schnittstelle:

@RequestMapping(method=RequestMethod.POST)
public ModelAndView addBean(@RequestBody MyBean bean) {
// add bean
return new ModelAndView(„viewName“, „object“, bean);
}

Die Methode des RequestMapping wird als POST-Operation definiert. Die Daten selber werden als Übergabeparameter in die Methoden-Signatur aufgenommen und müssen lediglich mit der Annotation @RequestBody versehen werden. Den Rest übernimmt der Jaxb2Marshaller.

Die Ressource ist mit einem POST-Request unter der URI http://<server>:<port>/webapp/myPath erreichbar.

Zum Testen der Ressource kann das Kommandozeilen-Tool curl http://curl.haxx.se/ verwendet werden. Hier der Befehl inklusive Parameter, mit denen sich die Ressource Testen lässt:

curl -v -H „Accept: application/xml“ -H „Content-type: application/xml“ -X POST -d ‚<myBean><id>1</id><message>some text</message></myBean>‘  http://<server>:<port>/<webapp>/myPath

Hier die Erläuterung zu den einzelnen Parametern:

  • H <header>: Hiermit wird dem Request eine Header-Information hinzugefügt.
    1. Die Antwort des Servers soll XML sein, da dies vom Client akzeptiert wird.
    2. Die vom Client gesendeten Daten bestehen aus XML.
  • X <command>: Der Request wird ausgewählt, z.B. GET, POST, PUT oder DELETE
  • d <data>: Hier wird der eigentliche Inhalt des Requests definiert.
  • v: Steht für verbose. Wird verwendet um mehr Informationen zu erhalten, kann ansonsten aber auch weggelassen werden.

Wenn man lieber mit einem Grafischen Tool arbeiten möchte, kann man statt curl das Firefox Plugin REST Client verwenden.

Erweitern wir nun die REST-Ressource um eine PUT-Methode, damit eine bestehende MyBean verändert werden kann. Hierbei wird zum einen die id benötigt, die im Pfad angegeben wird und als @PathVariable in der Methoden-Signatur annotiert ist. Zum anderen wird – wie beim POST-Request – die mit @RequestBody annotierte MyBean übergeben, die die aktualisierten Daten beinhaltet:

@RequestMapping(value = „/{id}“, method=RequestMethod.PUT)
public ModelAndView updateMyBean(@PathVariable String id, @RequestBody MyBean bean) {
// update bean
return new ModelAndView(„viewName“, „object“, bean);
}

Aufrufbar ist diese Ressource unter einem PUT-Request auf http://<server>:<port>/webapp/myPath/<id>.

Für die DELETE-Operation wird nur die id der zu löschenden MyBean im Pfad übergeben. Die REST-Ressource gibt dann eine Liste der verbleibenden MyBeans zurück, die wie gehabt in dem Wrapper MyBeanList enthalten sind.

@RequestMapping(value = „/{id}“, method=RequestMethod.DELETE)
public ModelAndView removeMyBean(@PathVariable String id) {
// delete mybean and return list of remaining beans
return new ModelAndView(„todos“, „object“, remainingBeans);
}

Die Ressource wird mittels eines DELETE-Requests auf http://<server>:<port>/webapp/myPath/<id> aufgerufen.

Im letzten Teil des Artikels soll der REST-Service erweitert werden, dass er nicht nur XML sondern auch JSON unterstützt. Hierfür bietet Spring den ContentNegotiatingViewResolver an, der für die korrekte Darstellung das View Konzept verwendet. Abhängig vom gewünschten Inhalt - diese gibt der Client im Accept-Header des Requests mit an (z.B.: Accept: application/json) - wird die entsprechende View angesprochen.

Für die JSON Unterstützung wird das Jackson-Framework http://xircles.codehaus.org/projects/jackson benötigt, welches zur Zeit in der Version 1.9.5 zu downloaden ist.

Das Jackson Framework kann natürlich auch als Maven Dependency verwendet werden:

<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.5</version>
</dependency>

Nun muss der ContentNegotiatingViewResolver konfiguriert werden:

<bean class=“org.spring…ContentNegotiatingViewResolver“>
<property name=“mediaTypes“>
<map>
<entry key=“json“ value=“application/json“/>
<entry key=“xml“ value=“application/xml“/>
</map>
</property>
<property name=“viewResolvers“>
<list>
<ref bean=“viewResolver“ />
</list>
</property>
<property name=“defaultViews“>
<list>
<ref bean=“viewName“/>
<bean class=“org.spring…MappingJacksonJsonView“>
<property name=“prefixJson“ value=“true“/>
</bean>
</list>
</property>
</bean>

Zunächst werden die beiden MediaTypes definiert, die unterstützt werden sollen, also XML und JSON. Als ViewResolver kann der bereits definierte BeanNameViewResolver verwendet werden. Ansonsten müssen jetzt noch die Views definiert werden. Da weiterhin XML unterstützt werden soll, erhält die Property defaultViews die bereits definierte MarshallingView übergeben. Außerdem wird hier noch die MappingJacksonJsonView definiert.

Die JSON-Unterstützung der REST-Ressource kann ebenfalls mit curl überprüft werden, indem die Header-Informationen und die Daten angepasst werden:

curl -v -H „Accept: application/json“ -H „Content-type: application/json“ -X POST -d ‚{„id“:“1″,“message“:“Some Text“}‘  http://<server>:<port>/<webapp>/myPath

Insgesamt lässt sich sagen, dass man mit Spring 3.x recht komfortable REST-Services erstellen kann. Die dafür benötigte Konfigurations-Arbeit hält sich in Grenzen und mit der Kombination aus Spring MVC- und JAXB-Annotations kommt man schnell zum Ziel. Lediglich die Dokumentation ist mangelhaft und man muss sich die Informationen recht mühsam zusammen suchen. Auch das die vom Jaxb2Marshaller zu transformierenden Klassen zwangsläufig die @XmlType Annotation benötigen, ist nur schwer nachzuvollziehen.