Les implémentations JAX-RS (1)

Il y a quelque temps l’envie de développer une petite application Java RESTFul m’a pris subitement. Je me suis donc penché sur les différentes implémentations JAX-RS et voici le résultat de mon investigation.

Je me suis tout de même posé quelques contraintes :

  • que mon code Java soit complètement indépendant de l’implémentation JAX-RS choisie ;
  • pouvoir facilement intégrer mes services avec Spring ;
  • ne produire et consommer que du JSON en utilisant l’API Jackson ;
  • pouvoir facilement mettre en place des tests unitaires.

Ce qui m’amène à comparer trois implémentations : CXF (version 2.3.2), Jersey (version 1.5), RESTEasy (version 2.1.0.GA) et Restlet (version 2.0.5).

CXF, Jersey et RESTEasy utilisent Jettison pour produire et consommer du JSON. Or Jettison est un outil de mapping JSON/XML or je souhaite effectuer un simple mapping POJO/JSON. Raison pour laquelle j’opte pour Jackson.

Je partirai du principe qu’une certaine connaissance de JAX-RS, Spring et Maven est acquise par mes lecteurs.

Concernant les performances, je ne m’attarderai pas dessus car il s’avère qu’elles se tiennent dans un mouchoir de poche.

Les sources sont disponibles sur Github.

Sans plus attendre, revêtons notre bleu de travail, calons notre crayon derrière l’oreille et mettons les mains dans le cambouis…

Le service

Voici le code – on ne peut plus simple – du service que l’on se propose de déployer :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Path("/someResource")
@Service
public class SomeService {
	@Resource
	private BeanRepo beanRepo;
 
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public List<SomeBean> getSomeBeans() {
		return beanRepo.getAll();
	}
 
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("{index}")
	public SomeBean getSomeBean(@PathParam("index") int index) {
		return beanRepo.get(index);
	}
 
	@PUT
	@Consumes(MediaType.APPLICATION_JSON)
	public void saveSomeBean(SomeBean someBean) {
		beanRepo.save(someBean);
	}
}

On remarquera l’utilisation des annotations @Produces et @Consumes qui indiquent que l’on ne produit et consomme ici que du JSON.

CXF

On ne présente plus CXF, la stack par excellence pour développer des services en Java supportant tous types de transports et de protocoles et aux performances qui ne sont plus à démontrer.

Et si vous n’êtes toujours pas convaincus, allez donc jeter un œil aux articles référencés ici ou .

Certains argueront qu’utiliser CXF pour du REST c’est comme aller chercher un couteau suisse pour ouvrir une bière quand un simple décapsuleur était à portée de main…

Dépendances

La documentation de CXF est très bien fournie à ce niveau. Cependant l’artifact Maven cxf-rt-frontend-jaxrs ramène toutes les dépendances permettant d’effectuer des mappings XML ainsi que Jettison, ce dont je n’ai pas besoin.

Afin de n’importer que le strict nécessaire mon POM ressemble donc à ceci :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-rt-frontend-jaxrs</artifactId>
	<version>${cxf.version}</version>
	<!-- Since we use jackson, we can get rid of Jettison and all XML stuff -->
	<exclusions>
		<exclusion>
			<groupId>org.apache.xmlbeans</groupId>
			<artifactId>xmlbeans</artifactId>
		</exclusion>
		<exclusion>
			<groupId>com.sun.xml.bind</groupId>
			<artifactId>jaxb-impl</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.codehaus.woodstox</groupId>
			<artifactId>woodstox-core-asl</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.codehaus.jettison</groupId>
			<artifactId>jettison</artifactId>
		</exclusion>
		<exclusion>
			<artifactId>cxf-rt-databinding-aegis</artifactId>
			<groupId>org.apache.cxf</groupId>
		</exclusion>
	</exclusions>
</dependency>

Configuration Spring

CXF gérant différents types de transports et de protocoles, il nécessite davantage de configuration que ses « concurrents » avec notamment l’import des fichiers de configuration CXF, le namespace jaxrs à définir et enfin la déclaration et configuration systématique de chaque service…

En outre la documentation n’explique pas très clairement comment intégrer Jackson et si vous êtes nouveau sur CXF il vous faudra parcourir toute la documentation avant de comprendre comment ajouter un provider à un service REST (ce qui en fin de compte n’est pas bien compliqué comme le montre la suite).

L’essentiel de la documentation se trouve ici et . Pour faire court, le principe est de fournir un provider JSON à nos services :

1
2
3
4
5
6
7
8
<jaxrs:server id="someService" transportId="http://cxf.apache.org/transports/http">
	<jaxrs:serviceBeans>
		<bean class="com.octo.rest.SomeService" />
	</jaxrs:serviceBeans>
	<jaxrs:providers>
		<bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
	</jaxrs:providers>
