JEE 6 : JEE enfin productif, léger et testable ? Partie 2
Dans la première partie nous avons vu comment avec JEE 6 nous pouvions représenter nos données et écrire des services (via EJB) permettant de les exploiter. Nous allons maintenant voir comment les exposer d'abord via un WebService REST puis via JSF 2.
Exposition des données : REST
On connaissait déjà l'exposition simplifiée de services REST en JEE5. La spécification JAX-RS 1.1 apporte toutefois quelques nouveautés fort appréciables. L'écriture du WebService n'a en soit pas vraiment changée, exemple :
@Path(value = "/formationsrest")
public class FormationsRestService {
@EJB
FormationManager formationManager;
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public List<Formation> getFormations() {
return formationManager.getAllFormations();
}
}
La nouveauté intéressante réside dans le code de la méthode getFormations(). Si vous reprenez le code de FormationManager de la partie 1, elle ne fait que retourner une liste d'entités JPA, pourtant on précise bien ici que notre méthode produit du XML ou du JSON. Et si on appel ce service avec l'url choisie, on verras bien du XML ou du JSON sortir.
En fait il suffit de rajouter une annotation sur notre entité JPA : @XmlRootElement
@Entity
@NamedQuery(name = "findAllFormations", query = "SELECT f FROM Formation f")
@XmlRootElement
public class Formation {
//...
}
Cette seule annotation JAXB va permettre à notre service REST d'exposer notre liste dans un XML valide. La même chose est possible en JSON. Enfin si vous regardez bien l'annotation sur la méthode du WebService : @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}), on voit que l'on utilise la même méthode pour exposer nos données en XML ou en JSON et ce avec les mêmes objets annotés via JAXB.
Ainsi il suffit au client de préciser dans l'en tête de la requête quel format il souhaite obtenir. Sous forme de tests d'intégration cela donnerait donc :
@Test
public void testGetXMLForRestService() {
String BASE_URI = "http://localhost:8080/octoformation/";
Client client = Client.create();
WebResource webResource = client.resource(BASE_URI);
// envoi une requête de type Get avec le champs Accept du header fixé à "application/xml"
String response = webResource.path("formationsrest").accept(MediaType.APPLICATION_XML).get(String.class);
assertTrue(response.startsWith("<?xml"));
}
@Test
public void testGetJSONForRestService() {
Client client = Client.create();
WebResource webResource = client.resource(BASE_URI);
// envoi une requête de type Get avec le champs Accept du header fixé à "application/xml"
String response = webResource.path("formationsrest").accept(MediaType.APPLICATION_JSON).get(String.class);
assertTrue(response.startsWith("[{"));
}
Via un même WebService on peut donc exposer des données sous deux formats différents, ici les plus couramment utilisés. On a donc peu de code mais aussi une factorisation des fonctions. En effet, le service exposant du JSON et celui exposant du XML n'en sont plus qu'un. C'est donc moins de code à écrire et donc à maintenir.
J'ajouterais que contrairement aux versions précédentes, il n'est plus nécessaire de configurer quoi que ce soit dans le web.xml de votre application. Les services ne requièrent plus de configurer une servlet (jersey ou resteasy dans le cas de jboss) dans le web.xml pour être exposés. Il suffit d'annoter un POJO ou un EJB.
CDI : L'injection de dépendances
Un des atout du framework Spring était l'injection de dépendances (en mode IOC). JEE avait un modèle valable mais pas aussi simple que celui de Spring. Le framework JBoss Seam a alors apporté un modèle d'injection similaire à celui de Spring et c'est celui-ci qui a été retenu pour la nouvelle spécification de JEE6 : CDI pour Context And Dependencies Injection. Ainsi pour injecter un composant il suffit d'ajouter @Inject à un attribut d'objet. @Named permettra de l'utiliser dans JSF via les EL. Exemple : Le composant injectable :
@Named("formationService")
public class FormationService {
//...
}
La seule configuration à mettre en place est un fichier beans.xml vide, dans le répertoire WEB-INF de votre application.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
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/beans_1_0.xsd">
</beans>
Injection :
@ManagedBean
@SessionScoped
public class FormationBean implements Serializable {
@Inject
FormationService formationService;
@ManagedBean et @SessionScoped seront expliqués dans la partie suivante. On voit ici que l'injection est désormais déconcertante de simplicité !
JSF2
JSF a longtemps été la bête noire des développeurs JEE. Un coût d'apprentissage énorme pour un framework IHM, l'intégration d'un cycle de vie pas vraiment adapté au protocole HTTP et la création de composants personnalisés très complexe sont les arguments qui ressortent tout le temps en défaveur de JSF. JSF2 améliore vraiment les choses. Le cycle de vie est plus simple à comprendre, le ticket d'entrée baisse fortement et enfin la création de composants personnalisés devient un vrai jeu. La productivité qui en découle est impressionnante. D'autre part, de plus en plus de bibliothèques de composants riches sont apparues (Richfaces, Icefaces, Primefaces...). Celles si sont à utiliser avec modération car elles génèrent beaucoup de javascript qui peut nuire aux performances mais permettent d'arriver rapidement à des applications internet riches et agréables à utiliser. Quoi qu'il arrive, JSF reste la couche IHM intégrée et recommandée par la spécification JEE 6. Bien entendu JEE6 ne vous impose pas d'utiliser JSF : vous êtes libre d'utiliser GWT, Flex ou autres. L'écriture simplifiée de services Rest (en JSON par exemple) peut être un atout à cela. D'autre part, Facelet avait permis de combler un certain nombre de lacunes de JSF, entre autre le templating et la possibilité de créer des assemblages de composants pour pouvoir les réutiliser. Facelet est désormais totalement intégré à JSF 2 : il ne s'agit plus d'une brique supplémentaire.
Voyons donc ces nouveautés dans des exemples extraits de notre PoC. Ici nous utiliserons le composant accordionPanel de la librairie Primefaces.
Nous allons avoir besoin d'un Backing Bean qui représente en quelque sorte le contrôleur dans un modèle MVC. JSF 2 simplifie aussi l'écriture de ces composants : il n'est plus nécessaire de les déclarer dans un fichier séparé, vous ne devez qu'exposez les méthodes voulues :
@ManagedBean
@SessionScoped
public class FormationBean implements Serializable {
Logger logger = LoggerFactory.getLogger("com.octo.octoformation.services.ejb.FormationBean");
@EJB
private FormationManager formationManager;
@Inject
FormationService formationService;
public List<Formation> getAllFormations() {
return formationManager.getAllFormations();
}
// ...
}
Grâce @ManagedBean on déclare notre composant comme backing bean JSF, pas besoin d'autre chose. @SessionScoped nous permet de spécifier que celui-ci sera en Scope Session. Pouvoir spécifier cela via des annotations est agréable et permet de gagner en lisibilité : en lisant la classe on connait tout de suite le cycle de vie de notre objet.
Et voici maintenant la JSF qui permet d'afficher notre liste de formations dans un composant riche de primefaces accordion Panel (sorte de panels en accordéon cliquables)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:p="http://primefaces.prime.com.tr/ui"
xmlns:formation="http://java.sun.com/jsf/composite/formation"
template="/layout/layout.xhtml">
<ui:define name="content">
<h:form>
<p:accordionPanel autoHeight="true" fillSpace="true">
<c:forEach items="#{formationBean.allFormations}" var="form">
<p:tab title="#{form.title}">
Date :
<h:outputText value="#{form.date}">
<f:convertDateTime pattern="dd-MM-yyyy" />
</h:outputText> <br />
<h:outputText value="#{form.description}" />
<br />
<br />
<h:outputLink value="/octoformation/editFormation.jsf">
Edit Formation
<f:param name="id" value="#{form.id}"></f:param>
</h:outputLink>
</p:tab>
</c:forEach>
</p:accordionPanel>
</h:form>
</ui:define>
</ui:composition>
Quelques explications s'imposent.
- Ici nous iterons grâce à c:forEach sur la liste de formations pour afficher un dépliant d'accordéon (p:tab) pour chaque formation.
- Ensuite grâce aux h:outputText nous affichons les données.
- Le composant f:convertDateTime nous permet de formater la date directement dans la vue.
- Enfin nous affichons un lien vers la page d'édition au quel nous passons en paramètres d'URL l'Id de la formation pour pouvoir la retrouver une fois sur la page voulue.
Vous aurez peut être souligné la déclaration d'un template dans l'en tête de la JSF, il s'agit en fait du template utilisé pour la mise en page qui est lui aussi une jsf. Les blocs ui:define permettent d'indiquer au template ou insérer les données que l'on indique. Voici le résultat obtenu :
Voici le code du template :
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.prime.com.tr/ui"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<f:view contentType="text/html">
<h:head>
<title>
Octo Formation
</title>
</h:head>
<h:body>
<div class="pageHeading">
<h1>Octo Formation</h1>
</div>
<div class="menuAndContent">
<div class="menuLeft" style="float:left;width:200px;">
<p:menu>
<p:submenu label="Formations">
<p:menuitem value="List" url="/index.jsf" />
<p:menuitem value="Create" url="/createFormation.jsf" />
<p:menuitem value="Delete" url="/deleteFormations.jsf" />
</p:submenu>
<p:submenu label="Formateurs">
<p:menuitem value="List" url="#" />
<p:menuitem value="Create" url="#" />
<p:menuitem value="Planning" url="#" />
</p:submenu>
<p:submenu label="Participants">
<p:menuitem value="List" url="/participants.jsf" />
<p:menuitem value="Create" url="/createParticipant.jsf" />
<p:menuitem value="Planning" url="#" />
</p:submenu>
</p:menu>
</div>
<div class="content" style="float:left;margin-left: 15px; width:800px;">
<ui:insert name="content" />
</div>
<div style="clear:both;"/>
</div>
</h:body>
</f:view>
</html>
Ici nous utilisons à nouveau un composant de la librairie primefaces pour déclarer notre menu. Ainsi nous pouvons déclarer notre mise en page grâce au balisage JSF/HTML et mettre en place notre style (vous excuserez le css inline il s'agit d'un exemple). ui:insert permet de définir ou seront inséré les éléments définis dans les pages entre les balise ui:define du même nom.
JSF 2 bien qu'encore verbeux (mais c'est le problème de tous les frameworks d'IHM et d'autant plus quand ils sont basés sur le XML) offre donc une syntaxe simplifiée et proche du HTML ce qui facilite l'apprentissage. Le code est en effet plus lisible et relativement simple à écrire. On a donc un framework d'IHM natif à JEE6 plutôt productif. Les performances de JSF 2 ont aussi été améliorées par rapport à JSF 1 et 1.2 ce qui est un point très important car JSF est souvent critiqué pour les problèmes de performances liés entre autre à son cycle de vie mal maitrisé par les développeurs. Le templating est désormais chose simple et nous allons maintenant voir un outil formidable de JSF 2, la création de composants personnalisées.
JSF 2 : Composants personnalisés
Nous risquons d'avoir besoin d'afficher le contenu d'une formation un peu partout dans notre application. Devoir copier coller le code de la JSF précédente serait quelque peu ennuyeux car générerait beaucoup de code à maintenir. On va donc écrire un composant réutilisable simplement. Ce cas d'usage est très courant, beaucoup d'application nécessite des "templates" pour leurs pages mais souhaiteraient aussi des templates pour leurs composants graphiques. Dans notre cas, il suffira alors de placer le fichier correspondant dans un repertoire "resources" à la racine de votre webapp et JSF2 le rendra disponible dans les déclarations de namespace.
Voici donc notre composant :
<composite:interface>
<composite:attribute name="date" />
<composite:attribute name="title" />
<composite:attribute name="description" />
<composite:attribute name="participants" />
</composite:interface>
<composite:implementation>
Title : <h:outputText value="#{cc.attrs.title}" />
<br />
Date :
<h:outputText value="#{cc.attrs.date}">
<f:convertDateTime pattern="dd-MM-yyyy" />
</h:outputText>
<br />
<br />
<h:outputText value="#{cc.attrs.description}" />
<br />
<br />
<h:outputLink value="/octoformation/editFormation.jsf">
Edit Formation
<f:param name="id" value="#{form.id}"></f:param>
</h:outputLink>
<h:dataTable value="#{cc.attrs.participants}" var="participant">
<h:column>
<h:outputText value="#{participant.trigram}" />
</h:column>
</h:dataTable>
</composite:implementation>
La partie composite:interface permet de déclarer les paramètres qui seront passés à notre composant. La partie composite:implementation décrit le corps du composants lui même et utilisant les paramètres précédents grâce à cc.attrs. Ainsi grâce à ce composant on va pouvoir réécrire notre première JSF de cette façon :
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:p="http://primefaces.prime.com.tr/ui"
xmlns:formation="http://java.sun.com/jsf/composite/formation"
template="/layout/layout.xhtml">
<ui:define name="content">
<h:form>
<p:accordionPanel autoHeight="true" fillSpace="true">
<c:forEach items="#{formationBean.allFormations}" var="form">
<p:tab title="#{form.title}">
<formation:formationViewer title="#{form.title}" date="#{form.date}" description="#{form.description}" participants="#{form.participants}"/>
</p:tab>
</c:forEach>
</p:accordionPanel>
</h:form>
</ui:define>
</ui:composition>
Il faut avouer que le code est alors beaucoup plus court et donc plus lisible. En définissant des composants de ce type dès le début vos JSF vont vous paraître beaucoup moins verbeuses qu'avant. Encore un point pour la productivité et la légèreté de JEE 6.
Events et asynchrone : arrêtons d'utiliser JMS à tout va !
JMS est aujourd'hui au coeur de beaucoup d'applications d'entreprises. Bien que conçu pour les traitements asynchrones et l'échange d'informations inter-applications, un certain nombre de projets l'utilisent aujourd'hui pour découpler les composants (ie. envoi de message entre objets pour éviter les dépendances). Hors JMS est loin d'être le roi de la légèreté et de la simplicité d'utilisation. JEE 6 apporte une solution à ce problème de découplage et d'asynchronisme : un système événementiel.
L'idée ici consiste à pouvoir lancer un événement depuis n'importe quel élément d'une application et que celui-ci soit rattrapé par un composant totalement indépendant. Un système équivalent était déjà présent dans JBoss Seam, il fait maintenant partie intégrante de JEE 6. Voyons un cas concret :
On commence par injecter le type d'événements qui nous intéresse, l'injection se basera sur le type générique pour déterminer les événements à injecter :
class FormationBean {
//...
@Inject @Any
Event<String> event;
//...
}
Puis dans une méthode on peut déclarer le lancement d'un événement, ici lors de la création d'une formation :
public void createFormation() {
formationManager.createFormation(formation);
FacesContext.getCurrentInstance().addMessage("message",
new FacesMessage("Created", " Formation created"));
event.fire("Formation created " + formation.getTitle());
}
Et enfin un autre objet, totalement indépendant est en écoute de l'événement et le traite :
public class EventConsumer {
Logger logger = LoggerFactory.getLogger("com.octo.octoformation.services.ejb.EventConsumer");
@Asynchronous
public void afterMyEvent(@Observes String event) {
logger.info("####" + event);
}
}
On le voit, ce système est très simple à mettre en place et laisse la porte ouverte à votre imagination pour son utilisation. On a alors ici un système événementiel léger et simple à écrire qui pourra être mis en oeuvre pour remplacer JMS (beaucoup plus complexe et lourd) dans de nombreux cas.
Un point à noter est l'annotation @Asynchronous (qui ne s'applique pas qu'aux événements d'ailleurs). Cette annotation permet de lancer la méthode de manière asynchrone. Cela peut être essentiel dans le cas de traitements lourds et ou/bloquant. En effet, l'appellant n'attendra pas la fin de la méthode pour continuer à exécuter son code.
Conclusion
Nous n'avons vu ici qu'une partie des nouvelles possibilités de JEE6 mais on voit bien que pour arriver au même résultat on écrit beaucoup moins de code (productivité et simplicité). De plus les performances sont en nette amélioration, faites vos tests : les résultats sont visibles. Tous les exemples précédents nous montrent donc clairement que JEE a pris la trajectoire de la légereté : on passe moins de temps sur du code technique ce qui permet d'en consacrer plus au code métier. La mise en place de Profils JEE permettant de choisir son panel de fonctionnalités est un des nouveaux atouts principaux de la plateforme car elle permet d'adapter l'outils à ses besoins, ce qui n'était pas le cas des version précédentes. Bien qu'encore jeune JEE 6 semble donc tout à fait viable pour des projets d'envergure. De plus n'oublions pas que JEE est le modèle de référence et le standard pour développer des applications d'entreprise en Java. On peut aussi ajouter que la tendance de la plateforme à s'orienter vers un modèle Convention Over Configuration réduit nettement les interminables fichiers XML de configuration et permet au code de gagner en lisibilité en portant la configuration via des annotations. Enfin les frameworks qui complétaient les lacunes de JEE 5 telles que JBoss Seam s'orientent désormais sous forme de module complémentaires à JEE 6. Seam propose par exemple un module de sécurité pour JEE 6 assez simple à utiliser et à mettre en place. On a donc une nette amélioration de la plateforme sous tous ses aspects : productivité, testabilité, performances et confort d'utilisation. La réduction des coûts qui en découle est donc relativement évidente à moyen terme.
Les exemples du PoC sont tous accessibles : https://github.com/mikrob/jee6poc
Pour aller plus loin : Conférences Devoxx 2010 : JEE 6 Reloaded, Creating Lightweight Application with nothing but Vanilla Java EE 6 Livre : JEE 6 et Glassfish 3 d'Antonio Goncalves