Tutoriel sur le développement des services REST avec Spring 3

| Netapsys | Olivier Chaumont |

Cet article présente les différentes options qui permettent d'exposer des services REST à l'aide de Spring MVC.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum. Commentez .

- Titre : Tutoriel sur le développement des services REST avec Spring 3

- Auteurs : Netapsys, Olivier Chaumont

- Parution : 25 février 2015

- Licence : Copyright ® 2015 Netapsys. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Méthode 1 : utiliser le ContentNegotiatingViewResolver

- III. Méthode 2 : utiliser les HttpMessageConverters

- IV. Conclusion

- V. Remerciements

I - Introduction

L'objet de cet article est de présenter les différentes options qui permettent d'exposer des services REST à l'aide de Spring MVC 3.2.

Spring propose deux approches pour exposer des services REST.

- La première est basée sur les principes de fonctionnement du framework MVC de Spring, à savoir l'utilisation du ContentNegotiatingViewResolver.

- La seconde, plus récente, fait appel aux HttpMessageConverters et à l'annotation @ResponseBody.

II - Méthode 1 : utiliser le ContentNegotiatingViewResolver

L'utilisation du ContentNegotiatingViewResolver pour exposer des API REST a été la première approche proposée, car elle s'appuie sur le mécanisme de Spring MVC existant.

Pour rappel, SpringMVC permet de définir des ViewResolver qui permettent d'expliquer la manière dont seront affichées les données retournées par le Controller MVC.

La déclaration de la servlet Spring dans le XML est nécessaire :

<web-app>
<servlet>
<servlet-name>dispatchServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>disptachServlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>

Dans le cas classique d'affichage d'une JSP, on utilise l'InternalViewResolver (qui permet d'appeler une JSP pour afficher les données) que l'on déclare dans la configuration Spring :

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>

Note : la servlet de Spring (DispatcherServlet) référence les différents ViewResolver en parcourant les beans définis dans la configuration Spring.

Lorsque vous utilisez le ContentNegotiatingViewResolver, votre controller web retourne un ModelView ou le nom d'une vue, et selon certains critères paramétrables, le ContentNegotiatingViewResolver va choisir la vue à utiliser pour retourner le résultat :

@Controller
@RequestMapping(value="/todo")
public class TodoRestService2 {
@Autowired
private TodoService todoService;

@RequestMapping(value="/{id}", method= RequestMethod.GET)
public ModelAndView find(@PathVariable("id") Long id) {
Todo todo = todoService.find(id);
return new ModelAndView("todo", "todo", todo);
}
}

Pour l'exposition de service REST, Spring propose différentes implémentations de ces vues pour les formats de contenus les plus courants :

- org.springframework.web.servlet.view.json.MappingJacksonJsonView : format de type JSON ;

- org.springframework.web.servlet.view.xml.MarshallingView : format de type XML ;

- org.springframework.web.servlet.view.documentClass.AbstractPdfView : format de type PDF ;

- etc.

Il est bien sur possible de créer des implémentations spécifiques ou d'étendre celles existant.

Les critères qui permettent au ContentNegotiatingViewResolver de choisir la bonne vue sont définis à l'aide de stratégies. Ces stratégies permettent de définir un lien entre l'URL appelée par le client et le format de la réponse qui sera retournée. La vue qui correspond au format sera alors appelée.

Spring fournit principalement les implémentations suivantes :

- org.springframework.web.accept.PathExtensionContentNegotiationStrategy : stratégie basée sur l'extension de l'URL (.json, .xml, .html, etc) ;

- org.springframework.web.accept.ParameterContentNegotiationStrategy : stratégie basée sur le passage d'un paramètre dans la requête ;

- org.springframework.web.accept.HeaderContentNegotiationStrategy : stratégie basée sur l'analyse sur l'attribut « accept » du header de la requête.

Prenons l'exemple d'un service RESTFul qui permettrait de retourner le contenu d'un bean Java en XML ou en JSON en fonction de l'extension de l'URL.

Ci-dessous la configuration qui permet de le faire :

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="contentNegotiationManager">
<bean class="org.springframework.web.accept.ContentNegotiationManager">
<constructor-arg>
<bean class="org.springframework.web.accept.PathExtensionContentNegotiationStrategy">
<constructor-arg>
<map>
<entry key="json" value="application/json"/>
<entry key="xml" value="application/xml"/>
</map>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
</property>