</jaxrs:server>

Simple certes, mais il faut le faire pour chaque service !

On notera l’attribut transportId sur la balise jaxrs:server, qui par défaut vaut http://schemas.xmlsoap.org/wsdl/http (donc du SOAP, ce qui n’est pas vraiment ce que l’on veut).

Il est possible de ne pas avoir à redéfinir le provider pour chacun de ses services, mais il faut creuser un peu dans les classes de CXF. En l’occurrence il suffit de manipuler directement la ProviderFactory :

1
2
3
4
5
<bean class="org.apache.cxf.jaxrs.provider.ProviderFactory" factory-method="getSharedInstance">
	<property name="userProviders">
		<bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
	</property>
</bean>

Bref CXF qui est génial pour faire du SOAP et du RPC dans tous les sens dans des architectures hétérogènes tout en garantissant d’excellentes performances n’est ici clairement pas la solution la plus pratique à mettre en œuvre.

Jersey

C’est l’implémentation de référence de JAX-RS, simple, efficace et performante.

Afin d’utiliser Jackson avec Jersey il nous faut :

  • importer l’artefact jersey-json
  • mettre à true la propriété com.sun.jersey.api.json.POJOMappingFeature au niveau de la servlet Jersey dans le web.xml. Cette propriété indique que l’on souhaite mapper nos POJO directement sur du JSON au lieu du XML.

Ce dernier point est parfaitement expliqué dans la documentation par ailleurs assez complète.

Enfin, la configuration Spring est on ne peut plus simple puisqu’il suffit d’activer la gestion des annotations.

Dépendances

Là encore un nettoyage est nécessaire si l’on ne souhaite importer que l’essentiel :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<dependency>
	<groupId>com.sun.jersey.contribs</groupId>
	<artifactId>jersey-spring</artifactId>
	<version>1.5</version>
</dependency>
<dependency>
	<groupId>com.sun.jersey</groupId>
	<artifactId>jersey-json</artifactId>
	<version>1.5</version>
	<exclusions>
		<exclusion>
			<groupId>org.codehaus.jackson</groupId>
			<artifactId>jackson-core-asl</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.codehaus.jackson</groupId>
			<artifactId>jackson-mapper-asl</artifactId>
		</exclusion>
		<exclusion>
			<artifactId>jaxb-impl</artifactId>
			<groupId>com.sun.xml.bind</groupId>
		</exclusion>
		<exclusion>
			<artifactId>jettison</artifactId>
			<groupId>org.codehaus.jettison</groupId>
		</exclusion>
	</exclusions>
</dependency>

Jersey est donc extrêmement simple à mettre en œuvre et s’avère largement suffisant pour mon utilisation.

RESTEasy

Pour finir voici l’implémention de JBoss de JAX-RS. Ses avantages sont principalement :

  • son intégration assez poussée avec le serveur JBoss
  • son intégration avec, outre Spring, Spring-MVC, Guice, Seam

Comme les autres implémentations, RESTEasy utilise Jettison par défaut comme provider JSON. Pour utiliser Jackson, il suffit d’importer l’artefact resteasy-jackson-provider et le tour est joué.

J’ajouterai que RESTEasy fournit probablement la documentation la plus complète des trois implémentations.

Dépendances

Une fois de plus il faudra faire du nettoyage dans le POM :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<dependency>
	<groupId>org.jboss.resteasy</groupId>
	<artifactId>resteasy-spring</artifactId>
	<version>2.1.0.GA</version>
	<exclusions>
		<exclusion>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-jaxb-provider</artifactId>
		</exclusion>
		<exclusion>
			<artifactId>resteasy-jettison-provider</artifactId>
			<groupId>org.jboss.resteasy</groupId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.jboss.resteasy</groupId>
	<artifactId>resteasy-jackson-provider</artifactId>
	<version>2.1.0.GA</version>
	<exclusions>
		<exclusion>
			<groupId>org.codehaus.jackson</groupId>
			<artifactId>jackson-core-asl</artifactId>
		</exclusion>
		<exclusion>
			<groupId>org.codehaus.jackson</groupId>
			<artifactId>jackson-mapper-asl</artifactId>
		</exclusion>
	</exclusions>
</dependency>

On notera qu’alors que l’artefact resteasy-jackson-provider n’importe bien que l’essentiel, ce qui offre un avantage intéressant comparé à ses concurrents. resteasy-spring importe malheureusement indirectement des providers via resteasy-core qu’il me faut exclure.

Restlet

Restlet est initialement une solution Java RESTFul conçue pour fonctionner de manière autonome contrairement aux frameworks précédents.
Restlet propose néanmoins diverses extensions dont une offrant la possibilité de le faire fonctionner au sein d’un conteneur de servlets, une autre offrant le support JAX-RS et une autre le support de Spring.
Par conséquent cette partie de l’article n’est pas forcément pertinente dans le sens où elle montre une utilisation de Restlet plutôt bancale, mais me semble néanmoins intéressante.

Dépendances

Un petit nettoyage du POM s’impose pour ne garder que l’essentiel :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<dependency>
	<groupId>org.restlet.jee</groupId>
	<artifactId>org.restlet.ext.jaxrs</artifactId>
	<version>2.0.5</version>
	<exclusions>
		<exclusion>
			<artifactId>jaxb-impl</artifactId>
			<groupId>com.sun.xml.bind</groupId>
		</exclusion>
		<exclusion>
			<artifactId>jaxb-api</artifactId>
			<groupId>javax.xml.bind</groupId>
		</exclusion>
		<exclusion>
			<artifactId>org.restlet.lib.org.json</artifactId>
			<groupId>org.restlet.jee</groupId>
		</exclusion>
		<exclusion>
			<artifactId>stax-api</artifactId>
			<groupId>javax.xml.stream</groupId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.restlet.jee</groupId>
	<artifactId>org.restlet.ext.spring</artifactId>
	<version>2.0.5</version>
	<exclusions>
		<exclusion>
			<artifactId>spring-webmvc</artifactId>
			<groupId>org.springframework</groupId>
		</exclusion>
	</exclusions>
</dependency>

Un peu de code

Il va nous falloir un minimum de code supplémentaire qui va servir de glue entre Restlet, JAX-RS, Spring et mon conteneur de servlets.
Cette « glue » se limite à une classe :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Component("jaxrsApp")
public class MyJaxrsApplication extends JaxRsApplication {
	@Autowired
	public MyJaxrsApplication(final ApplicationContext context) {
		super();
		add(new Application() {
			private Set<Class<?>> classes = new HashSet<Class<?>>();
			private Set<Object> singletons = new HashSet<Object>();
			{
				if (context.getBeansWithAnnotation(Path.class) != null)
					for (Object bean : context.getBeansWithAnnotation(Path.class).values())
						classes.add(bean.getClass());
				singletons.add(new JacksonJsonProvider());
			}
 
			@Override
			public Set<Class<?>> getClasses() {
				return classes;
			}
 
			@Override
			public Set<Object> getSingletons() {
				return singletons;
			}
		});
 
		setObjectFactory(new ObjectFactory() {
			public <T> T getInstance(Class<T> jaxRsClass)
					throws InstantiateException {
				return context.getBean(jaxRsClass);
			}
		});
	}
}

Je n’entrerai pas ici dans le détail du fonctionnement de Restlet – ce qui serait hors sujet -, néanmoins certains éléments nécessitent quelques précisions.
La classe Application est celle de la JSR-311 dont les méthodes getClasses et getSingletons sont censées retourner les classes ou singletons correspondant à des ressources REST ou des providers.
Il semblerait logique de fournir les ressources REST via la méthode getSingletons en allant les chercher dans le conteneur IoC de Spring. Malheureusement Restlet ne permet cela pour l’instant que pour les providers, les ressources devant nécessairement être fournies par la méthode getClasses.
Fort heureusement, la classe JaxRsApplication possède la méthode setObjectFactory qui va nous permettre de définir de quelle manière Restlet va instancier les classes renvoyées par la méthode getClasses.
Il faut ensuite configurer la servlet SpringServerServlet dans le web.xml en lui fournissant en paramètre le nom du bean Spring correspondant à l’application Restlet :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<servlet>
	<servlet-name>Restlet</servlet-name>
	<servlet-class>org.restlet.ext.spring.SpringServerServlet</servlet-class>
	<init-param>
		<param-name>org.restlet.application</param-name>
		<param-value>jaxrsApp</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>
 