<property name="defaultViews">
<list>
<!-- Renders JSON View -->
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />

<!-- Renders XML View -->
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="packagesToScan">
<list>
<value>org.demo.domain</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
</list>
</property>
</bean>

Dans cet exemple, la stratégie PathExtensionContentNegotiationStrategy permet de définir qu'un appel vers une URL finissant par .json sera dirigé vers la vue MappingJacksonJsonView, car elle est capable de générer du contenu de type application/json.

Même chose avec les URL finissant par .xml et la vue MarshallingView.

III - Méthode 2 : utiliser les HttpMessageConverters

Pour répondre de manière plus spécifique aux problèmes d'exposition de service REST, Spring propose une nouvelle approche depuis la version 3 : l'utilisation de l'annotation @RequestBodyqui permet l'usage systématique d'un HttpMessageConverters.

Qu'est-ce que cela change ?

Au lieu de retourner un ModelView (ou le nom d'une vue), la méthode retourne directement le contenu à retourner, ce qui se traduit par un objet ou une collection d'objets. Ensuite, le HttpMessageConverters se charge de convertir ces objets vers le flux de données attendu par le client.

L'activation des HttpMessageConverter est réalisée en déclarant la ligne suivante dans la configuration Spring :

<mvc:annotation-driven/>

Cette déclaration permet de charger les HttpMessageConverter par défaut fournis par Spring :

StringHttpMessageConverter

Lit/écrit un String à partir d'une requête ou d'une réponse. Par défaut, il répond aux médias de type « text/* » et retourne un contenu de type « text/plain ».

FormHttpMessageConverter

Lit/écrit un Data Form à partir d'une requête ou d'une réponse. Par défaut, il répond aux médias de type « application/x-www-form-urlencoded » et retourne un contenu à l'aide d'un MultiValueMap<String,String>.

MarshallingHttpMessageConverter

Lit/écrit des données XML en utilisant les marshaller/unmarshaller fournis par Spring. Par défaut, il retourne un contenu de type « application/xml ».

MappingJacksonHttpMessageConverter

Lit/écrit des données JSON en utilisant la librairie Jackson's en tant qu'ObjectMapper. Il convertit des données de type « application/json ».

AtomFeedHttpMessageConverter

Lit/écrit des flux de données au format ATOM à l'aide de la librairie ROME. Il convertit des données de type « application/atom+xml ».

RssChannelHttpMessageConverter

Lit/écrit des flux ATOM à l'aide de la librairie ROME. Il convertit des données de type « application/rss+xml ».

Si vous souhaitez déclarer vos propres HttpMessageConverter, il faut le faire ainsi :

<mvc:annotation-driven>
<mvc:message-converters register-defaults="false">
<ref bean="jsonHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>

<bean
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="prefixJson" value="false"/>
<property name="supportedMediaTypes" value="application/json"/>
</bean>

À noter que l'attribut register-defaults permet de définir si les HttpMessageConverter par défaut sont également déclarés ou pas.

Si l'on reprend l'exemple d'un service REST qui permettrait de retourner le contenu d'un bean Java en XML ou en JSON en fonction de l'extension de l'URL, tout le déclaratif est réalisé au niveau du controller :

@Controller
@RequestMapping(value="/todo")
public class TodoRestServiceWithHTTPConverter {

@Autowired
private TodoService todoService;

/*
* Return always Json data
*/
@RequestMapping(value="/{id}.json", method= RequestMethod.GET,
produces="application/json")
@ResponseBody public Todo findAsJson(@PathVariable("id") Long id) {
return todoService.find(id);
}

/*
* Return always XML data
*/
@RequestMapping(value="/{id}.xml", method= RequestMethod.GET,
produces="application/xml")
@ResponseBody
public Todo findAsXML(@PathVariable("id") Long id) {
return todoService.find(id);
}
}

L'attribut produces permet de définir le format de sortie et de faire le lien avec le HttpMessageConverter compatible.

IV - Conclusion

Nous avons vu deux méthodes pour exposer des services REST avec Spring. La mise en œuvre a été réalisée sur un POC disponible sur GitHub à l'adresse suivante :

https://github.com/ochaumont/spring-rest-simple.git

V - Remerciements

Cet article a été publié avec l'aimable autorisation de Netapsys.

Nous tenons à remercier Malick SECK pour la relecture orthographique attentive de cet article et Régis Pouiller pour la mise au gabarit.