<servlet-mapping>
	<servlet-name>Restlet</servlet-name>
	<url-pattern>/*</url-pattern>
</servlet-mapping>

Conclusion

Nous voici arrivés à la fin de la première partie de ce tour d’horizon des implémentations JAX-RS.

Que peut-on conclure à l’issue de cette première partie ?

CXF est incontestablement très puissant, et justement trop en l’occurrence. D’un autre côté, si pour une raison quelconque je devais passer à une architecture hétérogène, je sais qu’un passage vers CXF serait parfaitement envisageable et que la transition se ferait sans douleurs, puisque consistant essentiellement en un travail de configuration Spring (certes rébarbatif).

Concernant Restlet, comme dit précédemment, ce n’est pas vraiment une utilisation normale du framework qui est proposée ici, celui-ci se positionnant plus comme une alternative à JAX-RS.

Jersey et RESTEasy offrent simplicité et performance et collent parfaitement à mes besoins.

A mon sens, le choix entre RESTEasy et Jersey dépendra en partie du choix du serveur d’application : si vous optez pour JBoss, RESTEasy offre des avantages certains.

D’un autre côté la communauté derrière le projet Jersey, dont la dernière version date de janvier, semble bien plus active que celle de RESTEasy.

Reste toutefois un point essentiel à aborder : les tests, que nous verrons dans un prochain article et qui pourraient bien faire pencher la balance.

En attendant vous pouvez déjà jeter un oeil aux sources.

Mots-clés: , , , ,

7 commentaires pour “Les implémentations JAX-RS (1)”

  1. Bonjour,

    Restlet dispose de sa propre « Restlet API » beaucoup plus riche fonctionnellement que JAX-RS, qui repose sur une conception orientée-objet plus classique avec héritage et aussi une utilisation optionnelle et très light des annotations.

    Concernant JAX-RS, le support dans Restlet est disponible sous forme d’une extension « org.restlet.ext.jaxrs » qui n’exige absolument pas un héritage depuis la classe ServerResource.

    Je serais également curieux de savoir pourquoi ce désir de n’avoir aucune dépendance autre que l’API JAX-RS et donc d’écarter d’autres alternatives non estampillées par Oracle/JCP par principe?

    Surtout, dans la plupart des cas, les applis JAX-RS dépendent d’autres briques open source telles que Spring/Guice, Jackson/XStream qui limitent de facto cette portabilité un peu mirage. Le code résultant est pieds et poings lié à ces dépendances, donc qu’est-ce que l’usage de l’API JAX-RS apporte vraiment de plus.

    A l’ère de l’open source, le besoin de « standards » à la sauce JCP me semblent beaucoup moins pertinents, sauf pour des choses très consensuelles et bas niveau comme Java SE. Cela avait du sens à l’ère des serveurs d’applications commerciaux où la dépendance à l’éditeur et sa politique commerciale était beaucoup plus gênante.

    En attendant les futures améliorations de JAX-RS, notre projet continue à avancer à grands pas et à innover avec l’introduction dans la 2.1 en cours de développement du support de SIP (protocole cousin de HTTP pour la VoIP) ou encore le support de SDC (Secure Data Connector) en dehors de GAE, améliorant la portabilité multi-cloud, le vrai enjeu de portabilité en ce moment.

    Très cordialement,
    Jérôme Louvel
    htpp://www.restlet.org

  2. Bonjour,

    Merci pour vos remarques intéressantes.
    Je vous concède que j’ai été léger sur Restlet : j’ai donc rectifié le tir en lui dédiant un chapitre.
    Le but de cet article est de fournir un panel des implémentations JAX-RS. Les alternatives Java RESTFul feront l’objet d’un (ou plusieurs) article(s) ultérieur(s), ainsi que le déploiement dans le cloud.

    Cordialement,

    Cyril PODER

  3. Bonjour Cyril,

    Merci pour votre réactivité. Je vais suivre avec intérêt les prochains articles :)

    Bonne continuation,
    Jérôme Louvel

  4. [...] l’article précédent nous avions abordé la mise en œuvre des implémentations JAX-RS Jersey et RESTEasy [...]

  5. [...] OCTO a publié deux bons billets sur les implémentations de services REST et sur leur testabilité. Je vais donc laisser ce sujet de côté pour le moment et différer le [...]

  6. Salut Cyril,

    J’ai lu avec un très grand intérêt cet article qui m’a permit un déploiement sans soucis sur appengine. C’est « presque » parfait :)
    En effet, j’ai repris l’exemple en remaniant 2/3 choses pour obtenir une application basée uniquement sur RESTEasy. Il se trouve que les deux exclusions que tu fais sur « resteasy-jackson-provider » ne permette pas d’obtenir une application qui fonctionne. Sur ta configuration, le war obtenu embarque finalement jackson-core-asl.jar et jackson-mapper-asl.jar que tu pensais exclure.
    De mon côté, lorsque je les exclus du war, l’exemple ne foncionne plus.

    Voilà, bon courage !

  7. Bonjour,

    Pour la théorie, voici un lien intéressant sur le sujet chez developpez.com de Mickael Baron :

    http://mbaron.developpez.com/soa/jaxrs/

    Cdt,
    Yoann

Laissez un commentaire