Académique Documents
Professionnel Documents
Culture Documents
Java EE 6 ™
et GlassFish 3 ™
Antonio Goncalves
Réseaux
et télécom
Programmation
Développement
web
Sécurité
Système
d’exploitation
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Java EE 6
et GlassFish 3
Antonio Goncalves
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Pearson Education France a apporté le plus grand soin à la réalisation de ce livre afin de
vous fournir une information complète et fiable. Cependant, Pearson Education France
n’assume de responsabilités, ni pour son utilisation, ni pour les contrefaçons de brevets
ou atteintes aux droits de tierces personnes qui pourraient résulter de cette utilisation.
Les exemples ou les programmes présents dans cet ouvrage sont fournis pour
illustrer les descriptions théoriques. Ils ne sont en aucun cas destinés à une utilisation
commerciale ou professionnelle.
Pearson Education France ne pourra en aucun cas être tenu pour responsable des
préjudices ou dommages de quelque nature que ce soit pouvant résulter de l’utilisation
de ces exemples ou programmes.
Tous les noms de produits ou marques cités dans ce livre sont des marques déposées par
leurs propriétaires respectifs.
Publié par Pearson Education France Titre original : Beginning Java™ EE6
47 bis, rue des Vinaigriers Platform with GlassFish™ 3
75010 PARIS
Tél. : 01 72 74 90 00
www.pearson.fr Traduction : Éric Jacoboni, avec la
contribution de Éric Hébert
Mise en pages : TyPAO
Aucune représentation ou reproduction, même partielle, autre que celles prévues à l’article L. 122-5 2˚ et 3˚ a)
du code de la propriété intellectuelle ne peut être faite sans l’autorisation expresse de Pearson Education France
ou, le cas échéant, sans le respect des modalités prévues à l’article L. 122-10 dudit code.
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording or by any information storage retrieval system,
without permission from Pearson Education, Inc.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Table des matières
Remerciements ................................................................................................................... XV
Introduction......................................................................................................................... 1
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
IV Java EE 6 et GlassFish 3
Derby 10.5........................................................................................................................ 39
GlassFish v3. ................................................................................................................... 42
Installation de GlassFish............................................................................................... 48
Résumé..................................................................................................................................... 50
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Table des matières V
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
VI Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Table des matières VII
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
VIII Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Table des matières IX
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
X Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Table des matières XI
WSDL................................................................................................................................ 467
SOAP................................................................................................................................. 468
Protocole de transport.................................................................................................... 468
XML. ................................................................................................................................. 468
Résumé de la spécification des services web . .................................................................. 468
Bref historique des services web................................................................................ 469
Spécifications Java EE. ................................................................................................. 469
Implémentation de référence....................................................................................... 471
Appel d’un service web ........................................................................................................ 471
JAXB : Java Architecture for XML Binding..................................................................... 473
Liaison............................................................................................................................... 476
Annotations...................................................................................................................... 478
La partie immergée de l’iceberg.......................................................................................... 481
WSDL................................................................................................................................ 481
SOAP................................................................................................................................. 484
JAX-WS : Java API for XML-Based Web Services......................................................... 485
Le modèle JAX-WS....................................................................................................... 486
Appel d’un service web ............................................................................................... 494
Récapitulatif............................................................................................................................ 496
La classe CreditCard..................................................................................................... 497
Le service web CardValidator..................................................................................... 497
Compilation et assemblage avec Maven................................................................... 498
Déploiement dans GlassFish. ...................................................................................... 499
Le consommateur du service web ............................................................................. 501
Création des artefacts du consommateur et assemblage avec Maven............... 502
Exécution de la classe Main. ....................................................................................... 505
Résumé..................................................................................................................................... 505
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
XII Java EE 6 et GlassFish 3
Index................................................................................................................................................... 547
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Avant-propos
Bien que Java EE 5 soit unanimement considérée comme la version la plus impor-
tante de Java EE, Java EE 6 fournit de nombreuses fonctionnalités supplémen-
taires. La création d’applications d’entreprise est, en effet, encore améliorée grâce à
EJB 3.1, JPA 2.0, JAX-RS et JSF 2.0. La plate-forme Java EE est désormais arrivée
à un niveau de maturité permettant de faire côtoyer légèreté et puissance.
Vous pourriez évidemment utiliser votre navigateur favori pour parcourir les nom-
breux blogs, wikis et articles consacrés à Java EE 6, mais je vous conseille plutôt
de commencer par lire ce livre : il est concis, pragmatique et il distille l’expérience
complète de l’auteur sur le sujet.
Cet ouvrage utilise GlassFish comme serveur d’applications sous-jacent pour plu-
sieurs raisons : GlassFish V3 est l’implémentation de référence et est donc en phase
avec Java EE 6. Par ailleurs, l’expérience acquise en utilisant une implémentation
de référence et les technologies les plus récentes est adaptable au monde de l’entre-
prise. Ce que vous apprendrez sera directement exploitable en production.
Antonio Goncalves est un exemple rare de développeur où se mêlent passion pour
Java et expérience professionnelle de Java EE. Son travail de consultant, combiné
à sa participation au Java User Group de Paris ainsi, bien sûr, que son rôle dans
les différents groupes d’experts Java EE 6 en font l’auteur idéal de Java EE 6 et
GlassFish 3.
Après la lecture de ce livre, vous aurez compris que la plus grande richesse de Java
EE n’est pas la somme de ses fonctionnalités mais la communauté qui l'a créé, ainsi
que sa nature même de standard qui vous permet de choisir ou de modifier l'implé-
mentation en fonction de vos besoins. La liberté n'est pas simplement représentée
par l'open-source, mais également par les standards ouverts.
Alexis Moussine-Pouchkine
Équipe GlassFish, Sun Microsystems.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
À propos de l’auteur
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Remerciements
Écrire un livre sur une nouvelle spécification comme Java EE 6 est une tâche énorme
qui mobilise les talents de plusieurs personnes. Avant tout, je remercie Steve Anglin,
d’Apress, de m’avoir donné l’opportunité de participer à la collection "Beginning
Series" d’Apress, que j’apprécie beaucoup en tant que lecteur. Tout au long de mon
travail de rédaction, j’ai été en contact avec Candace English et Tom Welsh, qui
ont relu l’ouvrage et m’ont rassuré lorsque j’avais des doutes sur le fait de pouvoir
le terminer en temps voulu. Je remercie également les relecteurs techniques, Jim
Farley et Sumit Pal, qui ont fait un excellent travail en me suggérant de nombreuses
améliorations. Enfin, j’ai admiré le travail remarquable d’Ami Knox, qui a produit la
dernière version de l’édition. Merci également à Alexis Midon et Sebastien Auvray,
les coauteurs du Chapitre 15, consacré aux services web REST : Alex est un infor-
maticien passionné, fan de REST, et Sébastien est un développeur talentueux qui
utilise REST de façon pragmatique. Merci à eux pour leur aide précieuse.
Je remercie tout spécialement Alexis Moussine-Pouchkine, qui a gentiment accepté
d’écrire la préface et la section sur GlassFish. Il m’a également été d’un grand
secours pour contacter les bonnes personnes pour des sujets particuliers : je pense
à Ryan Lubke pour JSF 2.0, à Paul Sandoz pour JAX-RS 1.1 et à François Orsini
pour Derby.
Merci à Damien Gouyette pour son aide sur JSF 2.0. Damien a une grande expé-
rience en développement web et en JSF en particulier (au fait, merci à celui qui gère
le dépôt SVN). Merci également à Arnaud Héritier pour avoir écrit la section sur
Maven et pour m’avoir aidé à résoudre certains problèmes avec Maven, ainsi qu’à
Nicolas de Loof pour sa relecture technique sur le sujet.
Sébastien Moreno m’a aidé pour JUnit et a relu le manuscrit complet avec David
Dewalle et Pascal Graffion – tout cela dans un délai imparti très serré. Je les remer-
cie beaucoup pour leur travail.
Je remercie les correcteurs, Denise Green et Stefano Costa, pour avoir tenté de donner
une touche shakespearienne à ce livre.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
XVI Java EE 6 et GlassFish 3
Les diagrammes de cet ouvrage ont été réalisés avec l’extension Visual Paradigm
d’IntelliJ IDEA. Je remercie d’ailleurs Visual Paradigm et JetBrains pour m’avoir
offert une licence gratuite de leurs excellents produits.
Je n’aurais pas pu écrire ce livre sans l’aide de la communauté Java : tous ceux qui
ont pris sur leur temps libre pour m’aider dans leurs courriers électroniques, dans les
listes de diffusion ou sur les forums de discussion. La liste de diffusion des groupes
d’experts JCP est, évidemment, la première qui me vient à l’esprit : merci à Roberto
Chinnici, Bill Shannon, Kenneth Saks, Linda DeMichiel, Michael Keith, Reza Rah-
man, Adam Bien, etc.
Un gros bisou à ma fille, Éloïse : les interruptions dues à son espièglerie m’ont aidé
alors que je passais mes week-ends à écrire.
Un livre est le produit d’un nombre incalculable de personnes que je voudrais remer-
cier pour leur contribution, que ce soit pour un avis technique, une bière dans un bar,
un extrait de code... Merci donc à Jean-Louis Dewez, Frédéric Drouet, les geeks du
JUG de Paris, T. Express, les membres d’OSSGTP, les Cast Codeurs, FIP, Marion,
Les Connards, Vitalizen, La Fontaine, Ago, Laure, La Grille, les Eeckman, Yaya,
Rita, os Navalhas, La Commune Libre d’Aligre, etc.
Merci à tous !
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Introduction
Aujourd’hui, les applications doivent accéder à des données, appliquer une logique
métier, ajouter des couches de présentation et communiquer avec des systèmes
externes. Les entreprises tentent de réaliser toutes ces opérations à moindre coût, en
se servant de technologies standard et robustes supportant des charges importantes.
Si vous êtes dans cette situation, ce livre est fait pour vous.
Java Enterprise Edition est apparu à la fin des années 1990 et a doté le langage
Java d’une plate-forme logicielle fiable, prête pour les besoins des entreprises. Cri-
tiqué à chaque nouvelle version, mal compris ou mal utilisé, en compétition avec
les frameworks open-source, J2EE a été considéré comme une technologie lourde.
Java EE a bénéficié de toutes ces critiques pour s’améliorer : son but actuel est la
simplicité.
Si vous faites partie de ceux qui pensent "que les EJB sont nuls et qu’il faut s’en
débarrasser", lisez ce livre et vous changerez d’avis. Les EJB (Enterprise Java
Beans), comme toutes les technologies de Java EE, sont des composants puissants.
Si, au contraire, vous êtes un fan de Java EE, cet ouvrage vous montrera que la
plate-forme a trouvé son équilibre grâce à une simplification du développement,
à de nouvelles spécifications, à un modèle de composants EJB plus légers, à des
profils et à l’élagage. Si vous débutez avec Java EE, ce livre vous guidera dans votre
apprentissage des spécifications les plus importantes au moyen d’exemples et de
diagrammes très simples à comprendre.
Les standards ouverts sont l’une des forces principales de Java EE. Plus que jamais,
les applications écrites avec JPA, EJB, JSF, JMS, les services web SOAP ou REST
sont portables entre les différents serveurs d’applications. Comme vous pourrez le
constater, la plupart des implémentations de référence de Java EE 6 (GlassFish,
EclipseLink, Mojarra, OpenMQ, Metro et Jersey) sont distribuées sous les termes
d’une licence open-source.
Ce livre explore les innovations de cette nouvelle version et examine les différentes
spécifications et la façon de les assembler pour développer des applications. Java
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
2 Java EE 6 et GlassFish 3
EE 6 est formé d’environ 30 spécifications et c’est une version importante pour la
couche métier (EJB 3.1, JPA 2.0), la couche web (Servlet 3.0, JSF 2.0) et l’interopé-
rabilité (services web SOAP et REST). Ce livre présente une grande partie de toutes
ces spécifications, utilise le JDK 1.6 et certains patrons de conception bien connus,
ainsi que le serveur d’applications GlassFish, la base de données Derby, JUnit et
Maven. Il est abondamment illustré de diagrammes UML, de code Java et de copies
d’écran.
Structure du livre
Ce livre ne se veut pas une référence exhaustive de Java EE 6. Il s’intéresse essen-
tiellement aux spécifications les plus importantes et aux nouvelles fonctionnalités de
cette version. Sa structure respecte le découpage de l’architecture d’une application.
Présentation
Chapitre 10 : JavaServer Faces
Chapitre 11 : Pages et composants
Chapitre 12 : Traitements et navigation
Logique métier
Chapitre 6 : Enterprise Java Beans
Interopérabilité
Chapitre 7 : Beans de session et service timer
Chapitre 8 : Méthodes de rappel et intercepteurs Chapitre 13 : Envoi de messages
Chapitre 9 : Transactions et sécurité Chapitre 14 : Services web SOAP
Chapitre 15 : Services web REST
Persistance
Chapitre 2 : Persistance en Java
Chapitre 3 : ORM : ObjectRelational Mapping
Chapitre 4 : Gestion des objets persistants
Chapitre 5 : Méthodes de rappel et écouteurs
Le Chapitre 1 présente brièvement l’essentiel de Java EE 6 et les outils que nous
utiliserons dans ce livre (JDK, Maven, JUnit, Derby et GlassFish).
La couche de persistance est décrite du Chapitre 2 au Chapitre 5 et s’intéresse prin-
cipalement à JPA 2.0. Après un survol général et quelques exemples au Chapitre 2,
le Chapitre 3 s’intéresse à l’association objet-relationnel (ORM). Le Chapitre 4
montre comment gérer et interroger les entités, tandis que le 5 présente leur cycle de
vie, les méthodes de rappel et les écouteurs.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Introduction 3
Pour développer une couche métier transactionnelle avec Java EE 6, on utilise natu-
rellement les EJB, qui seront décrits du Chapitre 6 au Chapitre 9. Le Chapitre 6
passe en revue la spécification, son histoire et donne des exemples de code, tandis
que le 7 s’intéresse plus particulièrement aux beans de session et à leur modèle de
programmation, ainsi qu’au nouveau service timer. Le Chapitre 8 est consacré au
cycle de vie des EJB et aux intercepteurs, tandis que le 9 explique tout ce qui a trait
aux transactions et à la sécurité.
Du Chapitre 10 au Chapitre 12, vous apprendrez à développer une couche de pré-
sentation avec JSF 2.0. Après une présentation de la spécification au Chapitre 10,
le Chapitre 11 s’intéresse à la construction d’une page web avec JSF et Facelets.
Le 12, quant à lui, explique comment interagir avec un "backend" EJB et comment
naviguer entre les pages.
Enfin, les derniers chapitres vous présenteront différents moyens d’échanger des
informations avec d’autres systèmes. Le Chapitre 13 montre comment échanger des
messages asynchrones avec JMS (Java Message Service) et les MDB (Message-Dri-
ven Beans) ; le Chapitre 14 s’intéresse aux services web SOAP tandis que le 15 est
consacré aux services web REST.
Les exemples de ce livre sont conçus pour être compilés avec le JDK 1.6, déployés
sur le serveur d’applications GlassFish V3 et stockés dans la base de données
Derby. Le Chapitre 1 explique comment installer tous ces logiciels et chaque cha-
pitre montre comment compiler, déployer, exécuter et tester les composants selon la
technologie employée. Tous les codes des exemples ont été testés sur la plate-forme
Windows, mais ni sur Linux ni sur OS X. Les codes sources de ces exemples sont
disponibles sur le site des éditions Pearson (www.pearson.fr), sur la page consacrée
à cet ouvrage.
Contacter l’auteur
Pour toute question sur le contenu de ce livre, sur le code ou tout autre sujet, contac-
tez-moi à l’adresse antonio.goncalves@gmail.com. Vous pouvez également visiter
mon site web, http://www. antoniogoncalves.org.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
1
Tour d’horizon de Java EE 6
De nos jours, les entreprises évoluent dans une compétition à l’échelle mondiale.
Elles ont besoin pour résoudre leurs besoins métiers d’applications qui deviennent
de plus en plus complexes. À notre époque de mondialisation, les sociétés sont pré-
sentes sur les différents continents, fonctionnent 24 heures sur 24, 7 jours sur 7 via
Internet et dans de nombreux pays ; leurs systèmes doivent être internationalisés
et savoir traiter plusieurs monnaies et des fuseaux horaires différents – tout ceci
en réduisant les coûts, en améliorant le temps de réponse des services, en stockant
les données sur des supports fiables et sécurisés et en offrant différentes interfaces
graphiques à leurs clients, employés et fournisseurs.
La plupart des sociétés doivent combiner ces défis innovants avec leur système
d’information existant tout en développant en même temps des applications B2B
(business to business) pour communiquer avec leurs partenaires. Il n’est pas rare
non plus qu’une société doive coordonner des données stockées à divers endroits,
traitées par plusieurs langages de programmation et acheminées via des protocoles
différents. Évidemment, ceci ne doit pas faire perdre d’argent, ce qui signifie qu’il
faut empêcher les pannes du système et toujours rester disponible, sécurisé et évo-
lutif. Les applications d’entreprise doivent faire face aux modifications et à la com-
plexité tout en étant robustes. C’est précisément pour relever ces défis qu’a été créé
Java Enterprise Edition (Java EE).
La première version de Java EE (connue sous le nom de J2EE) se concentrait sur les
problèmes que devaient résoudre les sociétés en 1999 : les composants distribués.
Depuis, les logiciels ont dû s’adapter à de nouvelles solutions techniques comme les
services web SOAP ou REST. La plate-forme a donc évolué pour tenir compte de
ces besoins en proposant plusieurs mécanismes standard sous forme de spécifica-
tions. Au cours des années, Java EE a évolué pour devenir plus riche, plus léger, plus
simple d’utilisation et plus portable.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
6 Java EE 6 et GlassFish 3
Ce chapitre fait un tour d’horizon de Java EE. Après une présentation rapide de son
architecture interne, il présentera les nouveautés de Java EE 6. La seconde partie du
chapitre est consacrée à la mise en place de votre environnement de développement
pour que vous puissiez vous-même mettre en œuvre les extraits de code présentés
dans ce livre.
Présentation de Java EE
Lorsque l’on veut traiter une collection d’objets, on ne commence pas par dévelop-
per sa propre table de hachage : on utilise l’API des collections. De même, lorsque
l’on a besoin d’une application transactionnelle, sécurisée, interopérable et distri-
buée, on ne développe pas des API de bas niveau : on utilise la version entreprise de
Java (Java EE). Tout comme l’édition standard de Java (Java SE, Standard Edition)
permet de traiter les collections, Java EE fournit des moyens standard pour traiter les
transactions via Java Transaction API (JTA), les messages via Java Message Service
(JMS) ou la persistance via Java Persistence API (JPA). Java EE est un ensemble de
spécifications pour les applications d’entreprise ; il peut donc être considéré comme
une extension de Java SE destinée à faciliter le développement d’applications
distribuées, robustes, puissantes et à haute disponibilité.
Java EE 6 est une version importante. Non seulement elle marche dans les pas de
Java EE 5 pour fournir un modèle de développement simplifié, mais elle ajoute
également de nouvelles spécifications et apporte des profils et de l’élagage pour
alléger ce modèle. La sortie de Java EE 6 coïncide avec le dixième anniversaire de la
plate-forme entreprise : elle combine donc les avantages du langage Java avec l’ex-
périence accumulée au cours de ces dix dernières années. En outre, elle tire profit
du dynamisme de la communauté open-source et de la rigueur du JCP. Désormais,
Java EE est une plate-forme bien documentée, avec des développeurs expérimentés,
une communauté d’utilisateurs importante et de nombreuses applications déployées
sur les serveurs d’entreprises. C’est un ensemble d’API permettant de construire des
applications multi-tier reposant sur des composants logiciels standard ; ces compo-
sants sont déployés dans différents conteneurs offrant un ensemble de services.
Un peu d’histoire
Dix ans permettent de se faire une idée de l’évolution de Java EE (voir Figure 1.1),
qui s’est d’abord appelé J2EE. La première version, J2EE 1.2, a été initialement déve-
loppée par Sun et est apparue en 1999 sous la forme d’une spécification contenant dix
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 7
Servlet Annotations
JSP Injection
Project JPE Profil web
EJB Services web JPA
JMS EJB CMP Gestion WS-*
RMI/IIOP JCA Déploiement JSF
Mai 1998 Dec 1999 Sept 2001 Nov 2003 Mai 2006 Q3 2009
10 specs 13 specs 20 specs 23 specs 28 specs
INFO
CORBA est apparu vers 1988, précisément parce que les systèmes d’entreprise commençaient
à être distribués (Tuxedo, CICS, par exemple). Les EJB puis J2EE lui ont emboîté le pas, mais dix
ans plus tard. Lorsque J2EE est apparu pour la première fois, CORBA avait déjà gagné son sta-
tut industriel, mais les sociétés commençaient à utiliser des solutions "tout-Java" et l’approche
neutre de CORBA vis-à-vis des langages de programmation est donc devenu redondante.
À partir de J2EE 1.3, la spécification a été développée par le Java Community Pro-
cess (JCP) en réponse à la JSR 58. Le support des beans entité est devenu obliga-
toire et les EJB ont introduit les descripteurs de déploiement XML pour stocker les
métadonnées (qui étaient jusqu’alors sérialisées dans un fichier avec EJB 1.0). Cette
version a également réglé le problème du surcoût induit par le passage des para-
mètres par valeur avec les interfaces distantes en introduisant les interfaces locales
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
8 Java EE 6 et GlassFish 3
et en passant les paramètres par référence. J2EE Connector Architecture (JCA) a été
ajoutée afin de connecter J2EE aux EIS (Enterprise Information Systems).
INFO
JCP est une organisation ouverte créée en 1998 afin de définir les évolutions de la plate-
forme Java. Lorsqu’il identifie le besoin d’un nouveau composant ou d’une nouvelle API,
l’initiateur (appelé "leader de la spécification") crée une JSR et forme un groupe d’experts.
Ce groupe, formé de représentants de diverses sociétés ou organisations, ainsi que de per-
sonnes privées, est responsable du développement de la JSR et doit délivrer : 1) une spé-
cification qui explique les détails et définit les bases de la JSR ; 2) une implémentation de
référence (RI, Reference Implementation) ; et 3) un kit de test de compatibilité (TCK, Tech-
nology Compatibility Kit), c’est-à-dire un ensemble de tests que devront satisfaire toutes les
implémentations avant de pouvoir prétendre qu’elles sont conformes à la spécification. Une
fois qu’elle a été acceptée par le comité exécutif (EC, Executive Committee), la spécification
est fournie à la communauté pour être implémentée. En réalité, Java EE est une JSR qui
chapeaute d’autres JSR et c’est la raison pour laquelle on parle souvent de JSR umbrella.
J2EE 1.4 (JSR 151), qui est sorti en 2003, a ajouté vingt spécifications et le support
des services web. EJB 2.1 permettait ainsi d’invoquer des beans de session à partir
de SOAP/HTTP. Un service de temporisation a également été ajouté pour permettre
aux EJB d’être appelés à des moments précis ou à intervalles donnés. Cette version
fournissait un meilleur support pour l’assemblage et le déploiement des applications.
Bien que ses supporters lui aient prédit un grand avenir, toutes les promesses de
J2EE ne se sont pas réalisées. Les systèmes créés grâce à lui étaient trop complexes
et les temps de développement, souvent sans commune mesure avec les exigences
de l’utilisateur. J2EE était donc considéré comme un modèle lourd, difficile à tester,
à déployer et à exécuter. C’est à cette époque que des frameworks comme Struts,
Spring ou Hibernate ont commencé à émerger et à proposer une nouvelle approche
dans le développement des applications. Heureusement, Java EE 5 (JSR 244) fit son
apparition au deuxième trimestre de 2006 et améliora considérablement la situa-
tion. Il s’inspirait des frameworks open-source en revenant à un bon vieil objet Java
(POJO, Plain Old Java Object). Les métadonnées pouvaient désormais être définies
grâce à des annotations et les descripteurs XML devenaient facultatifs. Du point de
vue des développeurs, EJB 3 et la nouvelle spécification JPA représentèrent donc
plus un bond prodigieux qu’une évolution de la plate-forme. JSF (JavaServer Faces)
fit également son apparition comme framework standard de la couche présentation
et JAX-WS 2.0 remplaça JAX-RPC comme API pour les services web SOAP.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 9
Aujourd’hui, Java EE 6 (JSR 316) poursuit sur cette voie en appliquant les concepts
d’annotation, de programmation POJO et la politique "convention plutôt que confi-
guration" à toute la plate-forme, y compris la couche web. Il fournit également un
grand nombre d’innovations comme la toute nouvelle API JAX-RS 1.1, simplifie
des API matures comme EJB 3.1 et en enrichit d’autres comme JPA 2.0 ou le service
de temporisation. Les thèmes principaux de Java EE 6 sont la portabilité (en standar-
disant le nommage JNDI, par exemple), la dépréciation de certaines spécifications
(via l’élagage) et la création de sous-ensembles de la plate-forme au moyen de pro-
fils. Dans ce livre, nous présenterons toutes ces améliorations et montrerons com-
ment Java Enterprise Edition est devenu à la fois bien plus simple et bien plus riche.
Standards
Java EE repose sur des standards. C’est une spécification centrale qui chapeaute un cer-
tain nombre d’autres JSR. Vous pourriez vous demander pourquoi les standards sont si
importants puisque certains des frameworks Java les plus utilisés (Struts, Spring, etc.)
ne sont pas standardisés. La raison est que les standards, depuis l’aube des temps,
facilitent la communication et les échanges – des exemples de standards bien connus
concernent les langues, la monnaie, le temps, les outils, les trains, les unités de mesure,
l’électricité, le téléphone, les protocoles réseau et les langages de programmation.
Quand Java est apparu, le développement d’une application web ou d’entreprise passait
généralement par l’utilisation d’outils propriétaires : on créait son propre framework
ou l’on s’enfermait en choisissant un framework commercial propriétaire. Puis vint
l’époque des frameworks open-source, qui ne reposent pas toujours sur des stan-
dards ouverts. Vous pouvez donc utiliser une solution open-source qui vous enferme
dans une seule implémentation ou en choisir une qui implémente les standards et qui
sera alors portable. Java EE fournit des standards ouverts implémentés par plusieurs
frameworks commerciaux (WebLogic, Websphere, MQSeries, etc.) ou open-source
(GlassFish, JBoss, Hibernate, Open JPA, Jersey, etc.) pour gérer les transactions, la
sécurité, les objets à état, la persistance des objets, etc. Aujourd’hui plus que jamais
dans l’histoire de Java EE, votre application peut être déployée sur n’importe quel
serveur d’applications conforme, moyennant quelques modifications mineures.
Architecture
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
10 Java EE 6 et GlassFish 3
tains services aux composants qu’ils hébergent : gestion du cycle de vie, injection de
dépendances, etc. Les composants doivent respecter des contrats bien définis pour
communiquer avec l’infrastructure de Java EE et avec les autres composants, et ils
doivent être assemblés en respectant un certain standard (fichiers archives) avant
d’être déployés. Java EE étant un surensemble de la plate-forme Java SE, les API de
cette dernière peuvent donc être utilisées par n’importe quel composant de Java EE.
La Figure 1.2 présente les relations logiques qui relient les conteneurs. Les flèches
représentent les protocoles utilisés par un conteneur pour accéder à un autre. Le
conteneur web, par exemple, héberge les servlets qui peuvent accéder aux EJB via
le protocole RMI-IIOP.
Figure 1.2
<<executionEnvironment>> <<executionEnvironment>>
Conteneurs standard Conteneur web Conteneur EJB
SSL
HTTP RMI / IIOP
HTTP SSL
RMI / IIOP
<<executionEnvironment>> Conteneur
Conteneur Applets d'applications client
<<component>> <<component>>
Applet Application
Composants
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 11
Conteneurs
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
12 Java EE 6 et GlassFish 3
Le conteneur web (ou conteneur de servlets) fournit les services sous-jacents per-
mettant de gérer et d’exécuter les composants web (servlets, JSP, filtres, écouteurs,
pages JSF et services web). Il est responsable de l’instanciation, de l’initialisation et
de l’appel des servlets et du support des protocoles HTTP et HTTPS. C’est lui qu’on
utilise pour servir les pages web aux navigateurs des clients.
Le conteneur EJB est responsable de la gestion de l’exécution des beans entreprise
contenant la couche métier de votre application Java EE. Il crée de nouvelles ins-
tances des EJB, gère leur cycle de vie et fournit des services comme les transactions,
la sécurité, la concurrence, la distribution, le nommage ou les appels asynchrones.
Services
Services web
Services web
Connecteurs
Connecteurs
JAX-RPC
JAX-RPC
JavaMail
JavaMail
JAX-WS
JAX-WS
JAX-RS
JASP C
JAX-RS
Gestion
JASP C
Gestion
JACC
JAXR
JACC
JAXR
JSTL
JMS
JMS
JSF
JPA
JTA
JPA
JTA
SAAJ SAAJ
Java SE Java SE
Applets JDBC
Services web
JAX-RPC
JAX-WS
Applet
Gestion
JDBC
JAXR
JMS
JPA
Base de
Java SE données
SAAJ
Java SE
Figure 1.3
Services fournis par les conteneurs.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 13
Les conteneurs web et EJB, par exemple, fournissent des connecteurs pour accé-
der à EIS, alors que le conteneur d’applet ou ACC ne le font pas. Java EE offre les
services suivants :
■■ JTA. Ce service offre une API de démarcation des transactions utilisée par le
conteneur et l’application. Il sert également d’interface entre le gestionnaire de
transactions et un gestionnaire de ressource au niveau SPI (Service Provider
Interface).
■■ JPA. Fournit l’API pour la correspondance modèle objet-modèle relationnel
(ORM, Object-Relational Mapping). JPQL (Java Persistence Query Language)
permet d’interroger les objets stockés dans la base de données sous-jacente.
■■ JMS. Permet aux composants de communiquer de façon asynchrone par passage
de messages. Ce service fournit un système d’envoi de message fiable, point à
point (P2P) ainsi que le modèle publication-abonnement.
■■ JNDI (Java Naming and Directory Interface). Cette API, incluse dans Java SE,
permet d’accéder aux systèmes d’annuaires et de nommage. Votre application
peut l’utiliser pour associer des noms à des objets puis les retrouver dans un
annuaire. Avec elle, vous pouvez parcourir des sources de données, des fabriques
JMS, des EJB et d’autres ressources. Omniprésente dans le code jusqu’à J2EE 1.4,
JNDI est désormais utilisée de façon plus transparente grâce à l’injection.
■■ JavaMail. Le but de cette API consiste à simplifier l’envoi de courrier élec
tronique par les applications.
■■ JAF (JavaBeans Activation Framework). Cette API, incluse dans Java SE, est
un framework pour la gestion des données des différents types MIME. Elle est
utilisée par JavaMail.
■■ XML. La plupart des composants Java EE peuvent éventuellement être déployés
à l’aide de descripteurs de déploiements XML, et les applications doivent sou-
vent manipuler des documents XML. JAXP (Java API for XML Processing) per-
met d’analyser des documents XML à l’aide des API SAX et DOM, ainsi que
pour XSLT. StAX (Streaming API for XML) est une API d’analyse XML par flux.
■■ JCA. Les connecteurs permettent d’accéder à EIS à partir d’un composant
Java EE, que ce soient des bases de données, des mainframes ou des programmes
ERP (Enterprise Resource Planning).
■■ Sécurité. JAAS (Java Authentication and Authorization Service) fournit les ser-
vices permettant d’authentifier les utilisateurs et d’assurer le contrôle de leurs
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
14 Java EE 6 et GlassFish 3
droits d’accès. JACC (Java Authorization Service Provider Contract for Containers)
définit un contrat entre un serveur d’application Java EE et un fournisseur de
service d’autorisation, ce qui permet de greffer des fournisseurs de services d’au-
torisation personnalisés dans n’importe quel produit Java EE.
■■ Services web. Java EE reconnaît les services web SOAP et REST. JAX-WS
(Java API for XML Web Services) remplace JAX-RPC (Java API for XML-based
RPC) et fournit le support des protocoles SOAP et HTTP. JAX-RS (Java API for
RESTful Web Services) fournit le support des services web reposant sur REST.
■■ Gestion. Java EE définit des API pour gérer les conteneurs et les serveurs à
l’aide d’un bean d’entreprise spécialisé. L’API JMX (Java Management Exten-
sions) fournit également une aide à la gestion.
■■ Déploiement. La spécification de déploiement Java EE définit un contrat entre
les outils de déploiement et les produits Java EE afin de standardiser le déploie-
ment des applications.
Protocoles réseau
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 15
Paquetages
Pour être déployés dans un conteneur, les composants doivent d’abord être empa-
quetés dans une archive au format standard. Java SE définit les fichiers jar (Java
Archive), qui permettent de regrouper plusieurs fichiers (classes Java, descripteurs
de déploiement ou bibliothèques externes) dans un seul fichier compressé (reposant
sur le format ZIP). Java EE définit différents types de modules ayant leur propre
format de paquetage reposant sur ce format jar commun.
Un module d’application client contient des classes Java et d’autres fichiers de res-
sources empaquetés dans un fichier jar. Ce fichier peut être exécuté dans un environ-
nement Java SE ou dans un conteneur d’application client. Comme tous les autres
formats d’archive, le fichier jar contient éventuellement un répertoire META-INF décri-
vant l’archive. Le fichier META-INF/MANIFEST.MF, notamment, permet de décrire les
données du paquetage. S’il est déployé dans un ACC, le descripteur de déploiement
peut éventuellement se trouver dans le fichier META-INF/application-client.xml.
Un module EJB contient un ou plusieurs beans de session et/ou des beans pilotés par des
messages (MDB, Message Driven Bean) assemblés dans un fichier jar (souvent appelé
fichier jar EJB). Ce fichier peut éventuellement contenir un descripteur de déploiement
META-INF/ejb-jar.xml et ne peut être déployé que dans un conteneur EJB.
Un module d’application web contient des servlets, des JSP, des pages JSF, des
services web ainsi que tout autre fichier web associé (pages HTML et XHTML,
feuilles de style CSS, scripts JavaScript, images, vidéos, etc.). Depuis Java EE 6,
un module d’application web peut également contenir des beans EJB Lite (un sous-
ensemble de l’API des EJB que nous décrirons au Chapitre 6). Tous ces composants
sont assemblés dans un fichier jar portant l’extension .war (souvent désigné sous le
terme de fichier war, ou Web Archive). L’éventuel descripteur de déploiement est
défini dans le fichier WEB-INF/web.xml. Si le fichier war contient des beans EJB Lite,
l’archive peut également contenir un descripteur de déploiement décrit par WEB-
INF/ejb-jar.xml. Les fichiers .class Java se trouvent dans le répertoire WEB-INF/
classes et les fichiers jar dépendants sont dans le répertoire WEB-INF/lib.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
16 Java EE 6 et GlassFish 3
Spécifications de Java EE 6
Java EE 6 est une spécification centrale définie par la JSR 316 qui contient vingt-
huit autres spécifications. Un serveur d’application souhaitant être compatible avec
Java EE 6 doit donc implémenter toutes ces spécifications. Les Tableaux 1.1 à 1.5
énumèrent toutes leurs versions et leurs numéros de JSR. Certaines spécifications
ont été élaguées, ce qui signifie qu’elles seront peut-être supprimées de Java EE 7.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 17
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
18 Java EE 6 et GlassFish 3
Maintenant que vous connaissez l’architecture interne de Java EE, vous pourriez
vous demander ce qu’apporte Java EE 6. Le but principal de cette version est de
poursuivre la simplification de la programmation introduite par Java EE 5. Avec
Java EE 5, les EJB, les entités persistantes et les services web ont été remodelés afin
d’utiliser une approche plus orientée objet (via des classes Java implémentant des
interfaces Java) et pour se servir des annotations pour définir les métadonnées (les
descripteurs de déploiement XML sont donc devenus facultatifs). Java EE 6 pour-
suit dans cette voie et applique les mêmes paradigmes à la couche web. Aujourd’hui,
un bean géré par JSF est une classe Java annotée avec un descripteur XML.
Java EE 6 s’applique également à simplifier la plate-forme à l’aide de profils et en
supprimant certaines technologies obsolètes. Il ajoute des fonctionnalités supplé-
mentaires aux spécifications existantes (en standardisant, par exemple, les beans de
session singletons) et en ajoute de nouvelles (comme JAX-RS). Plus que jamais,
les applications Java EE 6 sont portables entre les conteneurs grâce aux noms JNDI
standard et à un conteneur EJB intégré.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 19
Plus léger
Élagage
La première version de Java EE est apparue en 1999 et, depuis, chaque nouvelle ver-
sion a ajouté son lot de nouvelles spécifications (comme on l’a vu à la Figure 1.1).
Cette inflation est devenue un problème en termes de taille, d’implémentation et
d’apprentissage. Certaines fonctionnalités n’étaient pas très bien supportées ou peu
déployées parce qu’elles étaient techniquement dépassées ou que d’autres solutions
avaient vu le jour entre-temps. Le groupe d’experts a donc décidé de proposer la
suppression de certaines fonctionnalités via l’élagage (pruning).
Java EE 6 a adopté ce mécanisme d’élagage (également appelé "marquage pour
suppression"), suivant en cela le groupe Java SE. Ce mécanisme consiste à proposer
une liste de fonctionnalités qui pourraient ne plus être reconduites dans Java EE 7.
Aucun de ces éléments n’est supprimé dans la version courante. Certaines fonction-
nalités seront remplacées par des spécifications plus récentes (les beans entités, par
exemple, sont remplacés par JPA) et d’autres quitteront simplement la spécifica-
tion Java EE 7 pour continuer d’évoluer comme des JSR indépendantes (les JSR 88
et 77, par exemple). Ceci dit, les fonctionnalités élaguées suivantes sont toujours
présentes dans Java EE 6 :
■■ EJB 2.x Entity Beans CMP (partie de la JSR 318). Ce modèle de composants
persistants complexe et lourd des beans entités d’EJB 2.x a été remplacé par JPA.
■■ JAX-RPC (JSR 101). Il s’agissait de la première tentative de modéliser les ser-
vices web SOAP comme des appels RPC. Cette spécification a désormais été
remplacée par JAX-WS, qui est bien plus simple à utiliser et plus robuste.
■■ JAXR (JSR 93). JAXR est l’API dédiée aux communications avec les registres
UDDI. Ce dernier étant peu utilisé, JAXR devrait quitter Java EE et continuer
d’évoluer comme une JSR distincte.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
20 Java EE 6 et GlassFish 3
Profils
Les profils sont une innovation majeure de l’environnement Java EE 6. Leur but
principal consiste à réduire la taille de la plate-forme pour qu’elle convienne mieux
aux besoins du développeur. Quelles que soient la taille et la complexité de l’appli-
cation que vous développez aujourd’hui, vous la déploierez sur un serveur d’appli-
cations qui vous offre les API et les services de vingt-huit spécifications. L’une des
principales critiques adressées à Java EE est qu’il était trop lourd : les profils ont
donc été conçus pour régler ce problème. Comme le montre la Figure 1.4, les profils
sont des sous-ensembles ou des surensembles de la plate-forme et peuvent chevau-
cher cette dernière ou d’autres profils.
Figure 1.4
Profils de la plate-forme Java EE 6 complet
Java EE.
Profil X
Profil web
Profil Y
Java EE 6 définit un seul profil : le profil web. Son but consiste à permettre au déve-
loppeur de créer des applications web avec l’ensemble de technologies approprié.
Web Profile 1.0 est spécifié dans une JSR distincte ; c’est le premier profil de la
plate-forme Java EE 6. D’autres seront créés dans le futur (on pourrait penser à un
profil minimal ou à un profil de portail). Le profil web, quant à lui, évoluera à son
rythme et nous pourrions disposer d’une version 1.1 ou 1.2 avant la sortie de Java
EE 7. Nous verrons également apparaître des serveurs d’applications compatibles
Web Profile 1.0 et non plus compatibles Java EE 6. Le Tableau 1.6 énumère les spéci-
fications contenues dans ce profil web.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 21
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
22 Java EE 6 et GlassFish 3
@EJB
private BookEJB bookEJB;
// Getters, setters
}
Les EJB sont également plus simples à développer en Java EE 6. Comme le montre
le Listing 1.2, une simple classe annotée sans interface suffit désormais pour accé-
der localement à un EJB. Les EJB peuvent également être déployés directement
dans un fichier war sans avoir été au préalable assemblés dans un fichier jar. Toutes
ces améliorations font des EJB les composants transactionnels les plus simples, qui
peuvent servir aussi bien à des applications web minimales qu’à des applications
d’entreprise complexes.
@PersistenceContext(unitName = "chapter01PU")
private EntityManager em;
Plus riche
D’un côté, Java EE 6 s’est allégé en introduisant les profils ; de l’autre, il s’est
enrichi en ajoutant de nouvelles spécifications et en améliorant celles qui existaient
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 23
déjà. Les services web REST ont fait leur chemin dans les applications modernes
et Java EE 6 suit donc les besoins des entreprises en ajoutant la nouvelle spécifica-
tion JAX-RS. Comme le montre le Listing 1.3, un service web REST est une classe
annotée qui répond à des actions HTTP. Vous en apprendrez plus sur JAX-RS au
Chapitre 15.
@GET
@Produces({"application/xml", "application/json"})
public List<Book> getAllBooks() {
Query query = em.createNamedQuery("findAllBooks");
List<Book> books = query.getResultList();
return books;
}
}
La nouvelle version de l’API de persistance (JPA 2.0) a été améliorée par l’ajout de
collections de types de données simples (String, Integer, etc.), d’un verrouillage
pessimiste, d’une syntaxe JPQL plus riche, d’une toute nouvelle API de définition
de requêtes et par le support de la mise en cache. JPA est décrite aux Chapitres 2 à
5 de cet ouvrage.
Les EJB sont plus faciles à écrire (avec des interfaces éventuelles) et à assembler
(dans un fichier war) et disposent également de nouvelles fonctionnalités, comme
les appels asynchrones ou un service de temporisation plus élaboré pour planifier les
tâches. Un nouveau composant bean de session singleton fait également son appa-
rition. Comme le montre le Listing 1.4, une simple annotation suffit à transformer
une classe Java en singleton géré par un conteneur (une seule instance du composant
par application). Les Chapitres 6 à 9 vous en apprendront plus sur ces nouvelles
fonctionnalités.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
24 Java EE 6 et GlassFish 3
cache.put(id, object);
}
La couche présentation s’est également enrichie. JSF 2.0 ajoute en effet le support
d’Ajax et des Facelets (voir Chapitres 10 à 12).
Plus portable
L’application CD-Bookstore
Tout au long de ce livre, nous présenterons des extraits de code qui manipulent des
entités, des EJB, des pages JSF, des écouteurs JMS et des services web SOAP ou
REST. Tous ces extraits proviennent de l’application CD-Bookstore, un site web de
commerce en ligne permettant de parcourir un catalogue de livres et de CD pour les
acheter. L’application interagit avec un système bancaire pour valider les cartes de
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 25
crédit. Le diagramme des cas d’utilisation présenté à la Figure 1.5 décrit les acteurs
et les fonctionnalités de ce système.
Figure 1.5 CD BookStore
Diagramme des cas
Création
d’utilisation d'un compte
de l’application Gestion du catalogue
Utilisateur
CD-Bookstore. des articles
Parcours
du catalogue
Gestion Recherche
des clients d'un article
Connexion et
Employé déconnexion
Parcours
des commandes Modification
du compte Client
Création Achat
<<Extend>>
de commande d'articles
Validation
<<Include>>
de carte bancaire
Banque
Les acteurs qui interagissent avec le système décrit à la Figure 1.5 sont les suivants :
■■ Les employés de la société, qui doivent gérer à la fois le catalogue des articles et
les informations sur les clients. Ils peuvent également parcourir les commandes.
■■ Les utilisateurs, qui sont les visiteurs anonymes du site qui consultent le catalo-
gue des livres et des CD. Pour acheter un article, ils doivent créer un compte afin
de devenir clients.
■■ Les clients, qui peuvent parcourir le catalogue, modifier les informations de leur
compte et acheter des articles en ligne.
■■ La banque externe, à laquelle le système délègue la validation des cartes de
crédit.
INFO
Le code des exemples de ce livre est disponible sur le site web des éditions Pearson (http://
www.pearson.fr), sur la page consacrée à cet ouvrage.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
26 Java EE 6 et GlassFish 3
JDK 1.6
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 27
Figure 1.6
Configuration de
l’installation du JDK.
Figure 1.7
Affichage de la version
du JDK.
Maven 2
Afin de refléter ce que vous trouverez sur le terrain, nous avons décidé d’utiliser
Maven (http://maven.apache.org) pour construire les exemples de ce livre, bien
que sa description complète sorte du cadre de cet ouvrage (vous trouverez de très
nombreuses ressources consacrées à cet outil sur Internet ou dans les librairies).
Cependant, nous introduirons quelques éléments que vous devez connaître pour
comprendre et utiliser nos exemples.
Historique
La construction d’une application Java EE exige plusieurs opérations :
■■ génération du code et des ressources ;
■■ compilation des classes Java et des classes de test ;
■■ assemblage du code dans une archive (jar, ear, war, etc.) avec, éventuellement,
des bibliothèques jar externes.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
28 Java EE 6 et GlassFish 3
Effectuer ces tâches manuellement prend du temps et risque de produire des erreurs.
Les équipes de développement ont donc recherché des moyens d’automatiser tout
ce processus.
En 2000, les développeurs Java commencèrent à utiliser Ant (http://ant.apache.
org), qui leur permettait d’écrire des scripts de construction de leurs applications.
Ant est lui-même écrit en Java et offre un grand nombre de commandes qui, à
l’instar de l’outil make d’Unix, sont portables entre les différentes plates-formes.
Les équipes de développement commencèrent donc à créer leurs propres scripts
en fonction de leurs besoins. Cependant, Ant atteignit ses limites lorsque les pro-
jets commencèrent à impliquer des systèmes hétérogènes complexes. Les sociétés
avaient du mal à industrialiser leur système de construction de leurs applications.
Il n’existait pas véritablement d’outil permettant de réutiliser simplement un script
de construction d’un projet à l’autre (le copier/coller était la seule méthode pour y
parvenir).
En 2002, la fondation Apache a mis à disposition Maven, qui non seulement résol-
vait tous ces problèmes mais allait également bien au-delà d’un simple outil de
construction. Maven offre aux projets une solution pour les construire, des biblio-
thèques partagées et une plate-forme évolutive au moyen d’extensions, permettant
ainsi d’assurer la qualité, de produire la documentation, de gérer les équipes de
travail, etc. Fondé sur le principe de "convention plutôt que configuration", Maven
introduit une description de projet standard et un certain nombre de conventions,
notamment une structure de répertoires standardisée (voir Figure 1.8). Avec son
architecture extensible reposant sur des extensions (appelées mojos), Maven offre
de nombreux services.
Figure 1.8
Structure de répertoires
standard de Maven.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 29
Descripteur de projet
Maven repose sur le fait qu’une grande majorité de projets Java et Java EE ont des
besoins similaires lors de la construction des applications. Un projet Maven doit
respecter des standards et définir des fonctionnalités spécifiques dans un descripteur
de projet ou POM (Project Object Model). Ce POM est un fichier XML (pom.xml)
situé à la racine du projet. Comme le montre le Listing 1.5, l’information minimale
permettant de définir l’identité d’un projet est le groupId, l’artifactId, la version
et le type de paquetage.
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapter01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
</project>
Un projet est souvent divisé en différents artéfacts qui sont alors regroupés sous le
même groupId (comme les paquetages en Java) et identifiés de façon unique par l’ar-
tifactId. Le marqueur packaging permet à Maven de produire l’artéfact dans un for-
mat standard (jar, war, ear, etc.). Enfin, version identifie un artéfact au cours de son
évolution (version 1.1, 1.2, 1.2.1, etc.). Maven impose cette numérotation des versions
pour qu’une équipe puisse gérer l’évolution du développement de son projet. Maven
introduit également le concept de versions SNAPSHOT (le numéro de version se termine
alors par la chaîne -SNAPSHOT) pour identifier un artéfact en cours de développement.
Le POM définit bien plus d’informations sur vos projets. Certaines sont purement
descriptives (nom, description, etc.), d’autres concernent l’exécution de l’applica-
tion, comme la liste des bibliothèques externes qu’elle utilise, etc. Enfin, le fichier
pom.xml précise l’environnement de construction du projet (outils de contrôle de
versions, serveur d’intégration, dépôts d’artéfacts) et tout autre processus spécifique
nécessaire à la construction du projet.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
30 Java EE 6 et GlassFish 3
Figure 1.9
Exemple de dépôt local.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 31
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.ejb</artifactId>
<version>3.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
...
Les dépendances peuvent avoir une visibilité limitée (désignée par scope) :
■■ test. La bibliothèque sert à compiler et à exécuter les classes de test mais n’est
pas assemblée dans l’artéfact produit.
■■ provided. La bibliothèque est fournie par l’environnement (fournisseur de per-
sistance, serveur d’application, etc.) et ne sert qu’à compiler le code.
■■ compile. La bibliothèque est nécessaire à la compilation et à l’exécution.
■■ runtime. La bibliothèque n’est requise que pour l’exécution mais est exclue
de la compilation (composants JSF ou bibliothèques de marqueurs JSTL, par
exemple).
Figure 1.10
Un projet et ses modules.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
32 Java EE 6 et GlassFish 3
Figure 1.11
Cycle de vie d’un projet.
Installation
Les exemples de ce livre ont été développés avec Maven 2.2.1. Après avoir installé
le JDK 1.6, assurez-vous que la variable JAVA_HOME pointe sur le répertoire de celui-
ci puis téléchargez Maven à partir de l’URL http://maven.apache.org/, dézippez le
fichier sur votre disque dur et ajoutez le répertoire apache-maven-2.2.1/bin à votre
variable PATH.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 33
Puis, dans une fenêtre de commande DOS, tapez mvn -version pour tester votre
installation. Comme le montre la Figure 1.12, Maven devrait afficher sa version et
celle du JDK.
Figure 1.12
Maven affiche
sa version.
Maven a besoin d’un accès Internet pour pouvoir télécharger les extensions et
les dépendances des projets à partir du dépôt principal. Si vous vous trouvez der-
rière un proxy, consultez la documentation pour ajuster votre configuration en
conséquence.
Utilisation
Voici quelques commandes que nous utiliserons pour les exemples de ce livre. Elles
invoquent toute une phase différente du cycle de vie (nettoyage, compilation, instal-
lation, etc.) et utilisent le fichier pom.xml pour ajouter des bibliothèques, personna
liser la compilation ou ajouter des comportements via des extensions :
■■ mvn clean. Supprime tous les fichiers générés (classes compilées, code produit,
artéfacts, etc.).
■■ mvn compile. Compile les classes Java principales.
■■ mvn test-compile. Compile les classes de test.
■■ mvn test. Compile les classes de test et exécute les tests.
■■ mvn package. Compile et exécute les tests et crée l’archive du paquetage.
■■ mvn install. Construit et installe les artéfacts dans votre dépôt local.
■■ mvn clean install. Nettoie et installe (remarquez que vous pouvez indiquer
plusieurs commandes en les séparant par un espace).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
34 Java EE 6 et GlassFish 3
INFO
Maven vous permet de compiler, d’exécuter et d’assembler les exemples de ce livre. Cepen-
dant, pour écrire le code, vous aurez besoin d’un environnement de développement inté-
gré (EDI). Personnellement, nous utilisons IntelliJ IDEA de JetBrains, dont vous apercevrez
quelques copies d’écran. Vous pouvez choisir n’importe quel IDE car cet ouvrage ne repose
que sur Maven, non sur des fonctionnalités spécifiques d’IntelliJ IDEA.
JUnit 4
JUnit est un framework open-source pour l’écriture et l’exécution de tests. Parmi ses
fonctionnalités, citons :
■■ les assertions pour tester des résultats attendus ;
■■ les "fixtures" pour partager des données de test communes ;
■■ les lanceurs pour exécuter les tests.
JUnit est une bibliothèque de tests unitaires qui est le standard de facto pour Java ;
elle est assemblée dans un unique fichier jar que vous pouvez télécharger à partir
de l’URL http://www.junit.org/ (ou utilisez la gestion des dépendances de Maven
pour le récupérer). La bibliothèque contient une API complète vous permettant
d’écrire vos tests unitaires, ainsi qu’un outil pour les exécuter. Ces tests unitaires
aident à rendre votre code plus robuste et plus fiable.
Historique
La première version de JUnit a été écrite par Erich Gamma et Kent Beck en 1998.
Elle s’inspirait du framework de test Sunit de Smalltalk, également écrit par Kent
Beck, et est rapidement devenue l’un des frameworks les plus connus du monde Java.
Apportant les avantages des tests unitaires à une grande variété de langages, JUnit a
inspiré une famille d’outils xUnit comme nUnit (.NET), pyUnit (Python), CppUnit
(C++), dUnit (Delphi), et bien d’autres encore. JUnit joue un rôle important dans le
développement piloté par les tests (DPT).
Fonctionnement
Depuis JUnit 4, l’écriture des tests unitaires s’est simplifiée grâce à l’usage des
annotations, des importations statiques et des autres fonctionnalités de Java. Par
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 35
rapport aux versions précédentes, JUnit 4 fournit un modèle de test plus simple, plus
riche et plus facile d’emploi. Il introduit également des initialisations plus souples,
des règles de nettoyage, des délais d’expiration et des cas de tests paramétrables.
Étudions quelques-unes de ses fonctionnalités au moyen d’un exemple simple. Le
Listing 1.7 représente un POJO Customer possédant quelques attributs dont une date
de naissance, des constructeurs, des getters et des setters.
// Getters, setters
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
36 Java EE 6 et GlassFish 3
@Before
public void clearCustomerHelper() {
customerHelper.clear();
}
@Test
public void notNegative() {
Customer customer = new Customer();
customer.setDateNaissance(new GregorianCalendar(1975, 5,
27).getTime());
customerHelper.setCustomer(customer);
customerHelper.calculateAge();
@Test
public void expectedValue() {
int expectedAge = 33;
customerHelper.setCustomer(customer);
customerHelper.calculateAge();
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 37
assertTrue(customerHelper.getAgeCalcResult() == expectedAge);
}
@Test(expected = NullPointerException.class)
public void emptyCustomer() {
Customer customer = new Customer();
customerHelper.setCustomer(customer);
customerHelper.calculateAge();
Méthodes de test
Avec JUnit 4, les classes de test n’ont pas besoin d’hériter d’une classe quelconque.
Pour être exécutée comme un cas de test, une classe JUnit doit au moins posséder
une méthode annotée par @Test. Si vous tentez d’exécuter une classe qui ne com-
porte pas au moins une méthode @Test, vous obtiendrez une erreur (java.lang.
Exception: No runnable methods).
Une méthode de test doit utiliser l’annotation @Test, renvoyer void et ne prendre
aucun paramètre. Ces contraintes sont vérifiées lors de l’exécution et leur non-res-
pect provoque la levée d’une exception. L’annotation @Test peut prendre un para-
mètre facultatif expected pour indiquer que la méthode de test concernée doit lever
une exception. Si elle ne le fait pas ou si l’exception est différente de celle annoncée,
le test échoue. Dans notre exemple, une tentative de calculer l’âge d’un client vide
doit provoquer la levée d’une exception NullPointerException.
La méthode nextBirthDay() n’est pas implémentée dans le Listing 1.9 mais vous ne
souhaitez pas pour autant que ce test échoue : vous voulez simplement l’ignorer. Pour
ce faire, il suffit d’ajouter l’annotation @Ignore avant ou après l’annotation @Test.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
38 Java EE 6 et GlassFish 3
Les lanceurs de tests signaleront le nombre de tests ignorés, ainsi que le nombre de
tests qui ont réussi ou échoué. Si vous voulez indiquer la raison pour laquelle un test
a été ignoré, vous pouvez éventuellement passer un paramètre String contenant le
message adéquat.
Méthodes d’assertions
Les cas de test doivent vérifier que les objets sont conformes à ce qui est attendu.
JUnit dispose pour cela d’une classe Assert contenant plusieurs méthodes. Pour
l’utiliser, vous devez soit utiliser la notation préfixe (Assert.assertEquals(), par
exemple) soit importer statiquement la classe Assert (c’est ce que nous avons fait
dans le Listing 1.9). Comme vous pouvez le constater avec la méthode notNega-
tive(), vous pouvez aussi vous servir du mot-clé assert de Java.
Fixtures
Les fixtures sont des méthodes permettant d’initialiser et de libérer n’importe quel
objet au cours des tests. JUnit utilise les annotations @Before et @After pour exécu-
ter du code respectivement avant et après chaque test. Les méthodes annotées par @
Before et @After peuvent porter n’importe quel nom (clearCustomerHelper() ici)
et une même classe de test peut en avoir plusieurs. JUnit utilise également les anno-
tations @BeforeClass et @AfterClass pour exécuter un code spécifique une seule
fois par classe. Ces méthodes doivent être uniques et statiques et sont très pratiques
pour allouer et libérer des ressources coûteuses.
Lancement de JUnit
Pour exécuter le lanceur de JUnit, vous devez ajouter le fichier jar de JUnit à votre
variable CLASSPATH (ou ajouter une dépendance Maven). Vous pouvez alors lancer
vos tests via la commande Java suivante :
java –ea org.junit.runner.JUnitCore
„ com.apress.javaee6.CustomerHelperTest
Notez que, lorsque l’on utilise le mot-clé assert, il faut préciser le paramètre -ea ;
sinon les assertions seront ignorées.
Cette commande produira le résultat suivant :
JUnit version 4.5
..E.I
Time: 0.016
There was 1 failure:
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 39
1) expectedValue(com.apress.javaee6.CustomerHelperTest) java.lang.
AssertionError: at
„ CustomerHelperTest.expectedValue (CustomerHelperTest.java:52)
FAILURES!!!
Tests run: 3, Failures: 1
La première information affichée est le numéro de version de JUnit (4.5, ici). Puis
JUnit indique le nombre de tests exécutés (trois, ici) et le nombre d’échecs (un seul
dans cet exemple). La lettre I indique qu’un test a été ignoré.
Intégration de JUnit
Actuellement, JUnit est très bien intégré à la plupart des EDI (IntelliJ IDEA,
Eclipse, NetBeans, etc.). Avec ces environnements, JUnit utilise généralement la
couleur verte pour indiquer les tests qui ont réussi et le rouge pour signaler les
échecs. La plupart des EDI fournissent également des outils pour faciliter la création
des classes de test.
JUnit est également intégré à Maven via l’extension Surefire utilisée au cours de la
phase de test. Cette extension exécute les classes de tests JUnit d’une application et
produit des rapports aux formats texte et XML. Pour lancer les tests JUnit via cette
extension, faites la commande suivante :
mvn test
Derby 10.5
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
40 Java EE 6 et GlassFish 3
Installation
L’installation de Derby est très simple ; en fait, vous constaterez qu’il est déjà ins-
tallé puisqu’il est fourni avec le JDK 1.6 – au cours de l’installation de ce dernier
(voir Figure 1.6), l’assistant propose d’installer par défaut Java DB. S’il n’est pas
installé sur votre machine, vous pouvez récupérer les binaires à partir de l’URL
http://db.apache.org.
Lorsque Derby est installé, configurez la variable DERBY_HOME pour qu’elle contienne
le répertoire où il a été placé sur votre système, puis ajoutez %DERBY_HOME%\bin à
votre variable PATH. Pour lancer Derby en mode serveur réseau, exécutez le script
%DERBY_HOME%\bin\startNetworkServer.bat : des informations s’afficheront alors
sur la console pour indiquer, par exemple, le numéro du port sur lequel il attend les
connexions (1527 par défaut).
Derby est fourni avec plusieurs programmes utilitaires, dont sysinfo. Ouvrez une
session DOS, tapez sysinfo et vous devriez voir apparaître des informations sur
votre environnement Java et Derby, analogues à celles de la Figure 1.13.
Figure 1.13
Résultat de sysinfo après
l’installation de Derby.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 41
Cette commande crée une table customer avec une clé primaire et deux colonnes
varchar(20) pour stocker le prénom et le nom de chaque client. Vous pouvez afficher
la description de cette table à l’aide de la commande suivante :
ij> describe customer;
COLUMN_NAME |TYPE_NAME|DEC&|NUM&|COLUM&|COLUMN_DEF|CHAR_OCTE&|IS_NULL&
--------------------------------------------------------------------------
CUSTID |INTEGER |0 |10 |10 |NULL |NULL |NO
FIRSTNAME |VARCHAR |NULL|NULL|20 |NULL |40 |YES
LASTNAME |VARCHAR |NULL|NULL|20 |NULL |40 |YES
Maintenant que la table est créée, vous pouvez y ajouter des données à l’aide
d’instructions insert :
ij> insert into customer values (1, ’Fred’, ’Chene’);
ij> insert into customer values (2, ’Sylvain’, ’Verin’);
ij> insert into customer values (3, ’Robin’, ’Riou’);
Vous pouvez ensuite utiliser toute la puissance de SQL pour obtenir, trier ou regrouper
des données :
ij> select count(*) from customer;
1
-----------
3
1 ligne sélectionnée
ij> select * from customer where custid=3;
CUSTID |FIRSTNAME |LASTNAME
---------------------------------------------------
3 |Robin |Riou
1 ligne sélectionnée
ij> exit;
Pour obtenir le LDD de la table créée, sortez d’ij et lancez dblook pour interroger
la base de données Chapter01DB :
C:\> dblook -d ’jdbc:derby://localhost:1527/Chapter01DB’
-- Horodatage : 2010-01-29 19:21:09.379
-- La base de données source est : Chapter01DB
-- L’URL de connexion est :
„ jdbc:derby://localhost:1527/Chapter01DB
-- appendLogs: false
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
42 Java EE 6 et GlassFish 3
-- ----------------------------------------------
-- Instructions DDL pour tables
-- ----------------------------------------------
-- ----------------------------------------------
-- Instructions DDL pour clés
-- ----------------------------------------------
-- primaire/unique
ALTER TABLE "APP"."CUSTOMER" ADD CONSTRAINT "SQL100129191119440" PRIMARY KEY
("CUSTID");
GlassFish v3
Bien qu’il s’agisse d’un serveur d’applications assez récent, GlassFish est déjà uti-
lisé par un grand nombre de développeurs et de sociétés. Non seulement il est l’im-
plémentation de référence de la technologie Java EE, mais c’est également lui que
vous obtenez lorsque vous téléchargez le SDK Java EE de Sun. Vous pouvez égale-
ment déployer des applications critiques sur GlassFish – en plus d’être un produit,
GlassFish est également une communauté réunie autour de l’Open Source sur le site
http://glassfish.org. Cette communauté est très réactive sur les listes de diffusion
et les forums.
Historique
Les origines de GlassFish remontent aux premiers jours de Tomcat, lorsque Sun
et le groupe JServ firent don de cette technologie à la fondation Apache. Depuis,
Sun a continué à utiliser Tomcat dans différents produits. En 2005, Sun lança le
projet GlassFish, qui avait pour but le développement d’un serveur d’applications
entièrement certifié Java EE. Sa première version, la 1.0, vit le jour en mai 2006.
Le conteneur web de GlassFish hérite beaucoup de Tomcat (en fait, une application
qui s’exécute sur Tomcat devrait également s’exécuter avec GlassFish sans avoir à
la modifier).
GlassFish v2 est apparu en septembre 2007 et a reçu depuis de nombreuses mises
à jour. Il s’agit de la version la plus déployée actuellement. GlassFish s’efforce de
ne pas modifier les habitudes des utilisateurs entre ses versions majeures et de ne
pas imposer de modification du code. En outre, il n’y a aucune différence de qualité
entre les versions "communautaire" et "commerciale" : les utilisateurs payants ont
accès à des correctifs et à des outils de gestion supplémentaires (GlassFish Enter-
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 43
INFO
L’équipe de GlassFish a fait un effort considérable pour réaliser une documentation complète
et à jour en produisant de nombreux guides : Quick Start Guide, Installation Guide, Adminis-
tration Guide, Administration Reference, Application Deployment Guide, Developer’s Guide,
etc. Vous pouvez les lire à l’URL http:// wiki.glassfish.java.net/Wiki.jsp?page=GlassFishDocs.
Consultez également les FAQ, les How-To et le forum GlassFish pour obtenir encore plus
d’informations.
Architecture de GlassFish v3
En tant que programmeur d’application (et non en tant que développeur de Glass-
Fish), vous n’avez pas besoin de comprendre son architecture interne, mais son
architecture générale et ses choix techniques peuvent vous intéresser. À partir de la
version préliminaire de GlassFish v3, le serveur d’applications est construit sur un
noyau modulaire reposant sur OSGi. GlassFish s’exécute directement au-dessus de
l’implémentation de Felix d’Apache, mais devrait également fonctionner avec les
runtimes OSGi Equinox ou Knopflerfish. HK2 (Hundred-Kilobyte Kernel) abstrait
le module système OSGi pour fournir les composants qui peuvent être vus comme
des services. Ceux-ci peuvent être découverts et injectés en cours d’exécution. Pour
l’instant, OSGi n’est pas exposé aux développeurs Java EE.
INFO
OSGi est un standard pour la gestion et la découverte dynamique des composants. Les ap-
plications ou les composants peuvent être installés, lancés, arrêtés, mis à jour et désinstal-
lés à chaud, sans nécessiter un redémarrage. Les composants peuvent également détecter
dynamiquement l’ajout ou la suppression de services et s’adapter en conséquence ; Felix
d’Apache, Equinox et Knopflerfish sont des implémentations d’OSGi.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
44 Java EE 6 et GlassFish 3
OSGI
Java SE
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 45
Packaging System, également appelé pkg), le système de paquetage utilisé par Open-
Solaris. Outre l’ensemble de modules fourni par défaut avec GlassFish, l’utilisateur
peut se connecter à différents dépôts pour mettre à jour les fonctionnalités déjà ins-
tallées, en ajouter de nouvelles (support de Grails, conteneur de portlet, etc.), voire
ajouter des applications tierces. Dans un environnement d’entreprise, vous pouvez
configurer votre propre dépôt et utiliser pkg pour lancer l’installation d’un logiciel
reposant sur GlassFish.
En pratique, avec GlassFish v3, le centre de mise à jour est accessible via la console
d’administration, le client graphique qui se trouve dans le répertoire %GLASSFISH_
HOME%\updatetool\bin ou le programme pkg en ligne de commande. Tous trois
vous permettent d’énumérer, d’ajouter et de supprimer des composants à partir d’un
ensemble de dépôts. Dans le cas de pkg (qui se trouve dans le répertoire %GLASS-
FISH_HOME%\pkg\bin), les commandes les plus fréquentes sont pkg list, pkg ins-
tall, pkg uninstall et pkg image-update.
Sous-projets GlassFish
Le serveur d’applications GlassFish étant composé de tant de parties différentes, le
projet a été découpé en sous-projets. Cette décomposition permet de mieux com-
prendre non seulement les différentes parties mais également l’adoption des fonc-
tionnalités individuelles en dehors de l’environnement GlassFish, lorsque l’on est en
mode autonome ou dans un autre conteneur. La Figure 1.15 présente un résumé de
l’architecture des composantes fonctionnelles du serveur d’applications.
OpenMQ, par exemple, est une implémentation open-source de JMS de qualité pro-
fessionnelle. Bien qu’il soit souvent utilisé de façon autonome pour les architectures
orientées messages, OpenMQ peut également être intégré de différentes façons à
GlassFish (in-process, out-of-process, ou distant). Son administration peut s’effec-
tuer via la console d’administration de GlassFish ou par l’interface asadmin en ligne
de commande (voir la section "asadmin"). Le site web de sa communauté se trouve
à l’URL http://openmq.dev.java.net.
Metro est le cœur des services web. Cette pile complète est construite au-dessus
de JAX-WS et lui ajoute des fonctionnalités avancées, comme une sécurité de bout
en bout, un transport optimisé (MTOM, FastInfoset), une messagerie fiable et un
comportement transactionnel pour les services web SOAP. Cette qualité de service
(QoS) pour les services web repose sur des standards (OASIS, W3C), s’exprime
par des politiques et ne nécessite pas l’utilisation d’une nouvelle API en plus de
JAX-WS. Metro est également régulièrement testé avec les implémentations .NET
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
46 Java EE 6 et GlassFish 3
de Microsoft pour assurer l’interopérabilité entre les deux technologies. Son site
communautaire se trouve à l’URL http://metro.dev.java.net.
Serveur
d'administration
Console Application
d'administration d'administration
Figure 1.15
Composantes fonctionnelles de GlassFish.
Mojarra est le nom de l’implémentation de JSF dans GlassFish ; elle est disponible à
l’URL http:// mojarra.dev.java.net. Jersey est l’implémentation de référence et de
qualité professionnelle pour la nouvelle spécification JAX-RS. Cette spécification et
son implémentation sont des nouveaux venus dans Java EE 6 et GlassFish. En fait,
Jersey 1.0 est disponible via le centre de mise à jour de GlassFish v2 et v3 depuis
sa sortie en 2008.
Administration
GlassFish étant un serveur d’applications complet, il implémente évidemment l’in-
tégralité des spécifications Java EE 6, mais il dispose également de fonctionnalités
supplémentaires comme son administration, qui peut s’effectuer via une interface
web (la "console d’administration") ou au moyen d’asadmin, une interface en ligne
de commande puissante. Quasiment toute sa configuration est stockée dans un
fichier nommé domain.xml (situé dans le répertoire domains\domain1\config), ce
qui simplifie la recherche des erreurs. Ce fichier ne doit pas être modifié manuelle-
ment mais via l’un des deux outils d’administration, qui reposent tous les deux sur
l’instrumentation JMX fournie par GlassFish.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 47
Console d’administration
La console d’administration est une interface web (voir Figure 1.16) pour le serveur
d’applications. Cet outil est destiné à la fois aux administrateurs et aux développeurs
et fournit une représentation graphique des objets gérés par le serveur, une visua-
lisation améliorée des journaux, de l’état du système et de la surveillance des don-
nées. Au minimum, cette console permet de gérer la création et la modification des
configurations (réglage de la JVM, niveau des journaux, réglage du pool et du cache,
etc.), JDBC, JNDI, JavaMail, JMS et les ressources connecteur ainsi que les applica-
tions (déploiement). Dans le profil cluster de GlassFish, la console d’administration
est améliorée pour permettre à l’utilisateur de gérer les clusters, les instances, les
agents nœuds et les configurations de répartition de la charge. Une aide contextuelle
est toujours disponible via le bouton d’aide situé en haut à droite de la fenêtre. Dans
une installation par défaut, la console est accessible après le démarrage de GlassFish
par l’URL http://localhost:4848. À partir de GlassFish v3, il est possible de confi-
gurer un utilisateur anonyme afin d’éviter de devoir s’authentifier. Si cet utilisateur
n’existe pas, une installation typique utilise admin comme nom d’utilisateur et admi-
nadmin comme mot de passe par défaut.
Figure 1.16
Console d’administration web.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
48 Java EE 6 et GlassFish 3
Figure 1.17
Ligne
de commande
asadmin.
Installation de GlassFish
GlassFish v3 peut être installé sous différents profils (chaque profil définit un ensemble
de fonctionnalités et de configurations). Le profil le plus classique en ce qui nous
concerne est le profil développeur. Si vous voulez utiliser les fonctionnalités de cluster
de GlassFish, en revanche, vous devrez soit l’installer sous le profil cluster, soit mettre
à jour votre installation existante en choisissant "Ajouter le support Cluster" à partir de
la console d’administration. Pour le moment, il n’existe que le profil développeur pour
GlassFish v3 (qui est nécessaire pour exécuter les applications Java EE 6).
GlassFish peut être téléchargé via différents mécanismes de distribution. Les choix
les plus évidents consistent à le récupérer à partir de l’URL http://glassfish.org, à
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 1 Tour d’horizon de Java EE 6 49
l’installer avec le SDK Java EE ou à utiliser l’EDI NetBeans. Nous expliquerons ici
comment le télécharger et l’installer à partir du site communautaire.
Rendez-vous sur la page principale de téléchargement, https://glassfish.dev.java.
net/public/downloadsindex.html, et choisissez GlassFish Server v3. Sélectionnez
l’archive convenant à votre plate-forme et aux besoins de votre système d’exploita-
tion (la distribution Unix fonctionnera avec Linux, Solaris et Mac OS X). L’installation
du programme d’installation lancera l’installateur graphique qui :
■■ vous demande d’accepter les termes de la licence ;
■■ vous demande le répertoire où vous souhaitez installer GlassFish ;
■■ vous permet de configurer un nom d’utilisateur et un mot de passe pour l’admi-
nistrateur (ou crée par défaut un utilisateur anonyme) ;
■■ vous permet de configurer les ports HTTP et d’administration (en vérifiant qu’ils
ne sont pas déjà utilisés) ;
■■ installe et active l’outil de mise à jour (les clients pkg et updatetool).
Puis il décompresse une installation préconfigurée de GlassFish avec une configu-
ration par défaut : le port d’administration est 4848, le port HTTP est 8080 et aucun
utilisateur admin n’est configuré. L’outil de mise à jour n’est pas installé par défaut ;
il le sera à partir du réseau lors du premier démarrage. Lorsqu’il a été correctement
installé, GlassFish peut être lancé avec la ligne de commande asadmin suivante (voir
Figure 1.18).
asadmin start-domain domain1
Vous pouvez ensuite afficher la console d’administration (que nous avons montrée
à la Figure 1.16) en faisant pointer votre navigateur vers http://localhost:4848 ou
aller sur le serveur web par défaut via http://localhost:8080.
INFO
Si vous n’avez qu’un seul domaine, vous pouvez omettre le nom de domaine par défaut et
lancer GlassFish uniquement avec la commande asadmin start-domain. Si vous voulez voir
apparaître le journal à l’écran au lieu de consulter le fichier qui lui est consacré (domains/
domain1/logs/server.log), utilisez la commande asadmin start-domain --verbose.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
50 Java EE 6 et GlassFish 3
règles de gestion, les propriétés système, la surveillance des données, le flux d’appel
et les différentes configurations de sécurité.
Figure 1.18
Lancement
de GlassFish.
Résumé
Lorsqu’une société développe une application Java et doit ajouter des fonctionnali-
tés professionnelles comme la gestion des transactions, la sécurité, la concurrence
ou la messagerie, Java EE est attractif. Il est standard, les composants sont déployés
dans différents conteneurs qui offrent de nombreux services et fonctionnent avec
plusieurs protocoles. Java EE 6 suit les traces de sa version précédente en ajoutant la
simplicité d’utilisation de la couche web. Cette version est plus légère (grâce à l’éla-
gage, aux profils et à EJB Lite), plus simple d’utilisation (plus besoin d’interfaces
sur les EJB ou d’annotations sur la couche web), plus riche (elle ajoute de nouvelles
spécifications et fonctionnalités) et, enfin, plus portable (elle inclut un conteneur
EJB standardisé et autorise les noms JNDI).
La deuxième partie de ce chapitre a été consacrée à la mise en place de l’envi-
ronnement de développement. Ce livre contient de nombreux extraits de code et
des sections "Récapitulatif". Vous aurez besoin de plusieurs outils et frameworks
pour compiler, déployer, exécuter et tester ces codes : JDK 1.6, Maven 2, JUnit 4,
Derby 10.5 et GlassFish v3.
Ce chapitre vous a donné un bref aperçu de Java EE 6. Les suivants étudieront plus
en détail ses spécifications.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
2
Persistance en Java
Les applications sont composées d’une logique métier, d’interactions avec d’autres
systèmes, d’interfaces utilisateur et... de persistance. La plupart des données mani-
pulées par les applications doivent être stockées dans des bases de données pour
pouvoir être ensuite récupérées et analysées. Les bases de données sont importantes :
elles stockent les données métier, servent de point central entre les applications et
traitent les données via des triggers ou des procédures stockées. Les données persis-
tantes sont omniprésentes – la plupart du temps, elles utilisent les bases de données
relationnelles comme moteur sous-jacent. Dans un système de gestion de base de
données relationnelle, les données sont organisées en tables formées de lignes et
de colonnes ; elles sont identifiées par des clés primaires (des colonnes spéciales
ne contenant que des valeurs uniques) et, parfois, par des index. Les relations entre
tables utilisent les clés étrangères et joignent les tables en respectant des contraintes
d’intégrité.
Tout ce vocabulaire est totalement étranger à un langage orienté objet comme Java.
En Java, nous manipulons des objets qui sont des instances de classes ; les objets
héritent les uns des autres, peuvent utiliser des collections d’autres objets et, parfois,
se désignent eux-mêmes de façon récursive. Nous disposons de classes concrètes, de
classes abstraites, d’interfaces, d’énumérations, d’annotations, de méthodes, d’at-
tributs, etc. Cependant, bien que les objets encapsulent soigneusement leur état et
leur comportement, cet état n’est accessible que lorsque la machine virtuelle (JVM)
s’exécute : lorsqu’elle s’arrête ou que le ramasse-miettes nettoie la mémoire, tout
disparaît. Ceci dit, certains objets n’ont pas besoin d’être persistants : par données
persistantes, nous désignons les données qui sont délibérément stockées de façon
permanente sur un support magnétique, une mémoire flash, etc. Un objet est persis-
tant s’il peut stocker son état afin de pouvoir le réutiliser plus tard.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
52 Java EE 6 et GlassFish 3
Il existe différents moyens de faire persister l’état en Java. L’un d’eux consiste à uti-
liser le mécanisme de sérialisation qui consiste à convertir un objet en une suite de
bits : on peut ainsi sérialiser les objets sur disque, sur une connexion réseau (notam-
ment Internet), sous un format indépendant des systèmes d’exploitation. Java fournit
un mécanisme simple, transparent et standard de sérialisation des objets via l’implé-
mentation de l’interface java.io.Serializable. Cependant, bien qu’elle soit très
simple, cette technique est assez fruste : elle ne fournit ni langage d’interrogation ni
support des accès concurrents intensifs ou de la mise en cluster.
Un autre moyen de mémoriser l’état consiste à utiliser JDBC (Java Database Connec-
tivity), qui est l’API standard pour accéder aux bases de données relationnelles. On
peut ainsi se connecter à une base et exécuter des requêtes SQL (Structured Query
Language) pour récupérer un résultat. Cette API fait partie de la plate-forme Java
depuis la version 1.1 mais, bien qu’elle soit toujours très utilisée, elle a tendance
à être désormais éclipsée par les outils de correspondance entre modèle objet et
modèle relationnel (ORM, Object-Relational Mapping), plus puissants.
Le principe d’un ORM consiste à déléguer l’accès aux bases de données relation-
nelles à des outils ou à des frameworks externes qui produisent une vue orientée
objet des données relationnelles et vice versa. Ces outils établissent donc une corres-
pondance bidirectionnelle entre la base et les objets. Différents frameworks fournis-
sent ce service, notamment Hibernate, TopLink et Java Data Objects (JDO), mais il
est préférable d’utiliser JPA (Java Persistence API) car elle est intégrée à Java EE 6.
JPA 1.0 a été créée avec Java EE 5 pour résoudre le problème de la persistance
des données en reliant les modèles objets et relationnels. Avec Java EE 6, JPA 2.0
conserve la simplicité et la robustesse de la version précédente tout en lui ajoutant
de nouvelles fonctionnalités. Grâce à cette API, vous pouvez accéder à des données
relationnelles et les manipuler à partir des EJB (Enterprise Java Beans), des compo-
sants web et des applications Java SE.
JPA est une couche d’abstraction au-dessus de JDBC, qui fournit une indépendance
vis-à-vis de SQL. Toutes les classes et annotations de cette API se trouvent dans le
paquetage javax.persistence. Ses composants principaux sont les suivants :
■■ ORM, qui est le mécanisme permettant de faire correspondre les objets à des
données stockées dans une base de données relationnelle.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java 53
■■ Une API gestionnaire d’entités permettant d’effectuer des opérations sur la base
de données, notamment les opérations CRUD (Create, Read, Update, Delete).
Grâce à elle, il n’est plus nécessaire d’utiliser directement JDBC.
■■ JPQL (Java Persistence Query Language), qui permet de récupérer des données
à l’aide d’un langage de requêtes orienté objet.
■■ Des mécanismes de transaction et de verrouillage lorsque l’on accède de façon
concurrente aux données, fournis par JTA (Java Transaction API). Les transactions
locales à la ressource (non JTA) sont également reconnues par JPA.
■■ Des fonctions de rappel et des écouteurs permettant d’ajouter la logique métier
au cycle de vie d’un objet persistant.
Historique de la spécification
Les solutions ORM existent depuis longtemps, bien avant Java. Des produits comme
TopLink ont commencé à être utilisés avec Smalltalk en 1994, avant de basculer
vers Java. Les produits ORM commerciaux comme TopLink sont donc disponibles
depuis les premiers jours du langage Java. Cependant, bien qu’ils aient prouvé leur
utilité, ils n’ont jamais été standardisés pour cette plate-forme. Une approche com-
parable à ORM a bien été standardisée sous la forme de JDO, mais elle n’a jamais
réussi à pénétrer le marché de façon significative.
En 1998, EJB 1.0 vit le jour et fut ensuite intégré à J2EE 1.2. Il s’agissait d’un com-
posant distribué lourd, utilisé pour la logique métier transactionnelle. CMP (Entity
Container Managed Persistence) fut ensuite ajouté à EJB 1.0 et continua d’évoluer
jusqu’à EJB 2.1 (J2EE 1.4). La persistance ne pouvait prendre place qu’à l’intérieur
d’un conteneur, via un mécanisme d’instanciation complexe utilisant des interfaces
locales ou distantes. Les capacités ORM étaient également très limitées car l’héri-
tage était difficile à traduire en termes relationnels.
Parallèlement au monde J2EE, la solution open-source Hibernate apportait des
modifications surprenantes en terme de persistance car ce framework fournissait un
modèle orienté objet persistant et léger.
Après des années de plaintes à propos des composants Entity CMP 2.x et en réponse
au succès et à la simplicité des frameworks open-source comme Hibernate, le
modèle de persistance de Java EE fut entièrement revu dans Java EE 5 : JPA 1.0 était
né et proposait désormais une approche légère, largement inspirée des principes
de conception d’Hibernate. La spécification JPA 1.0 a donc été intégrée à EJB 3.0
(JSR 220).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
54 Java EE 6 et GlassFish 3
Aujourd’hui, avec Java EE 6, la seconde version de JPA continue dans cette voie
de la simplicité tout en ajoutant de nouvelles fonctionnalités. Elle a évolué pour
posséder sa propre spécification, la JSR 317.
Si JPA 1.0 était un modèle de persistance entièrement nouveau par rapport à son
prédécesseur Entity CMP 2.x, JPA 2.0 est la suite de JPA 1.0, dont elle conserve
l’approche orientée objet utilisant les annotations et, éventuellement, des fichiers
de correspondance en XML. Cette seconde version ajoute de nouvelles API, étend
JPQL et intègre de nouvelles fonctionnalités :
■■ Les collections de types simples (String, Integer, etc.) et d’objets intégrables
(embeddable) peuvent désormais être associées à des tables distinctes alors
qu’auparavant on ne pouvait associer que des collections d’entités.
■■ Les clés et les valeurs des associations peuvent désormais être de n’importe quel
type de base, des entités ou des objets intégrables.
■■ L’annotation @OrderColumn permet maintenant d’avoir un tri persistant.
■■ La suppression des orphelins permet de supprimer les objets fils d’une relation
lorsque l’objet parent est supprimé.
■■ Le verrouillage pessimiste a été ajouté au verrouillage optimiste, qui existait déjà.
■■ Une toute nouvelle API de définition de requêtes a été ajoutée afin de pouvoir
construire des requêtes selon une approche orientée objet.
■■ La syntaxe de JPQL a été enrichie (elle autorise désormais les expressions case,
par exemple).
■■ Les objets intégrables peuvent maintenant être embarqués dans d’autres objets
intégrables et avoir des relations avec les entités.
■■ La notation pointée a été étendue afin de pouvoir gérer les objets intégrables avec
des relations ainsi que les objets intégrables d’objets intégrables.
■■ Le support d’une nouvelle API de mise en cache a été ajouté.
Nous présenterons en détail toutes ces fonctionnalités aux Chapitres 3, 4 et 5.
Implémentation de référence
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java 55
Le principe d’un ORM consiste à déléguer à des outils ou à des frameworks externes
(JPA, dans notre cas) la création d’une correspondance entre les objets et les tables.
Le monde des classes, des objets et des attributs peut alors être associé aux bases
de données constituées de tables formées de lignes et de colonnes. Cette association
offre une vue orientée objet aux développeurs, qui peuvent alors utiliser de façon
transparente des entités à la place des tables. JPA utilise les métadonnées pour faire
correspondre les objets à une base de données.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
56 Java EE 6 et GlassFish 3
Les métadonnées sont associées à chaque entité pour décrire son association :
elles permettent au fournisseur de persistance de reconnaître une entité et d’inter-
préter son association. Ces métadonnées peuvent s’exprimer dans deux formats
différents :
■■ Annotations. Le code de l’entité est directement annoté avec toutes sortes d’an-
notations décrites dans le paquetage javax.persistence.
■■ Descripteurs XML. Ils peuvent être utilisés à la place (ou en plus) des anno-
tations. L’association est définie dans un fichier XML externe qui sera déployé
avec les entités. Cette technique peut être très utile lorsque la configuration de la
base de données varie en fonction de l’environnement, par exemple.
Pour faciliter les correspondances, JPA (comme de nombreuses autres fonctionna-
lités de Java EE 6) utilise le concept de "convention plutôt que configuration" (éga-
lement appelé "configuration par exception" ou "programmation par exception").
Le principe est que JPA utilise un certain nombre de règles de correspondance par
défaut (le nom de la table est le même que celui de l’entité, par exemple) : si ces
règles vous satisfont, vous n’avez pas besoin de métadonnées supplémentaires
(aucune annotation ni XML ne sont alors nécessaires) mais, dans le cas contraire,
vous pouvez adapter la correspondance à vos propres besoins à l’aide des métadon-
nées. En d’autres termes, fournir une configuration est une exception à la règle.
Voyons un exemple. Le Listing 2.1 présente une entité Livre avec quelques attributs.
Comme vous pouvez le constater, certains sont annotés (id, titre et description)
alors que d’autres ne le sont pas.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java 57
Pour être reconnue comme entité, la classe Book doit être annotée par @javax.
persistence.Entity (ou son équivalent XML). L’annotation @javax.
persistence.Id sert à indiquer la clé primaire, et la valeur de cet identifiant est
automatiquement générée par le fournisseur de persistance (@GeneratedValue).
L’annotation @Column est utilisée avec certains attributs pour adapter la correspon-
dance par défaut des colonnes (title ne peut plus contenir NULL et description a
une longueur de 2 000 caractères). Le fournisseur de persistance pourra ainsi faire
correspondre à l’entité Book une table BOOK (règle de correspondance par défaut),
produire une clé primaire et synchroniser les valeurs des attributs vers les colonnes
de la table. La Figure 2.1 montre cette association entre l’entité et la table.
Figure 2.1 <<entity>>
Book BOOK
L’entité Book est +ID bigint Nullable = false
-id : Long
TITLE varchar(255) Nullable = false
associée à la table -title : String
PRICE double Nullable = true
-price : Float Association
BOOK. DESCRIPTION varchar(2000) Nullable = true
-description : String
ISBN varchar(255) Nullable = true
-nbOfPage : Integer
NBOFPAGE integer Nullable = true
-illustrations : Boolean
ILLUSTRATIONS smallint Nullable = true
Comme nous le verrons au Chapitre 3, cette correspondance est riche et vous per-
met d’associer toutes sortes de choses. Le monde de la programmation orientée
objet abonde de classes et d’associations entre elles (et les collections de classes).
Les bases de données modélisent également les relations, mais différemment : en
utilisant des clés étrangères ou des jointures. JPA dispose donc d’un ensemble de
métadonnées permettant de gérer cette correspondance entre ces deux visions des
relations. L’héritage peut également être traduit : bien que ce soit un mécanisme fré-
quemment utilisé en programmation pour réutiliser le code, ce concept est inconnu
des bases de données relationnelles (elles doivent le simuler avec des clés étrangères
et des contraintes). Même si cette traduction de l’héritage implique quelques contor-
sions, JPA l’autorise et vous offre différentes stratégies pour y parvenir. Nous les
décrirons au Chapitre 3.
JPA permet de faire correspondre les entités à des bases de données et de les inter-
roger en utilisant différents critères. La puissance de cette API vient du fait qu’elle
offre la possibilité d’interroger les entités et leurs relations de façon orientée objet
sans devoir utiliser les clés étrangères ou les colonnes de la base de données sous-
jacente. L’élément central de l’API, responsable de l’orchestration des entités, est le
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
58 Java EE 6 et GlassFish 3
gestionnaire d’entités : son rôle consiste à gérer les entités, à lire et à écrire dans une
base de données et à autoriser les opérations CRUD simples sur les entités, ainsi que
des requêtes complexes avec JPQL. D’un point de vue technique, le gestionnaire
d’entités n’est qu’une interface dont l’implémentation est donnée par le fournisseur
de persistance, EclipseLink. L’extrait de code suivant montre comment créer un
gestionnaire d’entités et rendre une entité Livre persistante :
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("chapter02PU");
EntityManager em = emf.createEntityManager();
em.persist(livre);
La Figure 2.2 montre comment l’interface EntityManager peut être utilisée par une
classe (Main, ici) pour manipuler des entités (Livre, ici). Grâce à des méthodes
comme persist() et find(), le gestionnaire d’entités masque les appels JDBC
adressés à la base de données, ainsi que les instructions SQL INSERT ou SELECT.
«Interface»
Main EntityManager
SQL / JDBC
+persist(entity : Object) : void Base de
+find(entityClass, : Class<T>, primaryKey : Object) : <T>
données
Book
-id : Long
-title : String
-price : Float
-description : String
-nbOfPage : Integer
-illustrations : Boolean
Figure 2.2
Le gestionnaire d’entités interagit avec l’entité et la base de données sous-jacente.
Une instruction JPQL peut exécuter des requêtes dynamiques (créées à l’exécution),
des requêtes statiques (définies lors de la compilation), voire des instructions SQL
natives. Les requêtes statiques, également appelées requêtes nommées, sont définies
par des annotations ou des métadonnées XML. L’instruction JPQL précédente peut,
par exemple, être définie comme une requête nommée sur l’entité Livre.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java 59
@Entity
@NamedQuery(name = "findBookByTitle",
„ query = "SELECT b FROM Book b WHERE b.title =’H2G2’")
public class Book {
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
Les entités sont simplement des POJO (Plain Old Java Objects) qui sont gérés ou
non par le gestionnaire d’entités. Lorsqu’elles sont gérées, elles ont une identité de
persistance et leur état est synchronisé avec la base de données. Lorsqu’elles ne le
sont pas (elles sont, par exemple, détachées du gestionnaire d’entités), elles peuvent
être utilisées comme n’importe quelle autre classe Java : ceci signifie que les entités
ont un cycle de vie, comme le montre la Figure 2.3. Lorsque vous créez une instance
de l’entité Book à l’aide de l’opérateur new, l’objet existe en mémoire et JPA ne le
connaît pas (il peut même finir par être supprimé par le ramasse-miettes) ; lorsqu’il
devient géré par le gestionnaire d’entités, son état est associé et synchronisé avec
la table BOOK. L’appel de la méthode EntityManager.remove() supprime les don-
nées de la base, mais l’objet Java continue d’exister en mémoire jusqu’à ce que le
ramasse-miettes le détruise.
Les opérations qui s’appliquent aux entités peuvent se classer en quatre catégories :
persistance, mise à jour, suppression et chargement, qui correspondent respective-
ment aux opérations d’insertion, de mise à jour, de suppression et de sélection dans
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
60 Java EE 6 et GlassFish 3
Figure 2.3
Cycle de vie d’une entité.
Existe en mémoire
Base de
données
Récapitulatif
Maintenant que vous connaissez un peu JPA, EclipseLink, les entités, le gestion-
naire d’entités et JPQL, rassemblons le tout pour écrire une petite application qui
stocke une entité dans une base de données. Nous allons donc écrire une simple
entité Book et une classe Main chargée de stocker un livre. Nous la compilerons avec
Maven 2 et l’exécuterons avec EclipseLink et une base de données cliente Derby.
Pour montrer la simplicité des tests unitaires sur une entité, nous verrons également
comment écrire une classe de test (BookTest) avec un cas de test JUnit 4 et à l’aide
du mode intégré de Derby, qui nous permettra de stocker les données en utilisant une
base de données en mémoire.
Pour respecter la structure de répertoires de Maven, les fichiers devront être placés
dans les répertoires suivants :
■■ src/main/java pour l’entité Book et la classe Main ;
■■ src/main/resources pour le fichier persistence.xml utilisé par la classe Main ;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java 61
■■ pom.xml,le Project Object Model (POM) de Maven, qui décrit le projet et ses
dépendances vis-à-vis d’autres modules et composants externes.
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
62 Java EE 6 et GlassFish 3
Pour des raisons de lisibilité, nous avons omis ici les constructeurs, les getters et les
setters de cette classe. Comme le montre ce code, hormis les quelques annotations,
Book est un simple POJO. Écrivons maintenant une classe Main qui stockera un livre
dans la base de données.
La classe Main présentée dans le Listing 2.4 se trouve dans le même répertoire que
l’entité Livre. Elle commence par créer une instance de Book (avec le mot-clé new de
Java) puis initialise ses attributs. Vous remarquerez qu’il n’y a rien de spécial ici : ce
n’est que du code Java traditionnel. Puis elle utilise la classe Persistence pour obte-
nir une instance d’EntityManagerFactory afin de désigner une unité de persistance
appelée chapter02PU que nous décrirons plus tard dans la section "Unité de persis-
tance pour la classe Main". Cette fabrique permet à son tour de créer une instance
d’EntityManager (la variable em). Comme on l’a déjà mentionné, le gestionnaire
d’entités est l’élément central de JPA car il permet de créer une transaction, de stoc-
ker l’objet Book à l’aide de la méthode EntityManager.persist() puis de valider
la transaction. À la fin de la méthode main() on ferme les objets EntityManager et
EntityManagerFactory afin de libérer les ressources du fournisseur.
tx.begin();
em.persist(book);
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java 63
tx.commit();
em.close();
emf.close();
}
}
Là encore, nous avons omis la gestion des exceptions pour des raisons de lisibilité.
Si une exception de persistance survenait, il faudrait annuler la transaction et enre-
gistrer un message dans le journal.
<persistence-unit name="chapter02PU"
transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider
</provider>
<class>com.apress.javaee6.chapter02.Book</class>
<properties>
<property name="eclipselink.target-database" value="DERBY"/>
<property name="eclipselink.jdbc.driver"
value="org.apache.derby.jdbc.ClientDriver"/>
<property name="eclipselink.jdbc.url"
value="jdbc:derby://localhost:1527/chapter02DB;
„ create=true"/>
<property name="eclipselink.jdbc.user" value="APP"/>
<property name="eclipselink.jdbc.password" value="APP"/>
<property name="eclipselink.ddl-generation"
value="create-tables"/>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
64 Java EE 6 et GlassFish 3
Cette unité de persistance énumère toutes les entités qui doivent être gérées par le
gestionnaire d’entités. Ici, le marqueur <class> désigne l’entité Book.
Vous disposez maintenant de tous les ingrédients pour lancer l’application : l’entité
Book que vous voulez stocker, la classe Main qui effectue le travail à l’aide d’un
gestionnaire d’entités et l’unité de persistance qui relie l’entité à la base de données
Derby. Pour compiler ce code, nous utiliserons Maven au lieu d’appeler directement
le compilateur javac. Vous devez donc d’abord créer un fichier pom.xml décrivant
le projet et ses dépendances (JPA, notamment). Vous devrez également informer
Maven que vous utilisez Java SE 6 en configurant l’extension maven-compiler-plu-
gin comme cela est décrit dans le Listing 2.6.
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.5.3.0</version>
</dependency>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java 65
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.5.3.0</version> <scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Pour compiler le code, vous avez d’abord besoin de l’API de JPA, qui définit toutes
les annotations et les classes qui se trouvent dans le paquetage javax.persistence.
Vous obtiendrez ces classes dans une archive jar désignée par l’identifiant d’artéfact
javax.persistence et qui sera stockée dans le dépôt Maven. Le runtime Eclipse-
Link (c’est-à-dire le fournisseur de persistance) est défini dans l’identifiant d’ar-
téfact eclipselink. Vous avez également besoin des pilotes JDBC permettant de
se connecter à Derby. L’identifiant d’artéfact derbyclient désigne l’archive jar qui
contient le pilote JDBC pour se connecter à Derby lorsqu’il s’exécute en mode serveur
(il est alors lancé dans un processus séparé et écoute sur un port), tandis que l’iden-
tifiant d’artéfact derby contient les classes pour utiliser Derby comme une base de
données intégrée. Notez que ce dernier est réservé aux tests (<scope>test</scope>)
et dépend de JUnit 4.
Pour compiler les classes, ouvrez une fenêtre de commande dans le répertoire racine
contenant le fichier pom.xml, puis entrez la commande Maven suivante :
mvn compile
Vous devriez voir apparaître le message BUILD SUCCESSFUL vous informant que la
compilation a réussi. Maven crée alors un sous-répertoire target contenant tous les
fichiers class ainsi que le fichier persistence.xml.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
66 Java EE 6 et GlassFish 3
Avant d’exécuter la classe Main, vous devez lancer Derby. Pour ce faire, le moyen
le plus simple consiste à se rendre dans le répertoire %DERBY_HOME%\bin et à lancer
le script startNetworkServer.bat. Derby se lance et affiche les messages suivants
sur la console :
2010-01-31 14:56:54.816 GMT : Le gestionnaire de sécurité a été installé au
moyen de la stratégie de sécurité de serveur de base.
2010-01-31 14:56:55.562 GMT : Apache Derby Serveur réseau - 10.5.3.0 -
(802917) démarré et prêt à accepter les connexions sur le port 1527
Le processus Derby écoute sur le port 1527 et attend que le pilote JDBC lui envoie
une instruction SQL. Pour exécuter la classe Main, vous pouvez utiliser l’interpré-
teur java ou la commande Maven suivante :
mvn exec:java -Dexec.mainClass="com.apress.javaee6.chapter02.Main"
Lorsque vous exécutez la classe Main, plusieurs choses se passent. Tout d’abord,
Derby crée automatiquement la base de données chapter02tDB dès que l’entité Book
est initialisée car nous avions ajouté la propriété create=true à l’URL de JDBC
dans le fichier persistence.xml :
<property name="eclipselink.jdbc.url"
„ value="jdbc:derby://localhost:1527/chapter02DB;create=true"/>
Ce raccourci est très pratique lorsque l’on est en phase de développement car nous
n’avons pas besoin d’écrire un script SQL pour créer la base. Puis la propriété
eclipselink.ddl-generation demande à EclipseLink de créer automatiquement
la table LIVRE. Enfin, le livre est inséré dans cette table (avec un identifiant produit
automatiquement).
em.persist(book);
Utilisons maintenant les commandes Derby pour afficher la structure de la table :
tapez la commande ij dans une fenêtre de commandes (comme on l’a expliqué plus
haut, le répertoire %DERBY_HOME%\bin doit avoir été ajouté à votre variable PATH).
Cette commande lance l’interpréteur de Derby à partir duquel vous pouvez exé-
cuter des commandes pour vous connecter à la base, afficher les tables de la base
chapter02DB (show tables), vérifier la structure de la table BOOK (describe book)
et même consulter son contenu à l’aide d’instructions SQL comme SELECT * FROM
BOOK.
C:\> ij
version ij 10.5
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java 67
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
68 Java EE 6 et GlassFish 3
peuvent en effet être testées avec une base de données très légère qui n’a pas besoin
de s’exécuter dans un processus distinct (il suffit d’ajouter un fichier jar au class-
path). C’est de cette façon que nous exécuterons notre classe BookTest, en utilisant
le mode intégré de Derby.
Maven utilise deux répertoires différents : l’un pour stocker le code de l’application,
un autre pour les classes de test. La classe BookTest, présentée dans le Listing 2.7,
est placée dans le répertoire src/test/java et teste que le gestionnaire d’entités
peut stocker un livre dans la base de données et le récupérer ensuite.
Listing 2.7 : Classe de test qui crée un livre et récupère tous les livres de la base
de données
public class BookTest {
private static EntityManagerFactory emf;
private static EntityManager em;
private static EntityTransaction tx;
@BeforeClass
public static void initEntityManager() throws Exception {
emf = Persistence.createEntityManagerFactory("chapter02PU");
em = emf.createEntityManager();
}
@AfterClass
public static void closeEntityManager() throws SQLException {
em.close();
emf.close();
}
@Before
public void initTransaction() {
tx = em.getTransaction();
}
@Test
public void createBook() throws Exception {
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java 69
Comme la classe Main, BookTest doit créer une instance EntityManager à l’aide
d’une fabrique EntityManagerFactory. Pour initialiser ces composants, nous nous
servons des fixtures de JUnit 4 : les annotations @BeforeClass et @AfterClass per-
mettent d’exécuter un code une seule fois, avant et après l’exécution de la classe
– c’est donc l’endroit idéal pour créer et fermer une instance EntityManager. L’an-
notation @Before, quant à elle, permet d’exécuter un certain code avant chaque test
– c’est là que nous créons une transaction.
Le cas de test est représenté par la méthode createBook() car elle est précédée de
l’annotation @Test de JUnit. Cette méthode stocke un livre (en appelant EntityMa-
nager.persist()) et vérifie avec assertNotNull que l’identifiant a bien été produit
automatiquement par EclipseLink. En ce cas, la requête nommée findAllBooks est
exécutée et l’on vérifie que la liste renvoyée n’est pas null.
Maintenant que la classe de test est écrite, vous avez besoin d’un autre fichier per-
sistence.xml pour utiliser Derby intégré car le précédent définissait un pilote
JDBC et une URL de connexion Derby en mode serveur réseau. Le fichier src/
test/resources/META-INF/persistence.xml du Listing 2.8 utilise au contraire un
pilote JDBC pour le mode intégré.
<persistence-unit name="chapter02PU"
transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
70 Java EE 6 et GlassFish 3
</provider>
<class>com.apress.javaee6.chapter02.Book</class>
<properties>
<property name="eclipselink.target-database" value="DERBY"/>
<property name="eclipselink.jdbc.driver"
value="org.apache.derby.jdbc.EmbeddedDriver"/>
<property name="eclipselink.jdbc.url"
value="jdbc:derby:chap02DB;create=true"/>
<property name="eclipselink.jdbc.user" value="APP"/>
<property name="eclipselink.jdbc.password" value="APP"/>
<property name="eclipselink.ddl-generation"
value="drop-and-create-tables"/>
<property name="eclipselink.logging.level" value="FINE"/>
</properties>
</persistence-unit>
</persistence>
L’exécution du test est très simple puisqu’il suffit de se reposer sur Maven. Ouvrez
une fenêtre de commande dans le répertoire où se trouve le fichier pom.xml et tapez
la commande suivante :
mvn test
Le niveau de journalisation des traces étant FINE, vous devriez voir apparaître
de nombreuses informations indiquant que Derby crée une base et des tables en
mémoire. Puis la classe BookTest est exécutée et Maven devrait vous informer du
succès du test.
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 9.415 sec
Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]----------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO]----------------------------------------------------------------
[INFO] Total time: 19 seconds
[INFO] Finished [INFO] Final Memory: 4M/14M
[INFO]----------------------------------------------------------------
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 2 Persistance en Java 71
Résumé
Ce chapitre est un tour d’horizon rapide de JPA 2.0. Comme la plupart des autres
spécifications de Java EE 6, JPA utilise une architecture objet simple et abandonne
le modèle composant lourd de son ancêtre (EJB CMP 2.x). Ce chapitre a égale-
ment présenté les entités, qui sont des objets persistants utilisant des métadonnées
exprimées via des annotations ou un fichier XML.
Dans la section "Récapitulatif", nous avons vu comment lancer une application JPA
avec EclipseLink et Derby. Les tests unitaires jouent un rôle important dans les
projets : avec JPA et des bases de données en mémoire comme Derby, la persistance
peut désormais être testée très simplement.
Dans les chapitres suivants, vous en apprendrez plus sur les composants principaux
de JPA. Le Chapitre 3 expliquera comment associer les entités, les relations et l’hé-
ritage à une base de données. Le Chapitre 4 sera consacré à l’API du gestionnaire
d’entités, à la syntaxe de JPQL et à l’utilisation des requêtes et des mécanismes de
verrouillage. Le Chapitre 5, le dernier de cette présentation de JPA, expliquera le
cycle de vie des entités et montrera comment ajouter une logique métier dans les
fonctions de rappel et les écouteurs.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
3
ORM : Object-Relational Mapping
Dans ce chapitre, nous passerons en revue les bases des ORM (Object-Relational
Mapping), qui consistent essentiellement à faire correspondre des entités à des tables
et des attributs à des colonnes. Nous nous intéresserons ensuite à des associations
plus complexes comme les relations, la composition et l’héritage. Un modèle objet
est composé d’objets interagissant ensemble ; or les objets et les bases de données
utilisent des moyens différents pour stocker les informations sur ces relations (via
des pointeurs ou des clés étrangères). Les bases de données relationnelles ne dis-
posent pas naturellement du concept d’héritage et cette association entre objets et
bases n’est par conséquent pas évidente. Nous irons donc dans les détails et présen-
terons des exemples qui montreront comment les attributs, les relations et l’héritage
peuvent être traduits d’un modèle objet vers une base de données.
Les chapitres précédents ont montré que les annotations étaient très utilisées dans
l’édition Entreprise depuis Java EE 5 (essentiellement pour les EJB, JPA et les ser-
vices web). JPA 2.0 poursuit dans cette voie et introduit de nouvelles annotations de
mapping (associations), ainsi que leurs équivalents XML. Même si nous utiliserons
surtout les annotations pour expliquer les différents concepts d’associations, nous
présenterons également les associations au moyen de XML.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
74 Java EE 6 et GlassFish 3
@Id
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
public Book() {
}
// Getters, setters
}
Cet exemple de code issu de l’application CD-BookStore représente une entité Book
dans laquelle on a omis les getters et les setters pour plus de clarté. Comme vous
pouvez le constater, à part les annotations, cette entité ressemble exactement à n’im-
porte quelle classe Java : elle a plusieurs attributs (id, title, price, etc.) de diffé-
rents types (Long, String, Float, Integer et Boolean), un constructeur par défaut et
des getters et setters pour chaque attribut. Les annotations vont permettre d’associer
très simplement cette entité à une table dans une base de données.
Tout d’abord, la classe est annotée avec @javax.persistence.Entity, ce qui permet
au fournisseur de persistance de la reconnaître comme une classe persistance et non
plus comme une simple classe POJO. Puis l’annotation @javax.persistence.Id
définit l’identifiant unique de l’objet. JPA étant destiné à associer des objets à des
tables relationnelles, les objets doivent posséder un identifiant qui sera associé à
une clé primaire. Les autres attributs (title, price, description, etc.) ne sont pas
annotés et seront donc stockés dans la table en appliquant une association standard.
Cet exemple de code ne contient que des attributs mais, comme nous le verrons au
Chapitre 5, une entité peut également avoir des méthodes métier. Notez que cette
entité Book est une classe Java qui n’implémente aucune interface et qui n’hérite d’au-
cune classe. En fait, pour être une entité, une classe doit respecter les règles suivantes :
■■ La classe de l’entité doit être annotée par @javax.persistence.Entity (ou dénotée
comme telle dans le descripteur XML).
■■ L’annotation @javax.persistence.Id doit être utilisée pour désigner une clé
p rimaire simple.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 75
Figure 3.1 <<entity>>
Book
Synchronisation des -id : Long
données entre l’entité -title : String
et la table. -price : Float
-description : String
-nbOfPage : Integer
-illustrations : Boolean
+Book ()
Couche de persistance
Couche base de données
Association
BOOK
+ID bigint Nullable = false
TITLE varchar(255) Nullable = false
PRICE double Nullable = true
DESCRIPTION varchar(2000) Nullable = true
ISBN varchar(255) Nullable = true
NBOFPAGE integer Nullable = true
ILLUSTRATIONS smallint Nullable = true
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
76 Java EE 6 et GlassFish 3
Java EE 5 a introduit l’idée de configuration par exception. Ceci signifie que, sauf
mention contraire, le conteneur ou le fournisseur doivent appliquer les règles par
défaut. En d’autres termes, fournir une configuration est une exception à la règle.
Cette politique permet donc de configurer une application avec un minimum d’effort.
Revenons à l’exemple précédent (celui du Listing 3.1). Sans annotation, l’entité
Book serait traitée comme n’importe quel POJO et ne serait pas persistante – c’est
la règle : sans configuration spéciale, le comportement par défaut s’applique et il
consiste évidemment à considérer que la classe Book est une classe comme les autres.
Comme nous souhaitons modifier ce comportement, nous annotons la classe avec @
Entity. Il en va de même pour l’identifiant : nous avons besoin d’indiquer au four-
nisseur de persistance que cet attribut doit être associé à une clé primaire, et c’est la
raison pour laquelle nous l’annotons avec @Id. Ce type de décision caractérise bien
la politique de configuration par exception : les annotations ne sont pas nécessaires
dans le cas général ; elles ne sont utilisées que pour outrepasser une convention.
Ceci signifie donc que tous les autres attributs de notre classe seront associés selon
les règles par défaut :
■■ Le nom de l’entité est associé à un nom de table relationnelle (l’entité Book sera
donc associée à une table BOOK). Si vous voulez l’associer à une autre table,
vous devrez utiliser l’annotation @Table, comme nous le verrons dans la section
"Associations élémentaires".
■■ Les noms des attributs sont associés à des noms de colonnes (l’attribut id, ou
la méthode getId(), est associé à une colonne ID). Si vous voulez changer ce
comportement, vous devrez utiliser l’annotation @Column.
■■ Ce sont les règles JDBC qui s’appliquent pour associer les types primitifs de
Java aux types de données de la base. Ainsi, un String sera associé à un VAR-
CHAR, un Long à un BIGINT, un Boolean à un SMALLINT, etc. La taille par défaut
d’une colonne associée à un String est de 255 caractères (VARCHAR(255)). Ces
règles par défaut peuvent varier en fonction du SGBDR : un String est associé
à un VARCHAR avec Derby, mais à un VARCHAR2 avec Oracle ; de la même façon,
un Integer est associé à un INTEGER avec Derby, mais à un NUMBER avec Oracle.
Les informations concernant la base de données sous-jacentes sont fournies par
le fichier persistence.xml, que nous étudierons dans la section "Contexte de
persistance" du Chapitre 4.
Selon toutes ces règles, l’entité Book sera donc associée à une table Derby ayant la
structure décrite dans le Listing 3.2.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 77
C’est donc un exemple d’association très simple. Les relations et l’héritage ont
également des règles d’association par défaut, que nous étudierons dans la section
"Association des relations".
La plupart des fournisseurs de persistance, dont EclipseLink, permettent de produire
automatiquement la base de données à partir des entités. Cette fonctionnalité est
tout spécialement pratique lorsque l’on est en phase de développement car, avec
uniquement les règles par défaut, on peut associer très simplement les données en
se contentant des annotations @Entity et @Id. Cependant, la plupart du temps, nous
devrons nous connecter à un SGBDR classique ou suivre des conventions de nom-
mage strictes : c’est la raison pour laquelle JPA définit un nombre important d’anno-
tations (ou leurs équivalents XML) – vous pourrez ainsi personnaliser chaque partie
de l’association (les noms des tables et des colonnes, les clés primaires, la taille des
colonnes, les colonnes NULL ou NOT NULL, etc.).
Associations élémentaires
D’importantes différences existent entre la gestion des données par Java et par un
SGBDR. En Java, nous utilisons des classes pour décrire à la fois les attributs qui
contiennent les données et les méthodes qui accèdent et manipulent ces données.
Lorsqu’une classe a été définie, nous pouvons créer autant d’instances que néces-
saire à l’aide du mot-clé new. Dans un SGBDR, en revanche, seules les données sont
stockées – pas les comportements (exception faite des triggers et des procédures
stockées) –, et la structure du stockage est totalement différente de la structure des
objets puisqu’elle utilise une décomposition en lignes et en colonnes. L’association
d’objets Java à une base de données sous-jacente peut donc être simple et se conten-
ter des règles par défaut ; parfois, cependant, ces règles peuvent ne pas convenir
aux besoins, auquel cas nous sommes obligés de les outrepasser. Les annotations
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
78 Java EE 6 et GlassFish 3
des associations élémentaires permettent de remplacer les règles par défaut pour
la table, les clés primaires et les colonnes, et de modifier certaines conventions de
nommage ou de contenu des colonnes (valeurs non nulles, longueur, etc.).
Tables
La convention établit que les noms de l’entité et de la table sont identiques (une entité
Book est associée à une table BOOK, une entité AncientBook, à une table ANCIENTBOOK,
etc.). Toutefois, si vous le souhaitez, vous pouvez associer vos données à une table
différente, voire associer une même entité à plusieurs tables.
@Table
L’annotation @javax.persistence.Table permet de modifier les règles par défaut
pour les tables. Vous pouvez, par exemple, indiquer le nom de la table dans laquelle
vous voulez stocker vos données, le catalogue et le schéma de la base. Le Listing 3.3
montre comment associer la table T_BOOK à l’entité Book.
@Id
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
public Book() {
}
// Getters, setters
}
INFO
Dans l’annotation @Table, le nom de la table est en minuscules (t_book). Par défaut, la plu-
part des SGBDR lui feront correspondre un nom en majuscules (c’est notamment le cas de
Derby), sauf si vous les configurez pour qu’ils respectent la casse.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 79
@SecondaryTable
Jusqu’à maintenant, nous avons toujours supposé qu’une entité n’était associée qu’à
une seule table, également appelée table primaire. Si l’on a déjà un modèle de don-
nées, en revanche, on voudra peut-être disséminer les données sur plusieurs tables, ou
tables secondaires. Cette annotation permet de mettre en place cette configuration.
@SecondaryTable permet d’associer une table secondaire à une entité, alors que
@SecondaryTables (avec un "s") en associe plusieurs. Vous pouvez distribuer les
données d’une entité entre les colonnes de la table primaire et celles des tables
secondaires en définissant simplement les tables secondaires avec des annotations,
puis en précisant pour chaque attribut la table dans laquelle il devra être stocké (à
l’aide de l’annotation @Column, que nous décrirons dans la section "Attributs"). Le
Listing 3.4 montre comment répartir les attributs d’une entité Address entre une
table primaire et deux tables secondaires.
Listing 3.4 : Les attributs de l’entité Address sont répartis dans trois tables différentes
@Entity
@SecondaryTables({
@SecondaryTable(name = "city"),
@SecondaryTable(name = "country")
})
public class Address {
@Id
private Long id;
private String street1;
private String street2;
@Column(table = "city")
private String city;
@Column(table = "city")
private String state;
@Column(table = "city")
private String zipcode;
@Column(table = "country")
private String country;
Par défaut, les attributs de l’entité Address seraient associés à la table primaire (qui
s’appelle ADDRESS par défaut). L’annotation @SecondaryTables précise qu’il y a deux
tables secondaires : CITY et COUNTRY. Vous devez ensuite indiquer dans quelle table
secondaire stocker chaque attribut (à l’aide de l’annotation @Column(table="city")
ou @Column(table="country")). Le résultat, comme le montre la Figure 3.2, est la
création de trois tables se partageant les différents attributs mais ayant la même clé
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
80 Java EE 6 et GlassFish 3
primaire (afin de pouvoir les joindre). N’oubliez pas que Derby met en majuscules
(CITY) les noms de tables en minuscules (villes).
Figure 3.2 COUNTRY
+#ID bigint Nullable = false
L’entité Address <<entity>> COUNTRY varchar(255) Nullable = true
est associée à trois Address
-id : Long ADDRESS
tables. +ID bigint Nullable = false
-street1 : String
STREET1 varchar(255) Nullable = true
-street2 : String
STREET2 varchar(255) Nullable = true
-city : String
-zipcode : String CITY
-country : String +#ID bigint Nullable = false
+Address() CITY varchar(255) Nullable = true
STATE varchar(255) Nullable = true
ZIPCODE varchar(255) Nullable = true
Comme vous l’avez sûrement compris, la même entité peut contenir plusieurs anno-
tations. Si vous voulez renommer la table primaire, vous pouvez donc ajouter une
annotation @Table. C’est ce que nous faisons dans le Listing 3.5.
INFO
Vous devez être conscient de l’impact des tables secondaires sur les performances car, à
chaque fois que vous accéderez à une entité, le fournisseur de persistance devra accéder
à plusieurs tables et les joindre. En revanche, les tables secondaires peuvent être intéres-
santes si vous avez des attributs de grande taille, comme des BLOB (Binary Large Objects),
car vous pourrez les isoler dans une table à part.
Clés primaires
Dans les bases de données relationnelles, une clé primaire identifie de façon unique
chaque ligne d’une table. Cette clé peut être une simple colonne ou un ensemble
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 81
de colonnes. Les clés primaires doivent évidemment être uniques (et la valeur NULL
n’est pas autorisée). Des exemples de clés primaires classiques sont un numéro de
client, un numéro de téléphone, un numéro de commande et un ISBN. JPA exige
que les entités aient un identifiant associé à une clé primaire qui suivra les mêmes
règles : identifier de façon unique une entité à l’aide d’un simple attribut ou d’un
ensemble d’attributs (clé composée). Une fois affectée, la valeur de la clé primaire
d’une entité ne peut plus être modifiée.
@Id et @GeneratedValue
Une clé primaire simple (non composée) doit correspondre à un seul attribut de la
classe de l’entité. L’annotation @Id que nous avons déjà rencontrée sert à indiquer
une clé simple. L’attribut qui servira de clé doit être de l’un des types suivants :
■■ Types primitifs de Java. byte, int, short, long, char.
■■ Classes enveloppes des types primitifs. Byte, Integer, Short, Long, Character.
■■ Tableau de types primitifs ou de classes enveloppes. int[], Integer[], etc.
■■ Chaîne, nombre ou dates. java.lang.String, java.math.BigInteger, java.
util.Date, java.sql.Date.
Lorsque l’on crée une entité, la valeur de cet identifiant peut être produite manuel
lement par l’application, ou automatiquement par le fournisseur de persistance si
l’on précise l’annotation @GeneratedValue. Celle-ci peut avoir quatre valeurs :
■■ SEQUENCE et IDENTITY précisent, respectivement, une séquence SQL de la base de
données ou une colonne identité.
■■ TABLE demande au fournisseur de persistance de stocker le nom de la séquence
et sa valeur courante dans une table et d’incrémenter cette valeur à chaque fois
qu’une nouvelle instance de l’entité est stockée dans la base. Derby, par exemple,
crée une table SEQUENCE de deux colonnes : une pour le nom de la séquence (qui
est arbitraire) et l’autre pour la valeur (un entier incrémenté automatiquement
par Derby).
■■ AUTO demande que la génération d’une clé s’effectue automatiquement par la
base de données sous-jacente, qui est libre de choisir la technique la plus appro-
priée. C’est la valeur par défaut de l’annotation @GeneratedValue.
En l’absence de @GeneratedValue, l’application est responsable de la production des
identifiants à l’aide d’un algorithme qui devra renvoyer une valeur unique. Le code
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
82 Java EE 6 et GlassFish 3
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 83
@EmbeddedId
Comme nous le verrons plus loin, JPA utilise différentes sortes d’objets intégrés
(embedded). Pour faire court, un objet intégré n’a pas d’identité (il n’a pas de clé
primaire) et ses attributs sont stockés dans des colonnes de la table associée à l’entité
qui le contient.
Le Listing 3.7 présente la classe NewsId comme une classe intégrable (embeddable).
Il s’agit simplement d’un objet intégré (annoté avec @Embeddable) composé de deux
attributs (title et language). Cette classe doit avoir un constructeur par défaut, des
getters, des setters et redéfinir equals() et hashCode(). Vous remarquerez que la
classe n’a pas d’identité par elle-même (aucune annotation @Id) : c’est ce qui carac-
térise un objet intégrable.
L’entité News, présentée dans le Listing 3.8, doit maintenant intégrer la classe de clé
primaire NewsId avec l’annotation @EmbeddedId. Toutes les annotations @EmbeddedId
doivent désigner une classe intégrable annotée par @Embeddable.
@EmbeddedId
private NewsId id;
private String content;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
84 Java EE 6 et GlassFish 3
Dans le prochain chapitre, nous verrons plus précisément comment retrouver les
entités à l’aide de leur clé primaire, mais le Listing 3.9 présente le principe général :
la clé primaire étant une classe avec un constructeur, vous devez d’abord l’instancier
avec les valeurs qui forment la clé, puis passer cet objet au gestionnaire d’entités
(l’attribut em).
Listing 3.9 : Code simplifié permettant de retrouver une entité à partir de sa clé primaire
composée
NewsId cle = new NewsId("Richard Wright est mort", "FR")
News news = em.find(News.class, cle);
@IdClass
L’autre méthode pour déclarer une clé primaire composée consiste à utiliser l’an-
notation @IdClass. Cette approche est différente de la précédente car, ici, chaque
attribut de la classe de la clé primaire doit également être déclaré dans la classe
entité et annoté avec @Id.
La clé primaire de l’exemple du Listing 3.10 est maintenant un objet classique qui
ne nécessite aucune annotation.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 85
L’approche @IdClass est plus sujette aux erreurs car vous devez définir chaque attri-
but de la clé primaire à la fois dans la classe de la clé primaire et dans l’entité, en
vous assurant d’utiliser les mêmes noms et les mêmes types. L’avantage est que vous
n’avez pas besoin de modifier le code de la classe de la clé primaire. Vous pourriez,
par exemple, utiliser une classe existante que vous n’avez pas le droit de modifier.
La seule différence visible est la façon dont vous ferez référence à l’entité dans
JPQL. Dans le cas de @IdClass, vous utiliseriez un code comme celui-ci :
select n.title from News n
Attributs
Une entité doit posséder une clé primaire (simple ou composée) pour être iden-
tifiable dans une base de données relationnelle. Elle dispose également de toutes
sortes d’attributs qui forment son état, qui doit également être associé à la table. Cet
état peut contenir quasiment tous les types Java que vous pourriez vouloir associer :
■■ types primitifs de Java (int, double, float, etc.) et leurs classes enveloppes
(Integer, Double, Float, etc.) ;
■■ tableaux d’octets ou de caractères (byte[], Byte[], char[], Character[]) ;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
86 Java EE 6 et GlassFish 3
@Basic
L’annotation @javax.persistence.Basic (voir Listing 3.13) est le type d’associa-
tion le plus simple avec une colonne d’une table car il redéfinit les options de base
de la persistance.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 87
FetchType.LAZY) pour que ces données ne soient lues dans la base que lorsqu’elles
seront vraiment nécessaires (lorsque nous accéderons à l’attribut wav via son getter,
par exemple).
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private Float duration;
@Basic(fetch = FetchType.LAZY)
@Lob
private byte[] wav;
@Basic(optional = true)
private String description;
Notez que l’attribut wav de type byte[] est également annoté par @Lob afin que sa
valeur soit stockée comme un LOB (Large Object) – les colonnes pouvant stocker
ces types de gros objets nécessitent des appels JDBC spéciaux pour être accessibles
à partir de Java. Pour en informer le fournisseur, il faut donc ajouter une annotation
@Lob à l’association de base.
@Column
L’annotation @javax.persistence.Column définit les propriétés d’une colonne.
Grâce à elle, nous pouvons modifier le nom de la colonne (qui, par défaut, est le
même que celui de l’attribut), sa taille et autoriser (ou non) la colonne à être NULL,
unique, modifiable ou utilisable dans une instruction INSERT de SQL. Le Listing 3.15
montre les différents éléments de son API, avec leurs valeurs par défaut.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
88 Java EE 6 et GlassFish 3
Pour redéfinir l’association par défaut de l’entité Book initiale, nous pouvons utiliser
de différentes façons l’annotation @Column (voir Listing 3.16). Ici, nous modifions
les noms des colonnes associées aux attributs title et nbOfPage, pour lesquelles
nous n’autorisons pas les valeurs NULL ; nous précisons également la longueur de la
colonne associée à description.
L’entité Book du Listing 3.6 sera donc associée à la table définie dans le Listing 3.17.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 89
@Temporal
En Java, vous pouvez utiliser java.util.Date et java.util.Calendar pour stocker
des dates puis obtenir des représentations différentes, comme une date, une heure ou
des millisecondes. Pour utiliser une date avec un ORM, vous pouvez utiliser l’anno-
tation @javax.persistence.Temporal, qui a trois valeurs possibles : DATE, TIME ou
TIMESTAMP. Le Listing 3.18, par exemple, définit une entité Customer contenant une
date de naissance et un attribut technique qui stocke le moment exact où ce client a
été ajouté au système (à l’aide d’une valeur TIMESTAMP).
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
90 Java EE 6 et GlassFish 3
@Transient
Avec JPA, tous les attributs d’une classe annotée par @Entity sont automatiquement
associés à une table. Si vous ne souhaitez pas associer un attribut particulier, utilisez
l’annotation @javax. persistence.Transient. Ajoutons, par exemple, un attribut
age à l’entité Customer (voir Listing 3.20) : l’âge pouvant être automatiquement
calculé à partir de la date de naissance, il n’est pas nécessaire de stocker cet attribut,
qui peut donc être déclaré comme transitoire.
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Transient
private Integer age;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
@Enumerated
Java SE 5 a introduit les énumérations, qui sont si souvent utilisées qu’elles font
partie de la vie du développeur. Les valeurs d’une énumération sont des constantes
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 91
a uxquelles est implicitement associé un numéro déterminé par leur ordre d’appari-
tion dans l’énumération. Ce numéro ne peut pas être modifié en cours d’exécution
mais sert à stocker la valeur du type énuméré dans la base de données. Le Listing 3.21
montre une énumération de types de cartes de crédit.
Les numéros affectés lors de la compilation aux valeurs de ce type énuméré seront 0
pour VISA, 1 pour MASTER_CARD et 2 pour AMERICAN_EXPRESS. Par défaut, les fournis-
seurs de persistance associeront ce type énuméré à la base de données en supposant
que la colonne est de type Integer. Le Listing 3.22 montre une entité CreditCard
qui utilise l’énumération précédente avec une association par défaut.
@Id
private String number;
private String expiryDate;
private Integer controlNumber;
private CreditCardType creditCardType;
Les règles par défaut feront que l’énumération sera associée à une colonne de
type entier et tout ira bien. Imaginons maintenant que nous ajoutions une nou-
velle constante au début de l’énumération. L’affectation des numéros dépendant de
l’ordre d’apparition des constantes, les valeurs déjà stockées dans la base de don-
nées ne correspondront plus à l’énumération. Une meilleure solution consiste donc
à stocker le nom de la constante à la place de son numéro d’ordre. C’est ce que fait
le Listing 3.23 à l’aide de l’annotation @Enumerated avec la valeur STRING (sa valeur
par défaut est ORDINAL).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
92 Java EE 6 et GlassFish 3
@Id
private String number;
private String expiryDate;
private Integer controlNumber;
@Enumerated(EnumType.STRING)
private CreditCardType creditCardType;
Types d’accès
Pour l’instant, nous n’avons vu que des annotations de classes (@Entity ou @Table)
et d’attributs (@Basic, @Column, @Temporal, etc.), mais les annotations qui s’appli-
quent à un attribut (accès au champ) peuvent également être placées sur la méthode
getter correspondante (accès à la propriété). L’annotation @Id, par exemple, peut
être affectée à l’attribut id ou à la méthode getId(). Il s’agit surtout ici d’une ques-
tion de goût personnel et nous préférons utiliser les accès aux propriétés (getters
annotés) car nous trouvons le code plus lisible : nous pouvons lire rapidement les
attributs d’une entité sans être perturbés par les annotations (dans ce livre, toutefois,
nous avons décidé d’annoter les attributs afin d’éviter de devoir alourdir les listings
par les codes des getters). Dans certains cas comme l’héritage, cependant, ce n’est
plus simplement une affaire de goût car cela peut avoir un impact sur l’association.
INFO
Java définit un champ comme un attribut d’instance. Une propriété est un champ avec un
accesseur (getter et setter) respectant la convention des Java beans (le nom de la méthode
d’accès est de la forme getXXX, setXXX ou isXXX si elle renvoie un Boolean).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 93
@Id @GeneratedValue
private Long id;
@Column(nom = "first_name", nullable = false, length = 50)
private String firstName;
@Column(nom = "last_name", nullable = false, length = 50)
private String lastName;
private String email;
@Column(nom = "phone_number", length = 15)
private String phoneNumber;
Lorsque l’on utilise un accès aux propriétés, comme dans le Listing 3.25, le fournis-
seur de persistance accède à l’état persistant via les méthodes getter et l’association
repose sur ces getters plutôt que sur les attributs. Tous les getters non annotés par
@Transient sont persistants.
// Constructeur...
@Id @GeneratedValue
public Long getId() {
return id;
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
94 Java EE 6 et GlassFish 3
En termes d’associations, les deux entités des Listings 3.24 et 3.25 sont en tout point
identiques car les noms des attributs sont les mêmes que ceux des getters. Au lieu
d’utiliser le type d’accès par défaut, vous pouvez également le préciser explicite-
ment avec l’annotation @javax.persistence.Access.
Cette annotation a deux valeurs possibles, FIELD ou PROPERTY, et peut être utilisée sur
l’entité elle-même et/ou sur chaque attribut ou getter. Lorsque @Access(AccessType.
FIELD) est appliqué à l’entité, par exemple, seules les annotations d’associations
placées sur les attributs seront prises en compte par le fournisseur de persistance.
Il est également possible d’annoter avec @Access(AccessType.PROPERTY) des getters
individuels pour effectuer des accès par propriété.
Les types d’accès explicites peuvent être très pratiques (avec les objets intégrables
et l’héritage, par exemple), mais leur mélange provoque souvent des erreurs. Le Lis-
ting 3.26 montre ce qui pourrait se passer si vous mélangez ces deux types d’accès.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 95
@Id @GeneratedValue
private Long id;
@Column(nom = "first_name", nullable = false, length = 50)
private String firstName;
@Column(nom = "last_name", nullable = false, length = 50)
private String lastName;
private String email;
@Column(nom = "phone_number", length = 15)
private String phoneNumber;
@Access(AccessType.PROPERTY)
@Column(nom = "phone_number", length = 555)
public String getPhoneNumber() {
return phoneNumber;
}
Cet exemple définit explicitement le type d’accès FIELD au niveau de l’entité, ce qui
indique au gestionnaire de persistance qu’il ne doit traiter que les annotations sur les
attributs. phoneNumber est annoté par @Column, qui limite sa taille à 15 caractères. En
lisant ce code, on pourrait s’attendre à ce que le type de la colonne correspondante
dans la base de données soit VARCHAR(15), mais ce ne sera pas le cas. En effet, le
type d’accès a été explicitement modifié pour la méthode getter getPhoneNumber() :
la longueur d’un numéro de téléphone dans la base sera donc de 555 caractères. Ici,
l’AccessType.FIELD de l’entité a été écrasé par AccessType.PROPERTY et la colonne
sera donc de type VARCHAR(555).
Les collections sont très utilisées en Java. Dans cette section, nous étudierons les rela-
tions entre entités (qui peuvent être des collections d’entités) : essentiellement, ceci
signifie qu’une entité contient une collection d’autres entités ou d’objets intégrables.
En terme d’association, chaque entité est associée à sa propre table et l’on crée des
références entre les clés primaires et les clés étrangères. Comme vous le savez, une
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
96 Java EE 6 et GlassFish 3
entité est une classe Java avec une identité et de nombreux autres attributs : mais
comment faire pour stocker une simple collection de types Java comme des String
et/ou des Integer ? Depuis JPA 2.0, il n’est plus nécessaire de créer une classe
distincte car on dispose des annotations @ElementCollection et @CollectionTable.
L’annotation @ElementCollection indique qu’un attribut de type java.util.Collec-
tion contient des types Java tandis que @CollectionTable permet de modifier les
détails de la table de la collection – son nom, par exemple. Si cette dernière est omise,
le nom de la table sera formé par la concaténation du nom de l’entité conteneur et de
celui de l’attribut collection, séparés par un blanc souligné ("_", ou underscore).
Utilisons une nouvelle fois l’entité Book et ajoutons-lui un attribut pour stocker des
tags. De nos jours, les tags et les nuages de tags sont partout et sont très pratiques
pour trier les données : dans notre exemple, nous voulons nous en servir pour décrire
un livre et le retrouver rapidement. Un tag n’étant qu’une simple chaîne, l’entité Book
contiendra donc une collection de chaînes pour stocker ces informations, comme le
montre le Listing 3.27.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 97
BOOK
+ID bigint Nullable = false
TITLE varchar(255) Nullable = false TAG
PRICE double Nullable = true #BOCK ID bigint Nullable = false
DESCRIPTION varchar(2000) Nullable = true VALUE varchar(255) Nullable = true
ISBN varchar(255) Nullable = true
NBOFPAGE integer Nullable = true
ILLUSTRATIONS smallint Nullable = true
Figure 3.3
Relation entre les tables BOOK et TAG.
INFO
Ces annotations n’existaient pas en JPA 1.0. Cependant, il était possible de stocker une liste
de types primitifs dans la base de données sous la forme d’un BLOB. En effet, java.util.Ar-
rayList implémente Serializable et JPA sait associer automatiquement des objets Seria-
lizable à des BLOB. En revanche, lorsque l’on utilisait une collection java.util.List, on
obtenait une exception car List n’implémente pas Serializable. L’annotation @Element-
Collection est donc un moyen plus élégant et plus pratique de stocker les listes de types
primitifs car leur stockage sous un format binaire opaque aux requêtes les rend inaccessibles.
Comme les collections, les tables de hachage sont très utiles pour le stockage des
données. Avec JPA 1.0, on ne pouvait pas en faire grand-chose en terme d’ORM.
Désormais, les tables de hachage peuvent utiliser n’importe quelle combinaison de
types de base, d’objets intégrables et d’entités comme clés ou comme valeurs : ceci
apporte beaucoup de souplesse. Pour l’instant, intéressons-nous aux hachages qui
utilisent des types de base.
Lorsqu’un hachage emploie des types de base, vous pouvez vous servir des anno-
tations @ElementCollection et @CollectionTable exactement comme nous venons
de le voir pour les collections. En ce cas, les données du hachage sont stockées dans
une table collection.
Prenons l’exemple d’un CD contenant un certain nombre de pistes (voir Listing 3.28).
Une piste peut être considérée comme un titre et une position (la première piste de
l’album, la seconde, etc.). Vous pourriez alors utiliser un hachage de pistes utilisant
un entier pour représenter la position (une clé du hachage) et une chaîne pour repré-
senter le titre (la valeur de cette clé dans le hachage).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
98 Java EE 6 et GlassFish 3
@Id
@GeneratedValue
private Long id;
private String title;
private Float price;
private String description;
@Lob
private byte[] cover;
@ElementCollection
@CollectionTable(name="track")
@MapKeyColumn (name = "position")
@Column(name = "title")
private HashMap<Integer, String> tracks;
CD
+ID bigint Nullable = false
TITLE varchar(255) Nullable = true TRACK
PRICE double Nullable = true #CD ID bigint Nullable = false
DESCRIPTION varchar(255) Nullable = true POSITION integer Nullable = true
COVER blob(64000) Nullable = true TITLE varchar(255) Nullable = true
Figure 3.4
Relation entre les tables CD et TRACK.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 99
Maintenant que vous connaissez mieux les bases des associations avec les anno-
tations, étudions celles qui utilisent XML. Si vous avez déjà utilisé un framework
ORM comme Hibernate, vous savez déjà comment associer vos entités via un fichier
de descripteurs de déploiement XML. Depuis le début de ce chapitre, nous n’avons
pourtant pas utilisé une seule ligne de XML – uniquement des annotations. Nous
n’entrerons pas trop dans les détails des associations XML car nous avons décidé de
nous concentrer sur les annotations (parce qu’elles sont plus simples à utiliser dans
un livre et parce que la plupart des développeurs les préfèrent à XML). Considérez
simplement que toutes les annotations que nous avons présentées dans ce chapitre
ont un équivalent XML : cette section serait énorme si nous les présentions toutes.
Nous vous renvoyons donc au Chapitre 11 de la spécification JPA 2.0, qui présente
en détail tous les marqueurs XML.
Les descripteurs de déploiement XML sont une alternative aux annotations. Bien
que chaque annotation ait un marqueur XML équivalent et vice versa, il y a toute-
fois une différence car les marqueurs XML ont priorité sur les annotations : si vous
annotez un attribut ou une entité avec une certaine valeur et que vous déployiez en
même temps le descripteur XML correspondant avec une valeur différente, celle de
l’annotation sera ignorée.
Quand utiliser les annotations plutôt que XML et pourquoi ? C’est avant tout une
question de goût car les deux méthodes ont exactement le même effet. Lorsque les
métadonnées sont vraiment couplées au code (une clé primaire, par exemple), les
annotations sont judicieuses car, en ce cas, les métadonnées ne sont qu’un autre
aspect du programme. D’autres types de métadonnées comme la longueur des
colonnes ou autres détails concernant le schéma peuvent en revanche dépendre de
l’environnement de déploiement (le schéma de la base peut, par exemple, varier
entre les environnements de développement, de test et de production). En ce cas, il
est préférable de les exprimer à l’aide de descripteurs de déploiement externes (un
par environnement), afin de ne pas devoir modifier le code.
Revenons à notre entité Book. Imaginons que nous travaillons dans deux environ-
nements : nous voulons associer l’entité à la table BOOK dans l’environnement de
développement et à la table BOOK_XML_MAPPING dans celui de test. La classe ne sera
annotée que par @Entity (voir Listing 3.29) et ne contiendra pas d’information sur la
table à laquelle elle est associée (elle ne contiendra donc pas d’annotation @Table).
L’annotation @Id définit une clé primaire produite automatiquement et @Column fixe
la taille de la description à 500 caractères.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
100 Java EE 6 et GlassFish 3
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private Float price;
@Column(length = 500)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
<entity class="com.apress.javaee6.chap03.Livre">
<table name="book_xml_mapping"/>
<attributes>
<basic name="title">
<column name="book_title" nullable="false"
updatable="false"/>
</basic>
<basic name="description">
<column length="2000"/>
</basic>
<basic name="nbOfPage">
<column name="nb_of_page" nullable="false"/>
</basic>
</attributes>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 101
</entity>
</entity-mappings>
Il ne faut jamais oublier que XML a priorité sur les annotations. Même si l’attribut
description est annoté par @Column(length = 500), la longueur de la colonne uti-
lisée sera celle définie dans le fichier book_mapping.xml, c’est-à-dire 2 000. Pensez
à toujours consulter le descripteur de déploiement XML en cas de doute.
La fusion des métadonnées XML et des métadonnées des annotations fera que l’en-
tité Book sera finalement associée à la table BOOK_XML_MAPPING, dont la structure est
définie dans le Listing 3.31.
Il ne manque plus qu’une information pour que ceci fonctionne : vous devez réfé-
rencer le fichier book_mapping.xml dans le fichier persistence.xml à l’aide d’un
marqueur <mapping-file>. Le fichier persistence.xml définit le contexte de persis-
tance de l’entité et la base de données à laquelle elle sera associée : le fournisseur
de contenu a absolument besoin de ce fichier pour pouvoir retrouver les associations
XML externes. Déployez l’entité Book avec ces deux fichiers XML (placés dans le
répertoire META-INF), et c’est fini (voir Listing 3.32).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
102 Java EE 6 et GlassFish 3
Objets intégrables
Dans la section "Clés primaires composées" plus haut dans ce chapitre, nous avons
rapidement vu comment une classe pouvait être intégrée pour servir de clé primaire
avec l’annotation @EmbeddedId. Les objets intégrables sont des objets qui n’ont pas
d’identité persistante par eux-mêmes. Une entité peut contenir des collections d’ob-
jets intégrables ainsi qu’un simple attribut d’une classe intégrable : dans les deux
cas, ils seront stockés comme faisant partie de l’entité et partageront son identité.
Ceci signifie que chaque attribut de l’objet intégré est associé à la table de l’entité.
Il s’agit donc d’une relation de propriété stricte (une composition) : quand l’entité
est supprimée, l’objet intégré disparaît également.
Cette composition entre deux classes passe par des annotations. La classe incluse
utilise @Embeddable et l’entité qui inclut utilise @Embedded. Prenons l’exemple d’un
client possédant un identifiant, un nom, une adresse e-mail et une adresse. Tous ces
attributs pourraient se trouver dans une entité Customer (voir Listing 3.34 un peu
plus loin) mais, pour des raisons de modélisation, ils sont répartis en deux classes :
Customer et Address. Cette dernière n’ayant pas d’identité propre mais étant simple-
ment une composante de l’état de Customer, c’est une bonne candidate au statut de
classe intégrable (voir Listing 3.33).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 103
@Embeddable indique qu’Address peut être intégrée dans une classe entité (ou dans
une autre classe intégrable). À l’autre extrémité de la composition, l’entité Customer
doit utiliser l’annotation @Embedded pour indiquer qu’Address est un attribut per-
sistant qui sera stocké comme composante interne et qu’il partage son identité (voir
Listing 3.34).
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Embedded
private Address address;
Chaque attribut d’Address est associé à la table de l’entité Customer. Il n’y aura donc
qu’une seule table qui aura la structure définie dans le Listing 3.35. Comme nous le ver-
rons plus loin dans la section "Clés primaires composées", les entités peuvent redéfinir
les attributs des objets qu’elles intègrent (avec l’annotation @AttributeOverrides).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
104 Java EE 6 et GlassFish 3
Le type d’accès d’une classe intégrable est déterminé par celui de la classe entité
dans laquelle elle est intégrée. Si cette entité utilise explicitement un type d’accès
par propriété, l’objet intégrable utilisera implicitement un accès par propriété aussi.
Vous pouvez préciser un type d’accès différent pour une classe intégrable au moyen
de l’annotation @Access.
Dans le Listing 3.36, l’entité Customer et la classe Address (voir Listing 3.37) utili-
sent des types d’accès différents.
@Id @GeneratedValue
private Long id;
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;
@Column(name = "last_name", nullable = false, length = 50)
private String lastName;
private String email;
@Column(name = "phone_number", length = 15)
private String phoneNumber;
@Embedded
private Address address;
// Constructeurs
@Column(nullable = false)
public String getStreet1() {
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 105
return street1;
}
public void setStreet1(String street1) {
this.street1 = street1;
}
@Column(length = 3)
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
Il est fortement conseillé de configurer le type d’accès des classes intégrables afin
d’éviter les erreurs d’association qui pourraient se produire lorsqu’une telle classe
est intégrée dans plusieurs entités. Étendons notre modèle en ajoutant une entité
Order (voir Figure 3.5). La classe Address est maintenant intégrée à la fois par
Customer (pour représenter l’adresse personnelle du client) et dans Order (pour
représenter l’adresse de livraison).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
106 Java EE 6 et GlassFish 3
Chaque entité définit un type d’accès différent : Customer utilise un accès par champ
et Order, un accès par propriété. Le type d’accès d’un objet intégrable étant déter-
miné par celui de la classe entité dans laquelle il est déclaré, Address sera associée
de deux façons différentes, ce qui peut poser des problèmes. Pour les éviter, le type
d’accès d’Address doit être indiqué explicitement.
INFO
Les types d’accès explicites sont également très utiles avec l’héritage. Par défaut, les entités
filles héritent du type d’accès de leur entité parente. Dans une hiérarchie d’entités, il est
cependant possible d’accéder à chacune différemment des autres : l’ajout d’une annotation
@Access permet de redéfinir localement le type d’accès par défaut utilisé dans la hiérarchie.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 107
Une relation a également une cardinalité. Chaque extrémité peut préciser le nombre
d’objets impliqués dans cette relation. Le diagramme UML de la Figure 3.8 indique,
par exemple, qu’une instance de Class1 est en relation avec zéro ou plusieurs
instances de Class2.
Dans le monde relationnel, les choses sont différentes puisque, à proprement parler,
une base de données relationnelle est un ensemble de relations (également appelées
tables) : tout est modélisé sous forme de table – pour modéliser une relation, vous ne
disposez ni de listes, ni d’ensembles, ni de tables de hachage : vous n’avez que des
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
108 Java EE 6 et GlassFish 3
tables. Une relation entre deux classes Java sera représentée dans la base de données
par une référence à une table qui peut être modélisée de deux façons : avec une clé
étrangère (une colonne de jointure) ou avec une table de jointure.
À titre d’exemple, supposons qu’un client n’ait qu’une seule adresse, ce qui implique
une relation 1–1. En Java, la classe Customer aurait donc un attribut Address ; dans
le monde relationnel, vous pourriez avoir une table CUSTOMER pointant vers une table
ADDRESS via une clé étrangère, comme le montre la Figure 3.9.
Address
Customer Clé primaire Street City Country
Clé primaire Firstname Lastname Clé étrangère 11 Aligre Paris France
1 James Rorisson 11 12 Balham London UK
2 Dominic Johnson 12 13 Alfama Lisbon Portugal
3 Maca Macaron 13
Figure 3.9
Une relation entre deux tables utilisant une colonne de jointure.
Address
Customer Clé primaire Street City Country
Clé primaire Firstname Lastname Clé étrangère 11 Aligre Paris France
1 James Rorisson 11 12 Balham London UK
2 Dominic Johnson 12 13 Alfama Lisbon Portugal
3 Maca Macaron 13
Table de jointure
Customer Pk Address Pk
1 11
2 12
3 13
Figure 3.10
Relation utilisant une table de jointure.
On n’utilise pas une table de jointure pour représenter une relation 1–1 car cela
pourrait avoir des conséquences sur les performances (il faudrait toujours accéder à
la troisième table pour obtenir l’adresse d’un client) ; elles sont généralement réser-
vées aux relations 1–N ou N–M. Comme nous le verrons dans la section suivante,
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 109
JPA utilise ces deux méthodes pour associer les relations entre les objets à une base
de données.
Cardinalité Direction
1–1 Unidirectionnelle
1–1 Bidirectionnelle
1–N Unidirectionnelle
N–1/1–N Bidirectionnelle
N–1 Unidirectionnelle
N–M Unidirectionnelle
N–M Bidirectionnelle
Vous pouvez constater que unidirectionnel et bidirectionnel sont des concepts répé-
titifs qui s’appliquent de la même façon à toutes les cardinalités. Vous verrez bientôt
la différence entre les relations unidirectionnelles et bidirectionnelles, puis com-
ment implémenter certaines de ces combinaisons, dont nous ne décrirons qu’un
sous-ensemble : les expliquer toutes serait répétitif. L’important est de comprendre
comment traduire la cardinalité et la direction en relations.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
110 Java EE 6 et GlassFish 3
Unidirectionnelle et bidirectionnelle
Du point de vue de la modélisation objet, la direction entre les classes est naturelle.
Dans une relation unidirectionnelle, un objet A pointe uniquement vers un objet B
alors que, dans une relation bidirectionnelle, ils se font mutuellement référence.
Cependant, comme le montre l’exemple suivant d’un client et de son adresse, un peu
de travail est nécessaire lorsque l’on veut représenter une relation bidirectionnelle
dans une base de données.
Dans une relation unidirectionnelle, une entité Customer a un attribut de type Address
(voir Figure 3.11). Cette relation ne va que dans un seul sens : on dit que le client est
le propriétaire de la relation. Du point de vue de la base de données, ceci signifie que
la table CUSTOMER contiendra une clé étrangère (une colonne de jointure) pointant
vers la table ADDRESS. Par ailleurs, le propriétaire de la relation peut personnaliser la
traduction de cette relation : si vous devez modifier le nom de la clé étrangère, par
exemple, cette annotation aura lieu dans l’entité Customer (le propriétaire).
Figure 3.11 Address
Customer -id : Long
Relation unidirectionnelle
-id : Long -sreet1 : String
entre Customer -firstname : String -street2 : String
et Address. -lastname : String -city : String
-email : String -state : String
-phoneNumber : String -zipcode : String
-country : String
Comme on l’a mentionné précédemment, les relations peuvent également être bidi-
rectionnelles. Pour naviguer entre Address et Customer, nous devons ajouter un
attribut Customer à l’entité Address (voir Figure 3.12). Notez que les attributs repré-
sentant une relation n’apparaissent pas dans les diagrammes UML.
Figure 3.12 Address
Customer -id : Long
Relation bidirectionnelle -id : Long -street1 : String
entre Customer -firstName : String -street2 : String
et Address. -lastName : String -city : String
-email : String -state : String
-phoneNumber : String -zipcode : String
-country : String
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 111
Figure 3.13 Address
Customer -id : Long
Relation bidirectionnelle -id : Long -street1 : String
représentée par -firstName : String -street2 : String
deux relations -lastName : String -city : String
unidirectionnelles. -email : String -state : String
-phoneNumber : String -zipcode : String
-country : String
Comment associer tout cela à une base de données ? Qui est le propriétaire de cette
relation bidirectionnelle ? À qui appartient l’information sur la colonne ou la table
de jointure ? Si les relations unidirectionnelles ont un côté propriétaire, les bidi-
rectionnelles ont à la fois un côté propriétaire et un côté opposé, qui doivent être
indiqués explicitement par l’élément mappedBy des annotations @OneToOne, @OneTo-
Many et @ManyToMany. mappedBy identifie l’attribut propriétaire de la relation ; il est
obligatoire pour les relations bidirectionnelles.
Pour illustrer tout ceci, comparons du code Java à sa traduction dans la base de don-
nées. Comme vous pouvez le constater dans la partie gauche de la Figure 3.14, les
deux entités pointent l’une vers l’autre au moyen d’attributs : Customer possède un
attribut address annoté par @OneToOne et l’entité Address a un attribut customer éga-
lement annoté. Dans la partie droite de cette figure se trouvent les tables C USTOMER
et ADDRESS. CUSTOMER est la table propriétaire de la relation car elle contient la clé
étrangère vers ADDRESS.
CUSTOMER
@Entity +ID bigint Nullable false
public class C { LASTNAME varchar(255) Nu lable true
PHONENUMBER varchar(255) Nu lable true
@Id @GeneratedValue EMAIL varchar(255) Nu lable true
private Long i ; FIRSTNAME varchar(255) Nu lable true
private String firstName; #ADDRESS FK bigint Nu lable true
private String lastName;
private String email;
private String phoneNumber;
@On T O
@JoinColumn(name = " s k")
private Address address;
}
ADDRESS
@Entity +ID bigint Nullable false
public class Address { STREET2 varchar(255) Nullable true
STREET1 varchar(255) Nullable true
@Id @GeneratedValue ZIPCODE varchar(255) Nullable true
private Long id; STATE varchar(255) Nullable true
private String street1; COUNTRY varchar(255) Nullable true
private String street2; CITY varchar(255) Nullable true
private String city;
private String state;
private String zipcode;
private String country;
@ n ( p d = " d s")
private Customer customer;
}
Figure 3.14
Code de Customer et Address avec leur correspondance dans la base de données.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
112 Java EE 6 et GlassFish 3
L’entité Address utilise l’élément mappedBy de son annotation @OneToOne. Ici, map-
pedBy indique que la colonne de jointure (address) est déclarée à l’autre extrémité
de la relation. De son côté, l’entité Customer définit la colonne de jointure avec
l’annotation @JoinColumn et renomme la clé étrangère en address_fk. Customer est
l’extrémité propriétaire de la relation et, en tant que telle, elle est la seule à définir
l’association de la colonne de jointure. Address est l’extrémité opposée et c’est donc
la table de l’entité propriétaire qui contient la clé étrangère (la table CUSTOMER a une
colonne ADDRESS_FK).
Il existe un élément mappedBy pour les annotations @OneToOne, @OneToMany et @Many-
ToMany, mais pas pour @ManyToOne.
INFO
Si vous connaissez Hibernate, vous pouvez considérer que l’élément mappedBy de JPA est
l’équivalent de l’attribut inverse de Hibernate, qui indique l’extrémité à ignorer dans une
relation.
@OnetoOne unidirectionnelle
Une relation 1–1 unidirectionnelle entre deux entités a une référence de cardina-
lité 1 qui ne peut être atteinte que dans une seule direction. Reprenons l’exemple
d’un client et de son adresse en supposant qu’il n’a qu’une seule adresse (cardina-
lité 1). Il faut pouvoir naviguer du client (la source) vers l’adresse (la cible) pour
savoir où habite le client. Dans le modèle de la Figure 3.15, nous n’avons pas
besoin de faire le trajet inverse (on n’a pas besoin de savoir quel client habite à une
adresse donnée).
Figure 3.15 Address
Customer -id : Long
Un client a une seule -id : Long -street1 : String
adresse. -firstName : String 1 -street2 : String
-lastName : String -city : String
-email : String -state : String
-phoneNumber : String -zipcode : String
-country : String
En Java, ceci signifie que la classe Customer aura un attribut Address (voir Listings 3.38
et 3.39).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 113
Comme vous pouvez le constater à la lecture des Listings 3.38 et 3.39, ces deux
entités utilisent un nombre minimal d’annotations – @Entity plus @Id et @Genera-
tedValue pour la clé primaire, c’est tout... Grâce à la configuration par exception,
le fournisseur de persistance les associera à deux tables et ajoutera une clé étran-
gère pour représenter la relation (allant du client à l’adresse). Cette relation 1–1
est déclenchée par le fait qu’Address est déclarée comme une entité et qu’elle est
incluse dans l’entité Customer sous la forme d’un attribut. Il n’y a donc pas besoin
d’annotation @OneToOne car le comportement par défaut suffit (voir Listings 3.40 et
3.41).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
114 Java EE 6 et GlassFish 3
PHONENUMBER VARCHAR(255),
ADDRESS_ID BIGINT,
primary key (ID),
foreign key (ADDRESS_ID) references ADDRESS(ID)
);
Comme vous le savez, si un attribut n’est pas annoté, JPA lui applique les règles
d’association par défaut. La colonne de clé étrangère s’appellera donc ADDRESS_ID
(voir Listing 3.40), qui est la concaténation du nom de l’attribut (address, ici), d’un
blanc souligné et du nom de la clé primaire de la table destination (ici, la colonne
ID de la table ADDRESS). Notez également que, dans le langage de définition des don-
nées, la colonne ADDRESS_ID peut, par défaut, recevoir des valeurs NULL : par défaut,
une relation 1–1 est donc associée à zéro (NULL) ou une valeur.
Il existe deux annotations permettant d’adapter l’association d’une relation 1–1. La
première est @OneToOne (car la cardinalité de la relation est un) : elle permet de
modifier certains attributs de la relation elle-même, comme la façon dont elle sera
parcourue. Son API est décrite dans le Listing 3.42.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 115
@OnetoMany unidirectionnelle
Dans une relation 1–N, l’objet source référence un ensemble d’objets cibles. Une
commande, par exemple, est composée de plusieurs lignes de commande (voir
Figure 3.16). Inversement, une ligne de commande pourrait faire référence à la com-
mande dont elle fait partie à l’aide d’une annotation @ManyToOne. Dans la figure,
Order est l’extrémité "One" (la source) de la relation et OrderLine est son extrémité
"Many" (la cible).
Figure 3.16 OrderLine
Order -id : Long
Une commande compte -id : Long
*
-item : String
plusieurs lignes. -creationDate : Date -unitPrice : Double
-quantity : Integer
La cardinalité est multiple et la navigation ne se fait que dans le sens Order vers
OrderLine. En Java, cette multiplicité est décrite par les interfaces Collection, List
et Set du paquetage java.util. Le Listing 3.44 présente le code de l’entité Order
avec une relation 1–N vers OrderLine (voir Listing 3.45).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
116 Java EE 6 et GlassFish 3
@Id @GeneratedValue
private Long id;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
private List<OrderLine> orderLines;
@Id @GeneratedValue
private Long id;
private String item;
private Double unitPrice;
private Integer quantity;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 117
ORDER LINE
ORDER
+ID bigint Nullable = false
+ID bigint Nullable = false
ITEM varchar(255) Nullable = true
CREATIONDATE timestamp Nullable = true
UNITPRICE double Nullable = true
QUANTITY integer Nullable = true
Figure 3.17
Table de jointure entre ORDER et ORDER_LINE.
@Id @GeneratedValue
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
118 Java EE 6 et GlassFish 3
L’entité Order du Listing 3.47 sera associée à la table de jointure décrite dans le
Listing 3.48.
La règle par défaut pour une relation 1–N unidirectionnelle consiste à utiliser une
table de jointure, mais il est très facile (et utile si vous utilisez une base de données
existante) de faire en sorte d’utiliser des clés étrangères (via une colonne de join-
ture). Pour cela, l’entité Order doit utiliser une annotation @JoinColumn à la place
de @JoinTable, comme le montre le Listing 3.49.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 119
Figure 3.18
Colonne de jointure entre Order et OrderLine.
@ManytoMany bidirectionnelle
Une relation N–M bidirectionnelle intervient lorsqu’un objet source fait référence
à plusieurs cibles et qu’une cible fait référence à plusieurs sources. Un album CD,
par exemple, est créé par plusieurs artistes, et un même artiste peut apparaître sur
plusieurs albums. Côté Java, chaque entité contiendra donc une collection d’entités
cibles. En terme de base de données relationnelle, la seule façon de représenter une
relation N–M consiste à utiliser une table de jointure (une colonne de jointure ne peut
pas convenir) ; comme nous l’avons vu précédemment, il faut également définir expli-
citement le propriétaire d’une relation bidirectionnelle à l’aide de l’élément mappedBy.
Si l’on suppose que l’entité Artist est propriétaire de la relation, ceci implique
que CD est l’extrémité opposée (voir Listing 3.50) et qu’elle doit utiliser l’élément
mappedBy de son annotation @ManyToMany. Ici, mappedBy indique au fournisseur de
persistance qu’appearsOnCDs est le nom de l’attribut correspondant dans l’entité
propriétaire de la relation.
@Id @GeneratedValue
private Long id;
private String title;
private Float price;
private String description;
@ManyToMany(mappedBy = "appearsOnCDs")
private List<Artist> createdByArtists;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
120 Java EE 6 et GlassFish 3
Si Artist est propriétaire de la relation (voir Listing 3.51), c’est donc à cette entité
qu’il revient de personnaliser la table de jointure via les annotations @JoinTable et
@JoinColumn.
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
@ManyToMany @JoinTable(name = "jnd_art_cd",
joinColumns = @JoinColumn(name = "artist_fk"),
inverseJoinColumns = @JoinColumn(name = "cd_fk"))
private List<CD> appearsOnCDs;
CD
ARTIST
+ID bigint Nullable = false
+ID bigint Nullable = false
TITLE varchar(255) Nullable = true
LASTNAME varchar(255) Nullable = true
PRICE double Nullable = true
FIRSTNAME varchar(255) Nullable = true
DESCRIPTION varchar(255) Nullable = true
JND ART CD
+#CD FK bigint Nullable = false
+#ARTIST FK bigint Nullable = false
Figure 3.19
Les tables Artist, CD et la table de jointure.
Dans une relation N–M et 1–1 bidirectionnelle, chaque extrémité peut, en fait, être
considérée comme la propriétaire de la relation. Quoi qu’il en soit, l’autre extré-
mité doit inclure l’élément mappedBy : dans le cas contraire, le fournisseur considé-
rera que les deux extrémités sont propriétaires et traitera cette relation comme deux
relations 1–N unidirectionnelles distinctes. Ici, cela produirait donc quatre tables :
ARTIST et CD et deux tables de jointures, ARTIST_CD et CD_ARTIST. On ne peut pas non
plus utiliser un élément mappedBy des deux côtés d’une relation.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 121
Toutes les annotations que nous avons vues (@OneToOne, @OneToMany, @ManyToOne
et @ManyToMany) définissent un attribut de chargement qui précise que les objets
associés doivent être chargés immédiatement (chargement "glouton") ou plus tard
(chargement "paresseux") et qui influe donc sur les performances. Selon l’applica-
tion, certaines relations sont utilisées plus souvent que d’autres : dans ces situations,
vous pouvez optimiser les performances en chargeant les données de la base lors
de la première lecture de l’entité (glouton) ou uniquement lorsqu’elle est utilisée
(paresseux). À titre d’exemple, prenons deux cas extrêmes.
Supposons que nous ayons quatre entités toutes reliées les unes aux autres avec des
cardinalités différentes (1–1, 1–N). Dans le premier cas (voir Figure 3.20), elles ont
toutes des relations "gloutonnes", ce qui signifie que, dès que l’on charge Class1
(par une recherche par ID ou par une requête), tous les objets qui en dépendent sont
automatiquement chargés en mémoire, ce qui peut avoir certaines répercussions sur
votre système.
Dans le scénario opposé, toutes les relations utilisent un chargement paresseux (voir
Figure 3.21). Lorsque l’on charge Class1, rien d’autre n’est placé en mémoire (sauf
les attributs directs de Class1, bien sûr). Il faut explicitement accéder à Class2 (via
la méthode getter, par exemple) pour que le fournisseur de persistance charge les
données à partir de la base, etc. Pour manipuler le graphe complet des objets, il faut
donc appeler explicitement chaque entité :
class1.getClass2().getClass3().getClass4()
Mais ne pensez pas qu’EAGER est le Mal et LAZY, le Bien. EAGER placera toutes les
données en mémoire à l’aide d’un petit nombre d’accès à la base (le fournisseur de
persistance utilisera sûrement des jointures pour extraire ces données). Avec LAZY,
vous ne risquez plus de remplir la mémoire puisque vous contrôlez les objets qui
sont chargés, mais vous devrez faire plus d’accès à la base à chaque fois.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
122 Java EE 6 et GlassFish 3
Le paramètre fetch est très important car, mal utilisé, il peut pénaliser les perfor-
mances. Chaque annotation a une valeur fetch par défaut que vous devez connaître
et changer si elle ne convient pas (voir Tableau 3.2).
Lorsque vous chargez une commande (Order) dans votre application, vous avez
toujours besoin d’accéder aux lignes de cette commande (OrderLine). Il peut donc
être avantageux de changer le mode de chargement par défaut de l’annotation @One-
ToMany en EAGER (voir Listing 3.52).
@Id @GeneratedValue
private Long id;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
@OneToMany(fetch = FetchType.EAGER)
private List<OrderLine> orderLines;
Avec les relations 1–N, les entités gèrent des collections d’objets. Du point de vue
de Java, ces collections ne sont généralement pas triées et les bases de données
relationnelles ne garantissent pas non plus d’ordre sur leurs tables. Si vous voulez
obtenir une liste triée, vous devez donc soit trier la collection dans votre programme,
soit utiliser une requête JPQL avec une clause Order By. Pour le tri des relations,
JPA dispose de mécanismes plus simples reposant sur les annotations.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 123
@OrderBy
L’annotation @OrderBy permet d’effectuer un tri dynamique : les éléments de la
collection seront triés lors de leur récupération à partir de la base de données.
L’exemple de l’application CD-BookStore permet à un utilisateur d’écrire des
articles à propos de musique et de livres : ces articles sont affichés sur le site web
et peuvent ensuite être commentés (voir Listing 3.53). Comme nous souhaitons que
ces commentaires apparaissent chronologiquement, nous devons les ordonner.
@Id @GeneratedValue
private Long id;
private String nickname;
private String content;
private Integer note;
@Column(name = "posted_date")
@Temporal(TemporalType.TIMESTAMP)
private Date postedDate;
Les commentaires sont modélisés par l’entité Comment du Listing 3.53. Ils ont un
contenu, sont postés par un visiteur anonyme (identifié par un pseudo) et ont une
date de publication de type TIMESTAMP automatiquement créée par le système. Dans
l’entité News du Listing 3.54, nous trions la liste des commentaires par ordre décrois-
sant des dates de publication en combinant l’annotation @OrderBy avec @OneToMany.
Listing 3.54 : Les commentaires d’une entité News sont triés par ordre décroissant
des dates de publication
@Entity
public class News {
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String content;
@OneToMany(fetch = FetchType.EAGER)
@OrderBy("postedDate desc")
private List<Comment> comments;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
124 Java EE 6 et GlassFish 3
L’annotation @OrderBy prend en paramètre les noms des attributs sur lesquels
portera le tri (postedDate, ici) et la méthode (représentée par la chaîne ASC ou
DESC pour signifier, respectivement, un tri croissant ou décroissant). Vous pou-
vez utiliser plusieurs paires attribut/méthode en les séparant par des virgules :
OrderBy("postedDate desc, note asc"), par exemple, demande de trier d’abord
sur les dates de publication (par ordre décroissant), puis sur le champ note (par
ordre croissant).
Cette annotation n’a aucun impact sur l’association dans la base de données – le
fournisseur de persistance est simplement informé qu’il doit utiliser une clause
order by lorsque la collection est récupérée.
@OrderColumn
JPA 1.0 supportait le tri dynamique avec l’annotation @OrderBy mais ne permettait
pas de maintenir un ordre persistant. JPA 2.0 règle ce problème à l’aide d’une nou-
velle annotation, @OrderColumn (voir Listing 3.55), qui informe le fournisseur de
persistance qu’il doit gérer la liste triée à l’aide d’une colonne séparée contenant un
index.
@Id @GeneratedValue
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 125
L’entité News présentée dans le Listing 3.57 peut alors annoter la relation avec @Order-
Column afin que le fournisseur de persistance associe l’entité News à une table
contenant une colonne supplémentaire pour stocker l’ordre.
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String content;
@OneToMany(fetch = FetchType.EAGER)
@OrderColumn("posted_index")
private List<Comment> comments;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
126 Java EE 6 et GlassFish 3
Traduction de l’héritage
Depuis leur création, les langages orientés objet utilisent le paradigme de l’héri-
tage. C++ autorise l’héritage multiple alors que Java ne permet d’hériter que d’une
seule classe. En programmation orientée objet, les développeurs réutilisent souvent
le code en héritant des attributs et des comportements de classes existantes.
Nous venons de voir que les relations entre entités ont des équivalents directs dans
les bases de données. Ce n’est pas le cas avec l’héritage car ce concept est totale-
ment inconnu du modèle relationnel. Il impose donc plusieurs contorsions pour être
traduit dans un SGBDR.
Pour représenter un modèle hiérarchique dans un modèle relationnel plat, JPA pro-
pose trois stratégies possibles :
■■ Une seule table par hiérarchie de classes. L’ensemble des attributs de toute la
hiérarchie des entités est mis à plat et regroupé dans une seule table (il s’agit de
la stratégie par défaut).
■■ Jointures entre sous-classes. Dans cette approche, chaque entité de la hiérarchie,
concrète ou abstraite, est associée à sa propre table.
■■ Une table par classe concrète. Chaque entité concrète de la hiérarchie est asso-
ciée à une table.
INFO
Le support de la stratégie une table par classe concrète est encore facultatif avec JPA 2.0. Les
applications portables doivent donc l’éviter tant que ce support n’a pas été officiellement
déclaré comme obligatoire dans toutes les implémentations.
Tirant parti de la simplicité d’utilisation des annotations, JPA 2.0 fournit un support
déclaratif pour définir et traduire les hiérarchies d’héritage comprenant des enti-
tés concrètes, des entités abstraites, des classes traduites et des classes transitoires.
L’annotation @Inheritance s’applique à une entité racine pour imposer une straté-
gie d’héritage à cette classe et à ses classes filles. JPA traduit aussi la notion objet
de redéfinition qui permet aux attributs de la classe racine d’être redéfinis dans les
classes filles. Dans la section suivante, nous verrons également comment utiliser les
types d’accès avec l’héritage afin de mélanger les accès par champ et par propriété.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 127
Stratégies d’héritage
JPA propose trois stratégies pour traduire l’héritage. Lorsqu’il existe une hiérarchie
d’entités, sa racine est toujours une entité qui peut définir la stratégie d’héritage
à l’aide de l’annotation @Inheritance. Si elle ne le fait pas, c’est la stratégie par
défaut, consistant à créer une seule table par hiérarchie, qui s’applique. Pour expli-
quer chacune de ces stratégies, nous étudierons comment traduire les entités CD et
Book, qui héritent toutes les deux de l’entité Item (voir Figure 3.22).
Figure 3.22 <<entity>>
Item
Hiérarchie d’héritage -id : Long
entre CD, Book et Item. -title : String
-price : Float
-description : String
<<entity>> <<entity>>
Book CD
-isbn : String -musicCompany : String
-publisher : String -numberOfCDs : Integer
-nbOfPage : Integer -totalDuration : Float
-illustrations : Boolean -gender : String
Item est l’entité racine ; elle possède un identifiant qui servira de clé primaire et
dont héritent les entités CD et Book. Chacune de ces classes filles ajoute des attributs
supplémentaires comme l’ISBN pour Book ou la durée totale d’un album pour CD.
Listing 3.58 : L’entité Item définit une stratégie d’héritage avec une seule table
@Entity
public class Item {
@Id @GeneratedValue
protected Long id;
@Column(nullable = false)
protected String title;
@Column(nullable = false)
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
128 Java EE 6 et GlassFish 3
Item est la classe parente des entités Book (voir Listing 3.59) et CD (voir Listing 3.60).
Ces entités héritent des attributs d’Item ainsi que de la stratégie d’héritage par
défaut : elles n’ont donc pas besoin d’utiliser l’annotation @Inheritance.
Sans l’héritage, ces trois entités seraient traduites en trois tables distinctes. Avec la
stratégie de traduction de l’héritage par une seule table, elles finiront toutes dans la
même table portant par défaut le nom de la classe racine : ITEM. La structure de cette
table est décrite à la Figure 3.23.
Comme vous pouvez le constater, la table ITEM rassemble tous les attributs des enti-
tés Item, Book et CD. Cependant, elle contient une colonne supplémentaire qui n’est
liée à aucun des attributs des entités : la colonne discriminante, DTYPE.
La table ITEM sera remplie d’articles, de livres et de CD. Lorsqu’il accède aux don-
nées, le fournisseur de persistance doit savoir à quelle entité appartient chaque ligne
afin d’instancier la classe d’objet appropriée (Item, Book ou CD) : la colonne discri-
minante est donc là pour préciser explicitement le type de chaque colonne.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 129
Figure 3.23 ITEM
+ID bigint Nullable = false
Structure de la table ITEM. DTYPE varchar(31) Nullable = true
TITLE varchar(255) Nullable = false
PRICE double Nullable = false
DESCRIPTION varchar(255) Nullable = true
ILLUSTRATIONS smallint Nullable = true
ISBN varchar(255) Nullable = true
NBOFPAGE integer Nullable = true
PUBLISHER varchar(255) Nullable = true
MUSICCOMPANY varchar(255) Nullable = true
NUMBEROFCDS integer Nullable = true
TOTALDURATION double Nullable = true
GENDER varchar(255) Nullable = true
La colonne discriminante s’appelle DTYPE par défaut, est de type String (traduit en
VARCHAR) et contient le nom de l’entité. Si ce comportement ne vous convient pas,
vous pouvez utiliser l’annotation @DiscriminatorColumn pour modifier le nom et le
type de cette colonne.
Dans le Listing 3.61, nous avons renommé la colonne discriminante en DISC (au
lieu de DTYPE) et modifié son type en Char au lieu de String ; chaque entité change
également sa valeur discriminante en I pour Item, B pour Book (voir Listing 3.62) et
C pour CD (voir Listing 3.63).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
130 Java EE 6 et GlassFish 3
@Id @GeneratedValue
private Long id;
private String title;
private Float price;
private String description;
L’entité racine Item définit la colonne discriminante pour toute la hiérarchie à l’aide
de l’annotation @DiscriminatorColumn. Elle change ensuite sa propre valeur en I
avec l’annotation @DiscriminatorValue. Les entités filles doivent uniquement redé-
finir leur propre valeur discriminante.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 131
Cette stratégie de table unique est la stratégie par défaut ; c’est la plus facile à com-
prendre et elle fonctionne bien lorsque la hiérarchie est relativement simple et stable.
En revanche, elle a quelques défauts : l’ajout de nouvelles entités dans la hiérarchie
ou d’attributs dans des entités existantes implique d’ajouter des colonnes à la table,
de migrer les données et de modifier les index. Cette stratégie exige également que
les colonnes des entités filles puissent recevoir la valeur NULL : si l’ISBN de l’entité
Book n’était pas nullable, par exemple, on ne pourrait pas insérer de CD car l’entité
CD n’a pas d’attribut ISBN.
@Id @GeneratedValue
protected Long id;
protected String title;
protected Float price;
protected String description;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
132 Java EE 6 et GlassFish 3
Du point de vue du développeur, la stratégie par jointure est naturelle car chaque
entité, qu’elle soit abstraite ou concrète, sera traduite dans une table distincte.
La Figure 3.26 montre comment seront transposées les entités Item, Book et CD.
BOOK ITEM CD
+#ID bigint Nullable false +ID bigint Nullable false +#ID bigint Nullable false
ILLUSTRATIONS smallint Nullable true DTYPE varchar(31) Nullable true MUSICCOMPANY varchar(255) Nullable true
ISBN varchar(255) Nullable true TITLE varchar(255) Nullable true NUMBEROFCDS integer Nullable true
NBOFPAGE integer Nullable true PRICE double Nullable true TOTALDURATION double Nullable true
PUBLISHER varchar(255) Nullable true DESCRIPTION varchar(255) Nullable true GENDER varchar(255) Nullable true
Figure 3.26
Traduction de l’héritage avec une stratégie par jointure.
Listing 3.65 : L’entité Item avec une stratégie une table par classe
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Item {
@Id @GeneratedValue
protected Long id;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 133
La Figure 3.27 montre les tables ITEM, BOOK et CD obtenues. Vous remarquerez que
BOOK et CD dupliquent les colonnes ID, TITLE, PRICE et DESCRIPTION de la table ITEM
et que les tables ne sont pas liées.
BOOK ITEM CD
+ID bigint Nullable false +ID bigint Nullable false +ID bigint Nullable false
TITLE varchar(255) Nullable true TITLE varchar(255) Nullable true MUSICCOMPANY varchar(255) Nullable true
PRICE double Nullable true PRICE double Nullable true NUMBEROFCDS integer Nullable true
ILLUSTRATIONS smallint Nullable true DESCRIPTION varchar(255) Nullable true TITLE varchar(255) Nullable true
DESCRIPTION varchar(255) Nullable true TOTALDURATION double Nullable true
ISBN varchar(255) Nullable true PRICE double Nullable true
NBOFPAGE integer Nullable true DESCRIPTION varchar(255) Nullable true
PUBLISHER varchar(255) Nullable true GENDER varchar(255) Nullable true
Figure 3.27
Les tables BOOK et CD dupliquent les colonnes d’ITEM.
Chaque table peut être redéfinie en annotant chaque entité avec @Table.
Cette stratégie est performante lorsque l’on interroge des instances d’une seule
entité car l’on se retrouve alors dans un scénario comparable à l’utilisation de la
stratégie à une seule table – la requête ne porte que sur une table. L’inconvénient est
que les requêtes polymorphiques à travers une hiérarchie de classes sont plus coû-
teuses que les deux autres stratégies : pour, par exemple, trouver tous les articles,
dont les livres et les CD, il faut interroger toutes les tables des sous-classes avec
une opération en utilisant une UNION, ce qui est coûteux lorsqu’il y a beaucoup de
données.
Avec la stratégie une table par classe, les colonnes de la classe racine sont dupli-
quées dans les classes filles en portant le même nom. Un problème se pose donc
si l’on utilise une base existante où ces colonnes ont des noms différents. Pour le
résoudre, JPA utilise l’annotation @AttributeOverride pour redéfinir l’association
de la colonne et @AttributeOverrides pour en redéfinir plusieurs.
Pour renommer les colonnes ID, TITLE et DESCRIPTION dans les tables BOOK et CD, par
exemple, le code de l’entité Item ne change pas, mais Book (voir Listing 3.66) et CD
(voir Listing 3.67) doivent utiliser l’annotation @AttributeOverride.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
134 Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 135
but doit être traduit par une colonne CD_TITLE. Le résultat obtenu est présenté à la
Figure 3.28.
BOOK ITEM CD
+BOOK_ID bigint Nullable false +ID bigint Nullable false +CD_ID bigint Nullable false
BOOK TITLE varchar(255) Nullable true TITLE varchar(255) Nullable true CD TITLE varchar(255) Nullable true
BOOK DESCRIPTION
varchar(255) Nullable true PRICE double Nullable true CD DESCRIPTION varchar(255) Nullable true
PRICE double Nullable true DESCRIPTION varchar(255) Nullable true PRICE double Nullable true
ILLUSTRATIONS small nt Nullable true MUSICCOMPANY varchar(255) Nullable true
ISBN varchar(255) Nullable true NUMBEROFCDS integer Nullable true
NBOFPAGE nteger Nullable true TOTALDURATION double Nullable true
PUBLISHER varchar(255) Nullable true GENDER varchar(255) Nullable true
Figure 3.28
Les tables BOOK et CD ont redéfini des colonnes d’ITEM.
INFO
Dans la section "Classes intégrables" de ce chapitre, nous avons vu qu’un objet intégrable
pouvait être partagé par plusieurs entités (Address était intégré dans Customer et Order).
Les objets intégrables étant des composantes à part entière de l’entité qui les intègre, leurs
colonnes seront également dupliquées dans les tables de chaque entité. Vous pouvez alors
utiliser l’annotation @AttributeOverrides si vous avez besoin de redéfinir les colonnes des
objets intégrables.
Entités abstraites
Dans les exemples précédents, l’entité Item était concrète. Elle était annotée par @
Entity et ne comprenait pas de mot-clé abstract ; mais une classe abstraite peut
également être désignée comme une entité. Elle ne diffère d’une entité concrète
que parce qu’elle ne peut pas être directement instanciée avec le mot-clé new, mais
elle fournit une structure de données que partageront toutes ses entités filles (Book
et CD) et elle respecte les stratégies de traduction d’héritage. Du point de vue du
fournisseur de persistance, la seule différence se situe du côté de Java, pas dans la
correspondance en table.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
136 Java EE 6 et GlassFish 3
Non-entités
Les non-entités sont également appelées classes transitoires, ce qui signifie qu’elles
sont des POJO. Une entité peut hériter d’une non-entité ou peut être étendue par
une non-entité. La modélisation objet et l’héritage permettent de partager les états
et les comportements ; dans une hiérarchie de classes, les non-entités peuvent donc
servir à fournir une structure de données commune à leurs entités filles. L’état d’une
superclasse non entité n’est pas persistant car il n’est pas géré par le fournisseur de
persistance (n’oubliez pas que la condition pour qu’une classe le soit est la présence
de l’annotation @Entity).
Comme le montre le Listing 3.68, Item est désormais une non-entité.
L’entité Book du Listing 3.69 hérite d’Item ; le code Java peut donc accéder aux
attributs title, price et description ainsi qu’à toutes les méthodes d’Item. Que
cette dernière soit concrète ou abstraite n’aura aucune influence sur la traduction
finale.
@Entity
public class Book extends Item {
@Id @GeneratedValue
private Long id;
private String isbn;
private String publisher;
private Integer nbOfPage;
private Boolean illustrations;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 137
Book est une entité qui hérite d’Item, mais seuls les attributs de Book seront stockés
dans une table. Aucun attribut d’Item n’apparaît dans la structure de la table du
Listing 3.70. Pour qu’un Book soit persistant, vous devez créer une instance de Book,
initialiser les attributs que vous souhaitez (title, price, isbn, publisher, etc.),
mais seuls ceux de Book (id, isbn, etc.) seront stockés.
Superclasse "mapped"
JPA définit un type de classe spéciale, appelée superclasse "mapped", qui partage
son état, son comportement ainsi que les informations de traduction des entités qui
en héritent. Cependant, les superclasses "mapped" ne sont pas des entités, elles ne
sont pas gérées par le fournisseur de persistance, n’ont aucune table qui leur soit
associée et ne peuvent pas être interrogées ni faire partie d’une relation ; en revanche,
elles peuvent fournir des propriétés de persistance aux entités qui en héritent. Les
superclasses "mapped" ressemblent aux classes intégrables, sauf qu’elles peuvent
être utilisées avec l’héritage. Elles sont annotées par @MappedSuperclass.
Dans le Listing 3.71, la classe racine Item est annotée par @MappedSuperclass, pas
par @Entity. Elle définit une stratégie de traduction de l’héritage (JOINED) et annote
certains de ces attributs avec @Column. Cependant, les superclasses "mapped" n’étant
pas associées à des tables, l’annotation @Table n’est pas autorisée.
@MappedSuperclass
@Inheritance(strategy = InheritanceType.JOINED)
public class Item {
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
138 Java EE 6 et GlassFish 3
Comme vous pouvez le constater, les attributs title et description sont annotés
par @Column. Le Listing 3.72 montre l’entité Book qui hérite d’Item.
Cette hiérarchie sera traduite en une seule table. Item n’est pas une entité et n’a donc
aucune table associée. Les attributs d’Item et de Book seront traduits en colonnes de
la table BOOK – les superclasses "mapped" partageant également leurs informations
de traduction, les annotations @Column d’Item seront donc héritées. Le Listing 3.73
montre que les colonnes TITLE et DESCRIPTION de la table BOOK ont bien été modifiées
selon les annotations d’Item.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 3 ORM : Object-Relational Mapping 139
Résumé
Grâce à la configuration par exception, il n’y a pas besoin de faire grand-chose pour
traduire des entités en tables : il faut simplement informer le fournisseur de persis-
tance qu’une classe est une entité (avec @Entity) et qu’un attribut est un identifiant
(avec @Id), et JPA s’occupe du reste. Ce chapitre aurait été bien plus court s’il s’était
contenté du comportement par défaut, mais JPA fournit également un grand nombre
d’annotations pour adapter le moindre détail de l’ORM.
Les annotations élémentaires permettent d’adapter la traduction des attributs (@Basic,
@Temporal, etc.) ou des classes. Vous pouvez ainsi modifier le nom de la table ou le
type de la clé primaire, voire empêcher le stockage avec l’annotation @Transient.
À partir de JPA 2.0, il devient possible de stocker dans la base de données des col-
lections de types de base ou d’objets intégrables. Selon votre modèle, vous pouvez
traduire des relations (@OneToOne, @ManyToMany, etc.) de directions et de cardinalités
différentes. Il en va de même pour l’héritage (@Inheritance, @MappedSuperclass,
etc.), où vous pouvez choisir entre plusieurs stratégies pour traduire une hiérarchie
d’entités et de non-entités.
Ce chapitre s’est intéressé à la partie statique de JPA, à la façon d’associer des entités
à des tables. Le chapitre suivant présentera les aspects dynamiques : l’interrogation
de ces entités.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
4
Gestion des objets persistants
L’API de persistance de Java, JPA, a deux aspects. Le premier est la possibilité d’as-
socier des objets à une base de données relationnelle. La configuration par exception
permet aux fournisseurs de persistance de faire l’essentiel du travail sans devoir
ajouter beaucoup de code, mais la richesse de JPA tient également à la possibilité
d’adapter ces associations à l’aide d’annotations ou de descriptions XML. Que ce
soit une modification simple (changer le nom d’une colonne, par exemple) ou une
adaptation plus complexe (pour traduire l’héritage), JPA offre un large spectre de
possibilités. Vous pouvez donc associer quasiment n’importe quel modèle objet à
une base de données existante.
Le second aspect concerne l’interrogation de ces objets une fois qu’ils ont été
associés à une base. Élément central de JPA, le gestionnaire d’entités permet de
manipuler de façon standard les instances des entités. Il fournit une API pour créer,
rechercher, supprimer et synchroniser les objets avec la base de données et permet
d’exécuter différentes sortes de requêtes JPQL sur les entités, comme des requêtes
dynamiques, statiques ou natives. Le gestionnaire d’entités autorise également la
mise en place de mécanismes de verrouillage sur les données.
Le monde des bases de données relationnelles repose sur SQL (Structured Query
Language). Ce langage de programmation a été conçu pour faciliter la gestion des
données relationnelles (récupération, insertion, mise à jour et suppression), et sa
syntaxe est orientée vers la manipulation de tables. Vous pouvez ainsi sélection-
ner des colonnes de tables constituées de lignes, joindre des tables, combiner les
résultats de deux requêtes SQL à l’aide d’une union, etc. Ici, il n’y a pas d’objets
mais uniquement des lignes, des colonnes et des tables. Dans le monde Java, où
l’on manipule des objets, un langage conçu pour les tables (SQL) doit être un peu
déformé pour convenir à un langage à objets (Java). C’est là que JPQL (Java Persis-
tence Query Language) entre en jeu.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
142 Java EE 6 et GlassFish 3
JPQL est le langage qu’utilise JPA pour interroger les entités stockées dans une base
de données relationnelle. Sa syntaxe ressemble à celle de SQL mais opère sur des
objets entités au lieu d’agir directement sur les tables. JPQL ne voit pas la structure
de la base de données sous-jacente et ne manipule ni les tables ni les colonnes –
uniquement des objets et des attributs. Il utilise pour cela la notation pointée que
connaissent bien tous les développeurs Java.
Dans ce chapitre, nous verrons comment gérer les objets persistants. Nous appren-
drons comment réaliser les opérations CRUD (Create, Read, Update et Delete) avec
le gestionnaire d’entités et créerons des requêtes complexes en JPQL. La fin du
chapitre expliquera comment JPA gère la concurrence d’accès aux données.
Comme premier exemple, étudions une requête simple : trouver un livre par son
identifiant. Le Listing 4.1 présente une entité Book utilisant l’annotation @Id pour
informer le fournisseur de persistance que l’attribut id doit être associé à une clé
primaire.
@Id
private Long id;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
L’entité Book contient les informations pour l’association. Ici, elle utilise la plupart
des valeurs par défaut : les données seront donc stockées dans une table portant le
même nom que l’entité (BOOK) et chaque attribut sera associé à une colonne homo-
nyme. Nous pouvons maintenant utiliser une classe Main distincte (voir Listing 4.2)
qui utilise l’interface javax.persistence.EntityManager pour stocker une instance
de Book dans la table.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 143
System.out.println(book);
em.close();
emf.close();
}
}
La classe Main du Listing 4.2 utilise quatre étapes pour stocker un livre dans la base
de données puis le récupérer.
1. Création d’une instance de l’entité Book. Les entités sont des POJO gérés par
le fournisseur de persistance. Du point de vue de Java, une instance de classe
doit être créée avec le mot-clé new, comme n’importe quel POJO. Il faut bien
insister sur le fait qu’à ce stade le fournisseur de persistance ne connaît pas
encore l’objet Book.
2. Création d’un gestionnaire d’entités et d’une transaction. C’est la partie
importante du code car on a besoin d’un gestionnaire d’entités pour les manipu-
ler. On crée donc d’abord une fabrique de gestionnaires d’entités pour l’unité de
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
144 Java EE 6 et GlassFish 3
<<Interface>>
Main EntityManager
SQL / JDBC
+persist(entity : Object) : void Base de
+find(entityClass, : Class<T>, primaryKey : Object) : <T>
données
Book
-id : Long
-title : String
-price : Float
-description : String
-nbOfPage : Integer
-illustrations : Boolean
Figure 4.1
Le gestionnaire d’entités interagit avec l’entité et la base de données sous-jacente.
Quel pilote JDBC utiliser ? Comment se connecter à la base ? Quel est le nom de la
base ? Toutes ces informations sont absentes du code précédent. Lorsque la classe
Main crée une fabrique EntityManagerFactory, elle lui passe le nom d’une unité de
persistance en paramètre – chapter04PU ici. Cette unité de persistance indique au
gestionnaire d’entités le type de la base à utiliser et les paramètres de connexion :
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 145
toutes ces informations sont précisées dans le fichier persistence.xml (voir Lis-
ting 4.3) qui doit être déployé avec les classes.
Le gestionnaire d’entités
Le gestionnaire d’entités est une composante essentielle de JPA. JPA gère l’état et le
cycle de vie des entités et les interroge dans un contexte de persistance. C’est égale-
ment lui qui est responsable de la création et de la suppression des instances d’enti-
tés persistantes et qui les retrouve à partir de leur clé primaire. Il peut les verrouiller
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
146 Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 147
public Query
createQuery(String qlString);
public Query
createQuery(QueryDefinition qdef);
public Query
createNamedQuery(String name);
public Query
createNativeQuery(String sqlString);
public Query
createNativeQuery(String sqlString,
Class resultClass);
public Query createNativeQuery(String sqlString,
String resultSetMapping);
Dans la section suivante, nous verrons comment obtenir une instance d’EntityManager.
Ne soyez pas effrayé par l’API du Listing 4.4 : ce chapitre expliquera la plupart de
ces méthodes.
Le gestionnaire d’entités est l’interface centrale pour interagir avec les entités, mais
l’application doit d’abord en obtenir un. Selon que l’on soit dans un environne-
ment géré par un conteneur (comme nous le verrons au Chapitre 6 avec les EJB) ou
géré par une application, le code peut être très différent. Dans le premier cas, par
exemple, c’est le conteneur qui gère les transactions, ce qui signifie que l’on n’a pas
besoin d’appeler explicitement les opérations commit() ou rollback(), contrairement
à un environnement géré par l’application.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
148 Java EE 6 et GlassFish 3
Le terme "géré par l’application" signifie que c’est l’application qui est respon-
sable de l’obtention explicite d’une instance d’EntityManager et de la gestion de
son cycle de vie (elle doit fermer le gestionnaire d’entités lorsqu’elle n’en a plus
besoin, par exemple). Dans le Listing 4.2, nous avons vu comment une classe qui
s’exécutait dans l’environnement Java SE obtenait une instance du gestionnaire :
elle utilise la classe Persistence pour lancer une fabrique EntityManagerFactory
associée à une unité de persistance (chapter04PU), et cette fabrique sert ensuite à
créer un gestionnaire d’entités. L’utilisation d’une fabrique pour créer un gestion-
naire d’entités est assez simple, mais ce qui différencie un environnement géré par
l’application d’un environnement géré par un conteneur est la façon dont on obtient
cette fabrique.
Dans un environnement géré par un conteneur, l’application s’exécute dans une ser-
vlet ou dans un conteneur d’EJB. Avec Java EE, la méthode la plus classique pour
obtenir un gestionnaire d’entités consiste alors soit à utiliser l’annotation @Persis-
tenceContext pour en injecter un, soit à utiliser JNDI. Un composant qui s’exécute
dans un conteneur (servlet, EJB, service web, etc.) n’a en revanche pas besoin de
créer ou de fermer le gestionnaire d’entités puisque son cycle de vie est géré par le
conteneur. Le Listing 4.5 montre le code d’une session sans état dans laquelle on
injecte une référence à l’unité de persistance chapter04PU.
Listing 4.5 : Injection d’une référence à un gestionnaire d’entités dans un EJB sans état
@Stateless
public class BookBean {
@PersistenceContext(unitName = "chapter04PU")
private EntityManager em;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 149
System.out.println(book);
}
}
Le code du Listing 4.5 est bien plus simple que celui du Listing 4.2 : il n’y a pas
besoin des objets Persistence ou EntityManagerFactory car le gestionnaire d’enti-
tés est injecté par le conteneur. En outre, les beans sans état gérant les transactions,
il n’est pas non plus nécessaire d’appeler explicitement commit() ou rollback().
Nous reviendrons sur ce style de gestionnaire d’entités au Chapitre 6.
Contexte de persistance
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
150 Java EE 6 et GlassFish 3
des entités qui pourront être gérées dans un contexte persistant. Elle porte un nom
(chapter04PU) et a un ensemble d’attributs.
Le gestionnaire d’entités sert également à créer des requêtes JPQL complexes pour
récupérer une ou plusieurs entités. Lorsqu’elle manipule des entités uniques, l’in-
terface EntityManager peut être considérée comme un DAO (Data Access Object)
générique permettant d’effectuer les opérations CRUD sur n’importe quelle entité
(voir Tableau 4.1).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 151
Méthode Description
void persist(Object entity) Crée une instance gérée et persistante.
<T> T find(Class<T> entityClass, Recherche une entité de la classe et de la clé
Object primaryKey) indiquées.
<T> T getReference(Class<T> Obtient une instance dont l’état peut être
entityClass, Object primaryKey) récupéré de façon paresseuse.
void remove(Object entity) Supprime l’instance d’entité du contexte de
persistance et de la base de données.
<T> T merge(T entity) Fusionne l’état de l’entité indiquée dans le
contexte de persistance courant.
void refresh(Object entity) Rafraîchit l’état de l’instance à partir de la
base de données en écrasant les éventuelles
modifications apportées à l’entité.
void flush() Synchronise le contexte de persistance avec la
base de données.
void clear() Vide le contexte de persistance. Toutes les
entités gérées deviennent détachées.
void clear(Object entity) Supprime l’entité indiquée du contexte de
persistance.
boolean contains(Object entity) Teste si l’instance est une entité gérée
appartenant au contexte de persistance courant
Pour mieux comprendre ces méthodes, nous utiliserons un exemple simple d’une
relation 1–1 unidirectionnelle entre un client et son adresse. Les deux entités Cus-
tomer (voir Listing 4.7) et Address (voir Listing 4.8) ont des identifiants produits
automatiquement (grâce à l’annotation @GeneratedValue), et Customer récupère
l’Address de façon paresseuse (c’est-à-dire uniquement lorsqu’il en a besoin).
Listing 4.7 : L’entité Customer avec une relation 1–1 unidirectionnelle avec Address
@Entity
public class Customer {
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
152 Java EE 6 et GlassFish 3
@Id @GeneratedValue
private Long id;
private String street1;
private String city;
private String zipcode;
private String country;
Ces deux entités seront traduites dans la base de données avec la structure présentée
à la Figure 4.2. Notez que la colonne ADDRESS_FK est la colonne de type clé étrangère
permettant d’accéder à ADDRESS.
CUSTOMER ADDRESS
+ID bigint Nullable = false +ID bigint Nullable = false
LASTNAME varchar(255) Nullable = false STREET1 varchar(255) Nullable = true
EMAIL varchar(255) Nullable = true ZIPCODE varchar(255) Nullable = true
FIRSTNAME varchar(255) Nullable = true COUNTRY varchar(255) Nullable = true
#ADDRESS FK bigint Nullable = true CITY varchar(255) Nullable = true
Figure 4.2
Les tables CUSTOMER et ADDRESS.
Pour plus de visibilité, les fragments de code utilisés dans la section suivante supposent
que l’attribut em est de type EntityManager et que tx est de type EntityTransaction.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 153
à une autre lorsqu’il y a des relations et, enfin, appeler la méthode EntityManager.
persist() comme le montre le cas de test JUnit du Listing 4.9.
tx.begin();
em.persist(customer);
em.persist(address);
tx.commit();
assertNotNull(customer.getId());
assertNotNull(address.getId());
Dans le Listing 4.9, le client et l’adresse ne sont que deux objets qui résident
dans la mémoire de la JVM. Tous les deux ne deviennent des entités gérées que
lorsque le gestionnaire d’entités (la variable em) les prend en compte en les rendant
persistantes (em.persist(customer)). Dès cet instant, les deux objets deviennent
candidats à une insertion dans la base de données. Lorsque la transaction est vali-
dée (tx.commit()), les données sont écrites dans la base : une ligne d’adresse est
ajoutée à la table ADDRESS et une ligne client, à la table CUSTOMER. L’entité Customer
étant la propriétaire de la relation, sa table contient une clé étrangère vers ADDRESS.
Les deux expressions assertNotNull testent que les deux entités ont bien reçu un
identifiant (fourni automatiquement par le fournisseur de persistance grâce aux
annotations).
Notez l’ordre d’appel des méthodes persist() : on rend d’abord le client persis-
tant, puis son adresse. Si l’on avait fait l’inverse, le résultat aurait été le même. Plus
haut, nous avons écrit que l’on pouvait considérer le gestionnaire d’entités comme
un cache de premier niveau : tant que la transaction n’est pas validée, les données
restent en mémoire et il n’y a aucun accès à la base. Le gestionnaire d’entités met
les données en cache et, lorsqu’il est prêt, les écrit dans l’ordre qu’attend la base de
données (afin de respecter les contraintes d’intégrité). À cause de la clé étrangère
que contient la table CUSTOMER, l’instruction insert dans ADRESS doit s’effectuer en
premier, suivie de celle de CUSTOMER.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
154 Java EE 6 et GlassFish 3
INFO
La plupart des entités de ce chapitre n’implémentent pas l’interface Serializable car elles
n’en ont tout simplement pas besoin pour être rendues persistantes. Elles sont passées par
référence d’une méthode à l’autre et ce n’est que lorsqu’il faut les rendre persistantes que
l’on appelle la méthode EntityManager.persist(). Si, toutefois, vous devez passer des
entités par valeur (appel distant, conteneur EJB externe, etc.), celles-ci doivent implémen-
ter l’interface java.io.Serializable pour indiquer au compilateur qu’il faut que tous les
champs de l’entité soient sérialisables afin qu’une instance puisse être sérialisée dans un flux
d’octets et passée par RMI (Remote Method Invocation).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 155
tx.begin();
em.persist(customer);
em.persist(address);
tx.commit();
tx.begin();
em.remove(customer);
tx.commit();
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
156 Java EE 6 et GlassFish 3
Les relations 1–1 ou 1–N disposent d’une option demandant la suppression des
orphelins. Dans notre exemple, il suffit d’ajouter l’élément orphanRemoval=true à
l’annotation @OneToOne, comme dans le Listing 4.13.
Listing 4.13 : L’entité Customer gère la suppression des Address orphelines
@Entity
public class Customer {
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
@OneToOne (fetch = FetchType.LAZY, orphanRemoval = true)
private Address address;
Toutes les modifications en attente exigent une instruction SQL et les deux insert
ne seront produits et rendus permanents que lorsque la transaction sera validée par
commit(). Pour la plupart des applications, cette synchronisation automatique suf-
fit : on ne sait pas exactement quand le fournisseur écrira vraiment les données
dans la base, nous pouvons être sûrs que l’écriture aura lieu lorsque la transaction
sera validée. Bien que la base de données soit synchronisée avec les entités dans
le contexte de persistance, nous pouvons explicitement écrire des données dans
la base (avec flush()) ou, inversement, rafraîchir des données à partir de la base
(avec refresh()). Si des données sont écrites dans la base à un instant précis et que
l’application appelle plus tard la méthode rollback() pour annuler la transaction,
les données écrites seront supprimées de la base.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 157
Écriture de données
La méthode EntityManager.flush force le fournisseur de persistance à écrire les don-
nées dans la base ; elle permet donc de déclencher manuellement le même processus
que celui utilisé en interne par le gestionnaire d’entités lorsqu’il écrit le contexte de
persistance dans la base de données.
tx.begin();
em.persist(customer);
em.flush();
em.persist(address);
tx.commit();
Il se passe deux choses intéressantes dans le code précédent. La première est que
em.flush() n’attendra pas que la transaction soit validée pour écrire le contexte de
persistance dans la base de données : une instruction insert sera produite et exécu-
tée à l’instant de l’appel à flush(). La seconde est que ce code ne fonctionnera pas
à cause des contraintes d’intégrité. Sans écriture explicite, le gestionnaire d’entités
met en cache toutes les modifications, les ordonne et les exécute de façon cohérente
du point de vue de la base. Avec une écriture explicite, l’instruction insert sur
la table CUSTOMER s’exécutera mais la contrainte d’intégrité sur la clé étrangère (la
colonne ADDRESS_FK de CUSTOMER) sera violée et la transaction sera donc annulée.
Les données écrites seront alors supprimées de la base. Vous devez donc faire atten-
tion lorsque vous utilisez des écritures explicites et ne les utiliser que lorsqu’elles
sont nécessaires.
em.refresh(customer);
assertEquals(customer.getFirstName(), "Antony");
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
158 Java EE 6 et GlassFish 3
Contains
La méthode EntityManager.contains() renvoie un Boolean indiquant si une
instance d’entité particulière est actuellement gérée par le gestionnaire d’enti-
tés dans le contexte de persistance courant. Le cas de test du Listing 4.15 rend un
Customer persistant – on peut immédiatement vérifier que l’entité est gérée (em.
contains(customer) renvoie true). Puis on appelle la méthode remove() pour sup-
primer cette entité de la base de données et du contexte de persistance ; l’appel à
em.contains(customer) renvoie alors false.
Listing 4.15 : Cas de test pour vérifier que l’entité Customer se trouve dans le contexte
de persistance
Customer customer = new Customer("Antony", "Balla",
"tballa@mail.com");
tx.begin();
em.persist(customer);
tx.commit();
assertTrue(em.contains(customer));
tx.begin();
em.remove(customer);
tx.commit();
assertFalse(em.contains(customer));
Clear et Detach
La méthode clear() porte bien son nom car elle vide le contexte de persis-
tance : toutes les entités qui étaient gérées deviennent donc détachées. La méthode
detach(Object entity) supprime l’entité indiquée du contexte de persistance –
après cette éviction, les modifications apportées à cette entité ne seront plus syn-
chronisées avec la base de données. Le Listing 4.16 crée une entité, vérifie qu’elle
est gérée, puis la supprime du contexte de persistance et vérifie qu’elle est bien
détachée.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 159
tx.begin();
em.persist(customer);
tx.commit();
assertTrue(em.contains(customer));
em.detach(customer);
assertFalse(em.contains(customer));
La méthode clear() peut agir sur tout le contexte de persistance (clear ()) ou
uniquement sur une entité (clear(Object entity)).
tx.begin();
em.persist(customer);
tx.commit();
em.clear();
tx.begin();
em.merge(customer);
tx.commit();
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
160 Java EE 6 et GlassFish 3
tx.begin();
em.persist(customer);
customer.setFirstName("William");
tx.commit();
Répercussion d’événements
Par défaut, chaque opération du gestionnaire d’entités ne s’applique qu’à l’entité
passée en paramètre à l’opération. Parfois, cependant, on souhaite propager son
action à ses relations – c’est ce que l’on appelle répercuter un événement. Jusqu’à
présent, nos exemples reposaient sur ce comportement par défaut : le Listing 4.19,
par exemple, crée un client en instanciant une entité Customer et une entité Address,
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 161
en les liant (avec customer. setAddress(address)), puis en les rendant toutes les
deux persistantes.
tx.begin();
em.persist(customer);
em.persist(address);
tx.commit();
tx.begin();
em.persist(customer);
tx.commit();
Sans cette répercussion, le client serait persistant, mais pas son adresse. Pour que
cette répercussion ait lieu, l’association de la relation doit donc être modifiée. Les
annotations @OneToOne, @OneToMany, @ManyToOne et @ManyToMany disposent d’un
attribut cascade pouvant recevoir un tableau d’événements à propager. Nous devons
donc modifier l’association de l’entité Customer (voir Listing 4.21) en ajoutant un
attribut cascade à l’annotation @OneToOne. Ici, on ne se contente pas de propager
persist, on fait de même pour l’événement remove, afin que la suppression d’un
client entraîne celle de son adresse.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
162 Java EE 6 et GlassFish 3
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
@OneToOne (fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = "address_fk")
private Address address;
Le Tableau 4.2 énumère les événements que vous pouvez propager vers une cible de
relation. Vous pouvez même tous les propager en utilisant le type CascadeType.ALL.
Type Description
PERSIST Propage les opérations persist à la cible de la relation.
REMOVE Propage les opérations remove à la cible de la relation.
MERGE Propage les opérations merge à la cible de la relation.
REFRESH Propage les opérations refresh à la cible de la relation.
CLEAR Propage les opérations clear à la cible de la relation.
ALL Propage toutes les opérations précédentes.
L’API de cache
La plupart des spécifications (pas seulement JAVA EE) s’intéressent beaucoup aux
fonctionnalités et considèrent le reste, comme les performances, l’adaptabilité ou la
mise en cluster, comme des détails d’implémentation. Les implémentations doivent
respecter strictement la spécification mais peuvent également ajouter des fonction-
nalités spécifiques. Un parfait exemple pour JPA serait la gestion d’un cache.
Jusqu’à JPA 2.0, la mise en cache n’était pas mentionnée dans la spécification.
Comme on l’a déjà évoqué, le gestionnaire d’entités est un cache de premier niveau
utilisé pour traiter les données afin qu’elles conviennent à la base de données et
pour mettre en cache les entités en cours d’utilisation. Ce cache permet de réduire
le nombre de requêtes SQL de chaque transaction – si un objet est modifié plusieurs
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 163
// Vide le cache.
public void evictAll();
}
JPQL
Nous venons de voir comment manipuler séparément les entités avec l’API d’En-
tityManager. Vous savez maintenant comment récupérer une entité à partir de son
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
164 Java EE 6 et GlassFish 3
identifiant, la supprimer, modifier ses attributs, etc. Mais rechercher une entité par
son identifiant est assez limité (ne serait-ce que parce qu’il vous faut connaître cet
identifiant...) : en pratique, vous aurez plutôt besoin de récupérer une entité en fonc-
tion de critères autres que son identifiant (par son nom ou son ISBN, par exemple)
ou de récupérer un ensemble d’entités satisfaisant certaines conditions (tous les clients
qui habitent en France, par exemple). Cette possibilité est inhérente aux bases de don-
nées relationnelles et JPA dispose d’un langage permettant ce type d’interactions :
JPQL.
JPQL (Java Persistence Query Language) sert à définir des recherches d’entités
persistantes indépendamment de la base de données sous-jacente. C’est un langage
de requête qui s’inspire de SQL (Structured Query Language), le langage standard
pour interroger les bases de données relationnelles. La différence principale est que
le résultat d’une requête SQL est un ensemble de lignes et de colonnes (une table)
alors que celui d’une requête JPQL est une entité ou une collection d’entités. Sa
syntaxe est orientée objet et est donc plus familière aux développeurs ne connaissant
que ce type de programmation. Ils peuvent ainsi manipuler un modèle objet en uti-
lisant la notation pointée classique (maClasse.monAttribut, par exemple) et oublier
la structure des tables.
En coulisse, JPQL utilise un mécanisme de traduction pour transformer une requête
JPQL en langage compréhensible par une base de données SQL. La requête s’exé-
cute sur la base de données sous-jacente avec SQL et des appels JDBC, puis les
instances d’entités sont initialisées et sont renvoyées à l’application – tout ceci de
façon simple et à l’aide d’une syntaxe riche.
La requête JPQL la plus simple qui soit sélectionne toutes les instances d’une seule
entité :
SELECT b
FROM Book b
Si vous connaissez SQL, cette instruction devrait vous sembler familière. Au lieu
de sélectionner le résultat à partir d’une table, JPQL sélectionne des entités, Book
ici. La clause FROM permet également de donner un alias à cette entité : ici, b est
un alias de Book. La clause SELECT indique que le type de la requête est l’entité b
(Book). L’exécution de cette instruction produira donc une liste de zéros ou plusieurs
instances de Book.
Pour restreindre le résultat, on utilise la clause WHERE afin d’introduire un critère de
recherche :
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 165
SELECT b
FROM Book b
WHERE b.title = "H2G2"
L’alias sert à naviguer dans les attributs de l’entité via l’opérateur point. L’entité
Book ayant un attribut persistant nommé title de type String, b.title désigne
donc l’attribut title de l’entité Book. L’exécution de cette instruction produira une
liste de zéros ou plusieurs instances de Book ayant pour titre H2G2.
La requête la plus simple est formée de deux parties obligatoires : les clauses SELECT
et FROM. La première définit le format du résultat de la requête tandis que la seconde
indique l’entité ou les entités à partir desquelles le résultat sera obtenu. Une requête
peut également contenir des clauses WHERE, ORDER BY, GROUP BY et HAVING pour
restreindre ou trier le résultat. La syntaxe complète de SELECT est définie dans le
Listing 4.23.
Il existe également les instructions DELETE et UPDATE, qui permettent respectivement
de supprimer et de modifier plusieurs instances d’une classe d’entité.
Select
La clause SELECT porte sur une expression qui peut être une entité, un attribut d’en-
tité, une expression constructeur, une fonction agrégat ou toute séquence de ce qui
précède. Ces expressions sont les briques de base des requêtes et servent à atteindre
les attributs des entités ou à traverser les relations (ou une collection d’entités) via
la notation pointée classique. Le Listing 4.23 définit la syntaxe d’une instruction
SELECT.
Une instruction SELECT simple renvoie une entité. Si une entité Customer a un alias
c, par exemple, SELECT c renverra une entité ou une liste d’entités.
SELECT c
FROM Customer c
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
166 Java EE 6 et GlassFish 3
Une clause SELECT peut également renvoyer des attributs. Si l’entité Customer a
un attribut firstName, SELECT c.firstName renverra un String ou une collection de
String contenant les prénoms.
SELECT c.firstName
FROM Customer c
Pour obtenir le prénom et le nom d’un client, il suffit de créer une liste contenant les
deux attributs correspondants :
SELECT c.firstName, c.lastName
FROM Customer c
Si l’entité Customer est en relation 1–1 avec Address, c.address désigne l’adresse
du client et le résultat de la requête suivante renverra donc non pas une liste de
clients mais une liste d’adresses :
SELECT c.address
FROM Customer c
Les expressions de navigation peuvent être reliées les unes aux autres pour traverser
des graphes d’entités complexes. Avec cette technique, nous pouvons construire des
expressions comme c.address.country.code afin de désigner le code du pays de
l’adresse d’un client.
SELECT c.address.country.code
FROM Customer c
Le résultat de cette requête sera une liste d’objets CustomerDTO instanciés avec
l’opérateur new et initialisés avec le prénom, le nom et la rue des clients.
L’exécution des requêtes précédentes renverra soit une valeur unique, soit une col-
lection de zéros ou plusieurs entités (ou attributs) pouvant contenir des doublons.
Pour supprimer ces derniers, il faut utiliser l’opérateur DISTINCT :
SELECT DISTINCT c
FROM Customer c
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 167
Le résultat d’une requête peut être le résultat d’une fonction agrégat appliquée à une
expression. La clause SELECT peut utiliser les fonctions agrégats AVG, COUNT, MAX, MIN
et SUM. En outre, leurs résultats peuvent être regroupés par une clause GROUP BY et
filtrés par une clause HAVING.
SELECT COUNT(c)
FROM Customer c
Les clauses SELECT, WHERE et HAVING peuvent également utiliser des expressions sca-
laires portant sur des nombres (ABS, SQRT, MOD, SIZE, INDEX), des chaînes (CONCAT,
SUBSTRING, TRIM, LOWER, UPPER, LENGTH) et des dates (CURRENT_DATE, CURRENT_ TIME,
CURRENT_TIMESTAMP).
From
La clause FROM d’une requête définit les entités en déclarant des variables d’identifi-
cation ou alias qui pourront être utilisés dans les autres clauses (SELECT, WHERE, etc.).
Sa syntaxe est simplement formée du nom de l’entité et de son alias. Dans l’exemple
suivant, l’entité est Customer et l’alias est c :
SELECT c
FROM Customer c
Where
La clause WHERE d’une requête est formée d’une expression conditionnelle permet-
tant de restreindre le résultat d’une instruction SELECT, UPDATE ou DELETE. Il peut
s’agir d’une expression simple ou d’un ensemble d’expressions conditionnelles
permettant de filtrer très précisément la requête.
La façon la plus simple de restreindre le résultat d’une requête consiste à utiliser un
attribut d’une entité. L’instruction suivante, par exemple, sélectionne tous les clients
prénommés Vincent :
SELECT c
FROM Customer c
WHERE c.firstName = ’Vincent’
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
168 Java EE 6 et GlassFish 3
Vous pouvez restreindre encore plus les résultats en utilisant les opérateurs logiques
AND et OR. L’exemple suivant utilise AND pour sélectionner tous les clients prénommés
Vincent qui habitent en France :
SELECT c
FROM Customer c
WHERE c.firstName = ’Vincent’ AND c.address.country = ’France’
La clause WHERE utilise également les opérateurs de comparaison =, >, >=, <, <=, <>,
[NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY et [NOT]
MEMBER [OF]. Voici quelques exemples d’utilisation :
SELECT c
FROM Customer c
WHERE c.age > 18
SELECT c
FROM Customer c
WHERE c.age NOT BETWEEN 40 AND 50
SELECT c
FROM Customer c
WHERE c.address.country IN (’USA’, ’Portugal’)
L’expression LIKE est formée d’une chaîne pouvant contenir des caractères "jokers" :
le blanc souligné (_) capture un caractère quelconque et le caractère pourcent (%)
capture un nombre quelconque (éventuellement nul) de caractères :
SELECT c
FROM Customer c
WHERE c.email LIKE ’%mail.com’
Liaison de paramètres
Jusqu’à maintenant, les clauses WHERE dont nous nous sommes servis n’utilisaient
que des valeurs fixes. Dans une application, cependant, les requêtes dépendent sou-
vent de paramètres et JPQL fournit donc deux moyens pour lier ces paramètres : par
position ou par nom.
Les paramètres positionnels sont indiqués par un point d’interrogation suivi d’un
entier (?1, par exemple). Lorsque la requête sera utilisée, il faudra fournir les valeurs
qui viendront remplacer ces paramètres.
SELECT c
FROM Customer c
WHERE c.firstName = ?1 AND c.address.country = ?2
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 169
Les paramètres nommés sont représentés par un identifiant de type String préfixé
par le caractère deux-points (:). Lorsque la requête sera utilisée, il faudra fournir des
valeurs à ces paramètres nommés.
SELECT c
FROM Customer c
WHERE c.firstName = :fname AND c.address.country = :country
Nous verrons dans la section "Requêtes", plus loin dans ce chapitre, comment lier
ces paramètres dans une application.
Sous-requêtes
Une sous-requête est une requête SELECT intégrée dans l’expression conditionnelle
d’une clause WHERE ou HAVING. Le résultat de cette sous-requête est évalué et inter-
prété dans l’expression conditionnelle de la requête principale. Pour, par exemple,
obtenir les clients les plus jeunes de la base de données, on exécute d’abord une
sous-requête avec MIN(age) et l’on évalue son résultat dans la requête principale :
SELECT c
FROM Customer c
WHERE c.age = (SELECT MIN(c.age) FROM Customer c)
Order By
La clause ORDER BY permet de trier les entités ou les valeurs renvoyées par une
requête SELECT. Le tri s’applique à l’attribut précisé dans cette clause. Si cet attribut
est suivi d’ASC ou d’aucun mot-clé, le tri sera ascendant ; s’il est suivi de DESC, le tri
sera descendant.
SELECT c
FROM Customer c
WHERE c.age > 18
ORDER BY c.age DESC
Group By et Having
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
170 Java EE 6 et GlassFish 3
de l’expression de la clause GROUP BY. Pour, par exemple, regrouper les clients par
pays et les compter, on utilisera la requête suivante :
SELECT c.address.country, count(c)
FROM Customer c
GROUP BY c.address.country
Suppressions multiples
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 171
L’instruction suivante, par exemple, supprime tous les clients âgés de moins de
18 ans :
DELETE FROM Customer c
WHERE c.age < 18
L’instruction UPDATE permet de modifier toutes les entités répondant aux critères de
sa clause WHERE. Sa syntaxe est décrite dans le Listing 4.25.
L’instruction suivante, par exemple, modifie le prénom de tous nos jeunes clients en
"trop jeune" :
UPDATE Customer c
SET c.firstName = ’TROP JEUNE’
WHERE c.age < 18
Requêtes
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
172 Java EE 6 et GlassFish 3
Le choix entre ces quatre types est centralisé au niveau de l’interface EntityMana-
ger, qui dispose de plusieurs méthodes fabriques (voir Tableau 4.3) renvoyant toutes
une interface Query.
Méthode Description
Query createQuery(String jpqlString) Crée une instance de Query permettant
d’exécuter une instruction JPQL pour des
requêtes dynamiques.
Query createQuery(QueryDefinition qdef) Crée une instance de Query permettant
d’exécuter une requête par critère.
Query createNamedQuery(String name) Crée une instance de Query permettant
d’exécuter une requête nommée (en JPQL ou
en SQL natif).
Query createNativeQuery(String Crée une instance de Query permettant
sqlString) d’exécuter une instruction SQL native.
Query createNativeQuery(String Crée une instance de Query permettant
sqlString, Class resultClass) d’exécuter une instruction SQL native en lui
passant la classe du résultat attendu.
Une API complète permet de contrôler l’implémentation de Query obtenue par l’une
de ces méthodes. L’API Query, présentée dans le Listing 4.26, est utilisable avec les
requêtes statiques (requêtes nommées) et les requêtes dynamiques en JPQL, ainsi
qu’avec les requêtes natives en SQL. Cette API permet également de lier des para-
mètres aux requêtes et de contrôler la pagination.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 173
Les méthodes les plus utilisées de cette API sont celles qui exécutent la requête.
Ainsi, pour effectuer une requête SELECT, vous devez choisir entre deux méthodes
en fonction du résultat que vous voulez obtenir :
■■ La méthode getResultList() exécute la requête et renvoie une liste de résultats
(entités, attributs, expressions, etc.).
■■ La méthode getSingleResult() exécute la requête et renvoie un résultat unique.
Pour exécuter une mise à jour ou une suppression, utilisez la méthode execu-
teUpdate(), qui exécute la requête et renvoie le nombre d’entités concernées par
son exécution.
Comme nous l’avons vu plus haut dans la section "JPQL", une requête peut prendre
des paramètres nommés (:monParam, par exemple) ou positionnels (?1, par exemple).
L’API Query définit plusieurs méthodes setParameter() pour initialiser ces para-
mètres avant l’exécution d’une requête.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
174 Java EE 6 et GlassFish 3
Les sections qui suivent décrivent les trois types de requêtes en utilisant quelques-
unes des méthodes que nous venons de décrire.
Requêtes dynamiques
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 175
La requête précédente récupère les clients prénommés Vincent, mais vous voudrez
peut-être pouvoir choisir ce prénom et le passer en paramètre : vous pouvez le faire
en utilisant des noms ou des positions. Dans l’exemple suivant, on utilise un para-
mètre nommé :fname (notez le préfixe deux-points) dans la requête et on le lie à une
valeur avec la méthode setParameter() :
jpqlQuery = "SELECT c FROM Customer c";
if (someCriteria)
jpqlQuery += " where c.firstName = :fname";
query = em.createQuery(jpqlQuery);
query.setParameter("fname", "Vincent");
List<Customer> customers = query.getResultList();
Notez que le nom de paramètre fname ne contient pas le symbole deux-points utilisé
dans la requête. Le code équivalent avec un paramètre positionnel serait le suivant :
jpqlQuery = "SELECT c FROM Customer c";
if (someCriteria)
jpqlQuery += " where c.firstName = ?1";
query = em.createQuery(jpqlQuery);
query.setParameter(1, "Vincent");
List<Customer> customers = query.getResultList();
Si vous voulez paginer la liste des clients par groupes de dix, utilisez la méthode
setMaxResults() de la façon suivante :
Requêtes nommées
Les requêtes nommées sont différentes des requêtes dynamiques parce qu’elles sont
statiques et non modifiables. Bien que cette nature statique n’offre pas la souplesse
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
176 Java EE 6 et GlassFish 3
des requêtes dynamiques, l’exécution des requêtes nommées peut être plus efficace
car le fournisseur de persistance peut traduire la chaîne JPQL en SQL au démarrage
de l’application au lieu d’être obligé de le faire à chaque fois que la requête est
exécutée.
Les requêtes nommées sont exprimées dans les métadonnées via une annotation @
NamedQuery ou son équivalent XML. Cette annotation prend deux éléments : le nom
de la requête et son contenu. Dans le Listing 4.27, nous modifions l’entité Customer
pour définir trois requêtes statiques à l’aide d’annotations.
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private Integer age;
private String email;
@OneToOne @JoinColumn(name = "address_fk")
private Address address;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 177
Le fragment de code qui suit appelle la requête findWithParam en lui passant le para-
mètre fname et en limitant le nombre de résultats à 3 :
Query query = em.createNamedQuery("findWithParam");
query.setParameter("fname", "Vincent");
query.setMaxResults(3);
List<Customer> customers = query.getResultList();
La plupart des méthodes de l’API Query renvoyant un objet Query, vous pouvez
utiliser un raccourci élégant qui consiste à appeler les méthodes les unes après les
autres (setParameter().setMaxResults(), etc.).
Query query = em.createNamedQuery("findWithParam").
„ setParameter("fname", "Vincent").setMaxResults(3);
List<Customer> customers = query.getResultList();
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
178 Java EE 6 et GlassFish 3
ce risque, vous pouvez remplacer ce nom par une constante. Le Listing 4.28 montre
comment refactoriser l’entité Customer.
Listing 4.28 : L’entité Customer définit une requête nommée à l’aide d’une constante
@Entity
@NamedQuery(name = Customer.FIND_ALL,
query="select c from Customer c")
public class Customer {
Requêtes natives
JPQL dispose d’une syntaxe riche permettant de gérer les entités sous n’importe
quelle forme et de façon portable entre les différentes bases de données, mais JPA
autorise également l’utilisation des fonctionnalités spécifiques d’un SGBDR via
des requêtes natives. Celles-ci prennent en paramètre une instruction SQL (SELECT,
UPDATE ou DELETE) et renvoient une instance de Query pour exécuter cette instruc-
tion. En revanche, les requêtes natives peuvent ne pas être portables d’une base de
données à l’autre.
Si le code n’est pas portable, pourquoi alors ne pas utiliser des appels JDBC ? La
raison principale d’utiliser des requêtes JPA natives plutôt que des appels JDBC
est que le résultat de la requête sera automatiquement converti en entités. Pour,
par exemple, récupérer toutes les entités Customer de la base en utilisant SQL,
vous devez appeler la méthode EntityManager.createNativeQuery(), qui prend
en paramètre la requête SQL et la classe d’entité dans laquelle le résultat sera
traduit :
Query query = em.createNativeQuery("SELECT * FROM t_customer",
Customer.class);
List<Customer> customers = query.getResultList();
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 179
Comme vous pouvez le constater, la requête SQL est une chaîne qui peut être
créée dynamiquement en cours d’exécution (exactement comme les requêtes JPQL
dynamiques). Là aussi la requête pourrait être complexe et, ne la connaissant pas
à l’avance, le fournisseur de persistance sera obligé de l’interpréter à chaque fois,
ce qui aura des répercussions sur les performances de l’application. Toutefois,
comme les requêtes nommées, les requêtes natives peuvent utiliser le mécanisme
des annotations pour définir des requêtes SQL statiques. Ici, cette annotation s’ap-
pelle @NamedNativeQuery et peut être placée sur n’importe quelle entité (voir Lis-
ting 4.29) – comme avec JPQL, le nom de la requête doit être unique dans l’unité
de persistance.
Concurrence
JPA peut servir à modifier des données persistantes et JPQL permet de récupérer des
données répondant à certains critères. L’application qui les utilise peut s’exécuter
dans un cluster de plusieurs nœuds, avoir plusieurs threads et une seule base de don-
nées : il est donc assez fréquent d’accéder aux entités de façon concurrente. Dans
cette situation, l’application doit contrôler la synchronisation des données au moyen
d’un mécanisme de verrouillage. Que votre programme soit simple ou complexe,
il y a de grandes chances pour que vous soyez obligé d’utiliser des verrous à un
endroit ou à un autre de votre code.
Pour illustrer le problème de l’accès concurrent à une base de données, prenons
l’exemple d’une application comprenant les deux méthodes de la Figure 4.3. L’une
des méthodes recherche un livre par son identifiant et augmente son prix de 2 €.
L’autre fait la même chose, mais augmente le prix de 5 €. Si les deux méthodes sont
exécutées en même temps dans des transactions distinctes et qu’elles manipulent le
même livre, vous ne pouvez donc pas prévoir le prix final. Dans notre exemple, son
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
180 Java EE 6 et GlassFish 3
prix initial était de 10 € : selon la transaction qui se termine en dernier, son prix final
sera de 12 € ou de 15 €.
tx1.begin() tx2.begin()
tx1.comit(); tx2.comit();
// Le prix est maintenant de 12€ // Le prix est maintenant de 15€
temps
Figure 4.3
Les transactions tx1 et tx2 modifient le prix d’un livre de façon concurrente.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 181
Méthode Description
<T> T find(Class<T> entityClass, Recherche une entité de la classe avec la clé
Object primaryKey, LockModeType indiquée et la verrouille selon le type du verrou.
lockMode)
Méthode Description
Query setLockMode(LockModeType Fixe le type de verrou utilisé pour l’exécution de
lockMode) la requête.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
182 Java EE 6 et GlassFish 3
Gestion de version
Java utilise le système des versions : Java SE 5.0, Java SE 6.0, EJB 3.1, JAX-RS 1.0,
etc. Lorsqu’une nouvelle version de JAX-RS apparaît, par exemple, son numéro de
version est augmenté et vous mettez à jour votre environnement avec JAX-RS 1.1.
JPA utilise exactement le même mécanisme lorsque l’on a besoin de versions d’en-
tités. La première fois que vous rendez une entité persistante, elle prend le numéro
de version 1. Si, plus tard, vous modifiez un attribut et que vous répercutiez cette
modification dans la base de données, le numéro de version de l’entité passe à 2, etc.
La version de l’entité évolue à chaque fois qu’elle est modifiée.
Pour que ceci fonctionne, l’entité doit posséder un attribut annoté par @Version, lui
permettant de stocker son numéro de version. Cet attribut est ensuite traduit par une
colonne dans la base de données. Les types autorisés pour les numéros de version
sont int, Integer, short, Short, long, Long ou Timestamp. Le Listing 4.30 montre
comment ajouter un numéro de version à l’entité Book.
@Id @GeneratedValue
private Long id;
@Version
private Integer version;
private String title;
private Float price;
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
// Constructeurs, getters, setters
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 183
L’entité peut lire la valeur de sa version mais ne peut pas la modifier : seul le fournis-
seur de persistance peut initialiser ou modifier cette valeur lorsque l’objet est écrit
ou modifié dans la base de données. Dans le Listing 4.31, par exemple, on rend une
nouvelle entité Book persistante. Lorsque la transaction se termine, le fournisseur
de persistance fixe son numéro de version à 1. Puis on modifie le prix du livre et,
après l’écriture des données dans la base, le numéro de version est incrémenté et
vaut donc 2.
tx.begin();
em.persist(book);
tx.commit();
assertEquals(1, book.getVersion());
tx.begin();
book.raisePriceByTwoEuros();
tx.commit();
assertEquals(2, book.getVersion());
L’attribut de version n’est pas obligatoire, mais il est conseillé lorsque l’entité est
susceptible d’être modifiée en même temps par plusieurs processus ou plusieurs
threads. La gestion de version est au cœur du verrouillage optimiste car elle offre
une protection pour les modifications concurrentes épisodiques des entités. En fait,
une entité est automatiquement gérée par verrouillage optimiste lorsqu’elle utilise
l’annotation @Version.
Verrouillage optimiste
Comme son nom l’indique, le verrouillage optimiste part du principe que les tran-
sactions sur la base de données n’entreront pas en conflit les unes avec les autres.
En d’autres termes, on estime qu’il y a de fortes chances pour que la transaction qui
modifie une entité soit la seule à modifier cette entité à cet instant. La décision de
verrouiller l’entité est donc prise à la fin de la transaction, afin de garantir que les
modifications apportées à l’entité seront cohérentes avec l’état courant de la base
de données. Les transactions qui violeraient cette contrainte provoqueraient la levée
d’une exception OptimisticLockException et seraient annulées.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
184 Java EE 6 et GlassFish 3
tx1.begin(); tx2.begin();
book.raisePriceByTwoEuros();
book.raisePriceByFiveEuros();
tx1.comit();
// Le prix est maintenant de 12€
b sio ( == 2 tx2.comit();
d t êt 1 ll ut 2
p k p
temps
Figure 4.4
OptimisticLockException est lancée par la transaction tx2.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 4 Gestion des objets persistants 185
Verrouillage pessimiste
Le verrouillage optimiste convient bien lorsqu’il y a peu de contention entre les tran-
sactions mais, quand cette contention augmente, le verrouillage pessimiste peut se
révéler préférable car le verrou sur la base est obtenu immédiatement, alors que les
transactions optimistes échouent souvent plus tard. En temps de crise, par exemple,
les marchés boursiers reçoivent d’énormes ordres de ventes. Si 100 millions de per-
sonnes veulent vendre leurs actions en même temps, le système doit utiliser un ver-
rouillage pessimiste pour assurer la cohérence des données. Notez qu’actuellement
le marché est plutôt pessimiste qu’optimiste, mais cela n’a rien à voir avec JPA.
Le verrouillage pessimiste peut s’appliquer aux entités qui ne sont pas annotées par
@Version.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
186 Java EE 6 et GlassFish 3
Résumé
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
5
Méthodes de rappel et écouteurs
Au chapitre précédent, nous avons vu comment interroger les entités liées à une
base de données. Nous savons maintenant comment rendre une entité persistante,
la supprimer, la modifier et la retrouver à partir de son identifiant. Grâce à JPQL,
nous pouvons récupérer une ou plusieurs entités en fonction de certains critères de
recherche avec des requêtes dynamiques, statiques et natives. Toutes ces opérations
sont réalisées par le gestionnaire d’entités – la composante essentielle qui manipule
les entités et gère leur cycle de vie.
Nous avons décrit ce cycle de vie en écrivant que les entités sont soit gérées par
le gestionnaire d’entités (ce qui signifie qu’elles ont une identité de persistance et
qu’elles sont synchronisées avec la base de données), soit détachées de la base de
données et utilisées comme des POJO classiques. Mais le cycle de vie d’une entité
est un peu plus riche. Surtout, JPA permet d’y greffer du code métier lorsque cer-
tains événements concernent l’entité : ce code est ensuite automatiquement appelé
par le fournisseur de persistance à l’aide de méthodes de rappel.
Vous pouvez considérer les méthodes de rappel et les écouteurs comme les triggers
d’une base de données relationnelle. Un trigger exécute du code métier pour chaque
ligne d’une table alors que les méthodes de rappel et les écouteurs sont appelés sur
chaque instance d’une entité en réponse à un événement ou, plus précisément, avant
et après la survenue d’un événement. Pour définir ces méthodes "Pre" et "Post",
nous pouvons utiliser des annotations ou des descripteurs XML.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
188 Java EE 6 et GlassFish 3
la JVM que comme un simple POJO (elle était alors détachée) et pouvait être uti-
lisée par l’application comme un objet normal. Dès qu’une entité devient gérée, le
gestionnaire synchronise automatiquement la valeur de ses attributs avec la base de
données sous-jacente.
Pour mieux comprendre tout ceci, examinez la Figure 5.1, qui représente les états
que peut prendre une entité Customer, ainsi que les transitions entre ces états.
Figure 5.1
Customer cust = new Customer() Supprimée par le ramasse-miettes
Cycle de vie
d’une entité. Existe en mémoire
Customer cust = em.find()
Requête JPQL
em.persist(cust)
Supprimée de
la base de données,
mais toujours en mémoire
em.clear()
em.merge(cust)
Sérialisée vers une autre couche em.remove(cust)
Détachée Gérée Supprimée
em.merge(cust)
em.refresh(cust)
Modifiée avec les accesseurs
Base de
données
On crée une instance de l’entité Customer à l’aide de l’opérateur new. Dès lors, cet
objet existe en mémoire bien que JPA ne le connaisse pas. Si l’on n’en fait rien, il
devient hors de portée et finit par être supprimé par le ramasse-miettes, ce qui marque
la fin de son cycle de vie. Nous pouvons aussi le rendre persistant à l’aide de la
méthode EntityManager.persist(), auquel cas l’entité devient gérée et son état est
synchronisé avec la base de données. Pendant qu’elle est dans cet état, nous pouvons
modifier ses attributs en utilisant ses méthodes setters (customer. SetFirstName(),
par exemple) ou rafraîchir son contenu par un appel à EntityManager.refresh().
Toutes ces modifications garderont l’entité synchronisée avec la base. Si l’on appelle
la méthode EntityManager.contains(customer), celle-ci renverra true car customer
appartient au contexte de persistance (il est géré).
Un autre moyen de gérer une entité consiste à la charger à partir de la base de don-
nées à l’aide de la méthode EntityManager.find() ou d’une requête JPQL récupé-
rant une liste d’entités qui seront alors toutes automatiquement gérées.
Dans l’état géré, un appel à la méthode EntityManager.remove() supprime l’entité de
la base de données et elle n’est plus gérée. Cependant, l’objet Java continue d’exister
en mémoire, et il reste utilisable tant que le ramasse-miettes ne le supprime pas.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 5 Méthodes de rappel et écouteurs 189
Méthodes de rappel
Annotation Description
@PrePersist La méthode sera appelée avant l’exécution d’EntityManager.persist().
@PostPersist La méthode sera appelée après que l’entité sera devenue persistante. Si
l’entité produit sa clé primaire (avec @GeneratedValue), sa valeur est
accessible dans la méthode.
@PreUpdate La méthode sera appelée avant une opération de modification de l’entité
dans la base de données (appel des setters de l’entité ou de la méthode
EntityManager.merge()).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
190 Java EE 6 et GlassFish 3
Annotation Description
@PostLoad La méthode sera appelée après le chargement de l’entité (par une requête
JPQL, par un appel à EntityManager.find()) ou avant qu’elle soit
rafraîchie à partir de la base de données. Il n’existe pas d’annotation
@PreLoad car cela n’aurait aucun sens d’agir sur une entité qui n’a pas
encore été construite.
@PreRemove
Détachée Gérée Supprimée
Avant d’insérer une entité dans la base de données, le gestionnaire d’entités appelle
la méthode annotée par @PrePersist. Si l’insertion ne provoque pas d’exception,
l’entité est rendue persistante, son identifiant est créé, puis la méthode annotée par
@PostPersist est appelée. Il en va de même pour les mises à jour (@PreUpdate, @
PostUpdate) et les suppressions (@PreRemove, @PostRemove). Lorsqu’une entité est
chargée à partir de la base de données (via un appel à EntityManager.find() ou
une requête JPQL), la méthode annotée par @PostLoad est appelée. Lorsque l’entité
détachée a besoin d’être fusionnée, le gestionnaire d’entités doit d’abord vérifier si
la version en mémoire est différente de celle de la base (@PostLoad) et modifier les
données (@PreUpdate, @PostUpdate) si c’est le cas.
Outre les attributs, les constructeurs, les getters et les setters, les entités peuvent
contenir du code métier pour valider leur état ou calculer certains de leurs attributs.
Comme le montre le Listing 5.1, ce code peut être placé dans des méthodes Java clas-
siques invoquées par d’autres classes ou dans des méthodes de rappel (callbacks).
Dans ce dernier cas, c’est le gestionnaire d’entités qui les appellera automatiquement
en fonction de l’événement qui a été déclenché.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 5 Méthodes de rappel et écouteurs 191
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Transient
private Integer age;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
@PrePersist
@PreUpdate
private void validate() {
if (dateOfBirth.getTime() > new Date().getTime())
throw new IllegalArgumentException("Invalid date of birth");
if (!phoneNumber.startsWith("+"))
throw new IllegalArgumentException("Invalid phone number");
}
@PostLoad
@PostPersist
@PostUpdate
public void calculateAge() {
if (dateOfBirth == null) {
age = null;
return;
}
Calendar birth = new GregorianCalendar();
birth.setTime(dateOfBirth);
Calendar now = new GregorianCalendar();
now.setTime(new Date());
int adjust = 0;
if (now.get(DAY_OF_YEAR) - birth.get(DAY_OF_YEAR) < 0) {
adjust = -1;
}
age = now.get(YEAR) - birth.get(YEAR) + adjust;
}
Dans le Listing 5.1, l’entité Customer définit une méthode pour valider les données
(elle vérifie les valeurs des attributs dateOfBirth et phoneNumber). Cette méthode
étant annotée par @PrePersist et @PreUpdate, elle sera appelée avant l’insertion
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
192 Java EE 6 et GlassFish 3
ou la modification des données dans la base. Si ces données ne sont pas valides, la
méthode lèvera une exception à l’exécution et l’insertion ou la modification sera
annulée : ceci garantit que la base contiendra toujours des données valides.
La méthode calculateAge() calcule l’âge du client. L’attribut age est transitoire et
n’est donc pas écrit dans la base de données : lorsque l’entité est chargée, rendue
persistante ou modifiée, cette méthode calcule l’âge à partir de la date de naissance
et initialise l’attribut.
Les méthodes de rappel doivent respecter les règles suivantes :
■■ Elles peuvent avoir un accès public, privé, protégé ou paquetage, mais elles ne
peuvent pas être statiques ni finales. Dans le Listing 5.1, la méthode validate()
est privée.
■■ Elles peuvent être marquées par plusieurs annotations du cycle de vie (la méthode
validate() est annotée par @PrePersist et @PreUpdate). Cependant, une anno-
tation de cycle de vie particulière ne peut apparaître qu’une seule fois dans une
classe d’entité (il ne peut pas y avoir deux annotations @PrePersist dans la
même entité, par exemple).
■■ Elles peuvent lancer des exceptions non contrôlées mais pas d’exceptions contrô-
lées. Le lancement d’une exception annule la transaction s’il y en a une en cours.
■■ Elles peuvent invoquer JNDI, JDBC, JMS et les EJB, mais aucune opération
d’EntityManager ou de Query.
■■ Avec l’héritage, si une méthode est définie dans la superclasse, elle sera appelée
avant la méthode de la classe fille. Si, par exemple, la classe Customer du Lis-
ting 5.1 héritait d’une classe Person fournissant une méthode @PrePersist, cette
dernière serait appelée avant celle de Customer.
■■ Si une relation utilise la répercussion des événements, la méthode de rappel asso-
ciée sera également appelée en cascade. Si un Customer contient une collection
d’adresses et que la suppression d’un Customer soit répercutée sur Address, la
suppression d’un client invoquera la méthode @PreRemove d’Address et celle de
Customer.
Écouteurs (listeners)
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 5 Méthodes de rappel et écouteurs 193
Les méthodes de rappel d’une entité fonctionnent bien lorsque la logique métier
n’est liée qu’à cette entité. Les écouteurs permettent d’extraire cette logique dans
une classe séparée qui pourra être partagée par plusieurs entités. En réalité, un
écouteur d’entité est simplement un POJO qui définit une ou plusieurs méthodes
de rappel du cycle de vie. Pour enregistrer un écouteur, il suffit que l’entité utilise
l’annotation @EntityListeners.
Par rapport à l’exemple précédent, nous allons extraire les méthodes calculateAge()
et validate() pour les placer respectivement dans deux classes écouteurs, AgeCal-
culationListener (voir Listing 5.2) et DataValidationListener (voir Listing 5.3).
@PostLoad
@PostPersist
@PostUpdate
public void calculateAge(Customer customer) {
if (customer.getDateOfBirth() == null) {
customer.setAge(null);
return;
}
@PrePersist
@PreUpdate
private void validate(Customer customer) {
if (dateOfBirth.getTime() > new Date().getTime())
throw new IllegalArgumentException("Invalid date of birth");
if (!phoneNumber.startsWith("+"))
throw new IllegalArgumentException("Invalid phone number");
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
194 Java EE 6 et GlassFish 3
Une classe écouteur ne doit obéir qu’à des règles simples. La première est qu’elle
doit avoir un constructeur public sans paramètre. La seconde est que les signa-
tures des méthodes de rappel sont légèrement différentes de celles du Listing 5.1.
Lorsqu’elle est appelée sur un écouteur, une méthode de rappel doit en effet avoir
accès à l’état de l’entité (le prénom et le nom du client, par exemple) : elle doit donc
avoir un paramètre d’un type compatible avec celui de l’entité. Nous avons vu que,
lorsqu’elle est définie dans l’entité, une méthode de rappel a la signature suivante,
sans paramètre :
void <MÉTHODE>();
Les méthodes de rappel définies dans un écouteur peuvent en revanche avoir deux
types de signatures. Si une méthode doit servir à plusieurs entités, elle doit prendre
un paramètre de type Object :
void <MÉTHODE>(Object uneEntité)
Si elle n’est destinée qu’à une seule entité ou à ses sous-classes, le paramètre peut
être celui de l’entité :
void <MÉTHODE>(Customer customerOuSousClasses)
Pour indiquer que ces deux écouteurs seront prévenus des événements du cycle de
vie de l’entité Customer, celle-ci doit le préciser à l’aide de l’annotation @EntityLis-
teners (voir Listing 5.4). Cette annotation prend en paramètre une classe écouteur
ou un tableau d’écouteurs. Lorsqu’il y a plusieurs écouteurs et qu’un événement du
cycle de vie survient, le fournisseur de persistance parcourt chacun de ces écouteurs
dans l’ordre où ils ont été indiqués et invoquera la méthode de rappel en lui passant
une référence à l’entité concernée par l’événement. Puis il appellera les méthodes de
rappel de l’entité elle-même (s’il y en a).
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 5 Méthodes de rappel et écouteurs 195
Ce code produit exactement le même résultat que l’exemple précédent (voir Lis-
ting 5.1). L’entité Customer utilise la méthode DataValidationListener.vali-
date() pour valider ses données avant toute insertion ou mise à jour et la méthode
AgeCalculationListener.calculateAge() pour calculer son âge.
Les règles que doivent respecter les méthodes d’un écouteur sont les mêmes que
celles suivies par les méthodes de rappel, mis à part quelques détails :
■■ Elles ne peuvent lancer que des exceptions non contrôlées. Ceci implique que les
autres écouteurs et méthodes de rappel ne seront pas appelés et que l’éventuelle
transaction sera annulée.
■■ Dans une hiérarchie de classes, si plusieurs entités définissent des écouteurs,
ceux de la superclasse seront appelés avant ceux des sous-classes. Si une entité
ne veut pas hériter des écouteurs de sa superclasse, elle peut explicitement les
exclure à l’aide d’une annotation @ExcludeSuperclassListeners (ou son équi-
valent XML).
L’entité Customer du Listing 5.4 définissait deux écouteurs, mais il est également
possible qu’un écouteur soit défini par plusieurs entités, ce qui peut se révéler utile
lorsque l’écouteur fournit une logique générale dont les entités pourront profiter. Le
Listing 5.5, par exemple, crée un écouteur de débogage affichant le nom des événe-
ments déclenchés.
@PrePersist
void prePersist(Object object) {
System.out.println("prePersist");
}
@PostPersist
void postPersist(Object object) {
System.out.println("postPersist");
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
196 Java EE 6 et GlassFish 3
@PreUpdate
void preUpdate(Object object) {
System.out.println("preUpdate");
}
@PostUpdate
void postUpdate(Object object) {
System.out.println("postUpdate");
}
@PreRemove
void preRemove(Object object) {
System.out.println("preRemove");
}
@PostRemove
void postRemove(Object object) {
System.out.println("postRemove");
}
@PostLoad
void postLoad(Object object) {
System.out.println("postLoad");
}
}
Notez que chaque méthode prend un Object en paramètre, ce qui signifie que n’im-
porte quel type d’entité peut utiliser cet écouteur en ajoutant la classe de DebugLis-
tener à son annotation @EntityListeners. Cependant, pour que toutes les entités
d’une application utilisent cet écouteur, il faudrait ajouter manuellement cette anno-
tation à chacune d’elles : pour éviter cela, JPA permet de définir des écouteurs par
défaut qui couvrent toutes les entités d’une unité de persistance. Comme il n’existe
pas d’annotation s’appliquant à la portée entière d’une unité de persistance, ces
écouteurs par défaut ne peuvent être déclarés que dans un fichier d’association
XML.
Au Chapitre 3, nous avons vu comment utiliser les fichiers XML à la place des anno-
tations. Il suffit de suivre ici les mêmes étapes pour définir DebugListener comme
écouteur par défaut. Pour cela, vous devez créer et déployer avec l’application le
fichier XML présenté dans le Listing 5.6.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 5 Méthodes de rappel et écouteurs 197
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener
class="com.apress.javaee6.DebugListener"/>
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
@Id @GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Transient
private Integer age;
@Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
198 Java EE 6 et GlassFish 3
Résumé
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
6
Enterprise Java Beans
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
200 Java EE 6 et GlassFish 3
Ce chapitre est une introduction aux EJB et les trois chapitres suivants vous don-
neront toutes les informations nécessaires pour construire la couche métier d’une
application d’entreprise. Nous y expliquerons les différents types d’EJB et leurs
cycles de vie ; nous décrirons également la notion de programmation orientée aspect
(POA), ainsi que la gestion des transactions et de la sécurité.
Les EJB sont des composants côté serveur qui encapsulent la logique métier et la
prennent en charge ; ils s’occupent aussi de la sécurité. Les EJB savent également
traiter les messages, l’ordonnancement, l’accès distant, les services web (SOAP et
REST), l’injection de dépendances, le cycle de vie des composants, la programma-
tion orientée aspect avec intercepteurs, etc. En outre, ils s’intègrent parfaitement
avec les autres technologies de Java SE et Java EE – JDBC, JavaMail, JPA, JTA
(Java Transaction API), JMS (Java Messaging Service), JAAS (Java Authentica-
tion and Authorization Service), JNDI (Java Naming and Directory Interface) et
RMI (Remote Method Invocation). C’est la raison pour laquelle on les utilise pour
construire les couches métier (voir Figure 6.1) au-dessus de la couche de persistance
et comme point d’entrée pour les technologies de la couche présentation, comme
JSF (JavaServer Faces).
Figure 6.1
<<layer>>
Architecture en couches. Présentation
<<layer>>
Logique métier
<<layer>>
Persistance
<<layer>>
Base de données
Les EJB utilisent un modèle de programmation très puissant qui allie simplicité
d’utilisation et robustesse – il réduit la complexité tout en ajoutant la réutilisabilité et
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 201
Types d’EJB
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
202 Java EE 6 et GlassFish 3
INFO
Pour des raisons de compatibilité, la spécification EJB 3.1 mentionne encore les beans enti-
tés. Ce modèle de composants persistants a été élagué et est susceptible d’être supprimé de
Java EE 7. JPA étant la technologie qui a été retenue pour associer et interroger les bases de
données, nous ne présenterons pas les beans entités dans ce livre.
@PersistenceContext(unitName = "chapter06PU")
private EntityManager em;
public Book findBookById(Long id) {
return em.find(Book.class, id);
}
public Book createBook(Book book) {
em.persist(book);
return book;
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 203
Les versions précédentes de J2EE exigeaient des développeurs qu’ils créent plu-
sieurs artéfacts pour obtenir un bean de session : une interface locale ou distante (ou
les deux), une interface "home" locale ou distante (ou les deux) et un descripteur de
déploiement. Java EE 5 et EJB 3.0 ont considérablement simplifié ce modèle pour
ne plus exiger qu’une seule classe et une ou plusieurs interfaces métier. EJB 3.1
va encore plus loin puisqu’il permet à un POJO annoté d’être un bean de session.
Comme le montre le code du Listing 6.1, la classe n’implémente aucune interface et
n’utilise pas non plus de configuration XML : l’annotation @Stateless suffit à trans-
former une classe Java en composant transactionnel et sécurisé. Puis, en utilisant le
gestionnaire d’entités que nous avons présenté aux chapitres précédents, BookEJB
crée et récupère des livres de la base de données de façon simple mais efficace. Nous
verrons au chapitre suivant qu’il est également très simple de déclarer un bean à état
ou un bean singleton.
Cette simplicité s’applique aussi au code client. L’appel d’une méthode de BookEJB
ne nécessite qu’une seule annotation, @EJB, pour obtenir une référence à l’aide d’une
injection. Cette injection de dépendances permet à un conteneur (client, web ou
EJB) d’injecter automatiquement une référence vers un EJB. Dans le Listing 6.2,
par exemple, la classe Main obtient une référence à BookEJBRemote en annotant par @
EJB l’attribut statique et privé bookEJB. Si l’EJB est déployé dans un conteneur, Main
doit accéder à cet EJB à distance : il suffit d’ajouter une interface distante à l’EJB
pour qu’on puisse y accéder à distance.
@EJB
private static BookEJBRemote bookEJB;
Le Listing 6.2 montre l’une des différences qui existent entre une classe Java pure
et un bean de session. Ici, la classe Main n’utilise pas le mot-clé new pour créer une
instance de BookEJB : elle doit d’abord obtenir une référence à l’EJB – par injection
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
204 Java EE 6 et GlassFish 3
ou par une recherche JNDI – avant d’appeler l’une de ses méthodes. On utilise ce
mécanisme parce que l’EJB s’exécute dans un environnement géré – il doit être
déployé dans un conteneur (intégré ou non).
Comme la plupart des composants de Java EE 6, les EJB ont besoin des métadon-
nées (exprimées sous forme d’annotation ou de XML) pour informer le conteneur
des actions requises (démarcation des transactions) ou des services à injecter. Les
EJB peuvent être déployés avec le descripteur de déploiement facultatif ejb-jar.
xml, qui, s’il est présent, a priorité sur les annotations. Grâce à la configuration par
exception, une simple annotation suffit généralement à transformer un POJO en EJB
puisque le conteneur applique le comportement par défaut.
Conteneur d’EJB
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 205
transaction qu’il doit utiliser. C’est le conteneur qui prend en charge la validation
ou l’annulation des transactions.
■■ Sécurité. Les EJB peuvent préciser un contrôle d’accès au niveau de la classe ou
des méthodes afin d’imposer une authentification de l’utilisateur et l’utilisation
de rôles.
■■ Gestion de la concurrence. À part les singletons, tous les autres types d’EJB
sont thread-safe par nature. Vous pouvez donc développer des applications paral-
lèles sans vous soucier des problèmes liés aux threads.
■■ Intercepteurs transversaux. Les problèmes transversaux peuvent être placés
dans des intercepteurs qui seront automatiquement appelés par le conteneur.
■■ Appels de méthodes asynchrones. Avec EJB 3.1, il est désormais possible
d’avoir des appels asynchrones sans utiliser de messages.
Lorsque l’EJB est déployé, le conteneur s’occupe de toutes ces fonctionnalités, ce
qui permet au développeur de se concentrer sur la logique métier tout en bénéficiant
de ces services sans devoir ajouter le moindre code système.
Les EJB sont des objets gérés. Lorsqu’un client appelle un EJB (comme dans le Lis-
ting 6.2), il travaille non pas directement avec une instance de cet EJB mais avec un
proxy de cette instance. À chaque fois qu’un client invoque une méthode de l’EJB,
cet appel est en réalité pris en charge par le proxy. Tout ceci est, bien entendu, trans-
parent pour le client : de sa création à sa destruction, un EJB vit dans un conteneur.
Dans une application Java EE, le conteneur EJB interagira généralement avec
d’autres conteneurs : le conteneur de servlets (responsable de la gestion de l’exécu-
tion des servlets et des pages JSF), le conteneur client d’application (pour la gestion
des applications autonomes), le gestionnaire de messages (pour l’envoi, la mise en
attente et la réception des messages), le fournisseur de persistance, etc. Ces conte-
neurs s’exécutent tous dans un serveur d’applications (GlassFish, JBoss, Weblogic,
etc.) dont l’implémentation est spécifique mais qui fournit le plus souvent des fonc-
tionnalités de clustering, de montée en charge, de répartition de la charge, de reprise
en cas de panne, d’administration, de cache, etc.
Conteneur intégré
Dès le moment où ils sont créés, les EJB doivent s’exécuter dans un conteneur qui
s’exécute lui-même dans une JVM séparée. Pensez à GlassFish, JBoss, Weblogic, etc.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
206 Java EE 6 et GlassFish 3
et vous vous rappellerez que le serveur d’applications doit d’abord être lancé avant
que vous puissiez déployer et utiliser vos EJB. Pour un environnement en produc-
tion, où le serveur tourne en permanence, c’est tout à fait souhaitable ; par contre,
pour un environnement de développement où l’on a souvent besoin de déployer
pour déboguer, par exemple, cela prend trop de temps. Un autre problème avec les
serveurs qui s’exécutent dans un processus différent est que cela limite les pos-
sibilités de tests unitaires car ils ne peuvent s’exécuter simplement sans déployer
l’EJB sur un serveur. Pour résoudre ces problèmes, certaines implémentations de
serveurs d’applications étaient fournies avec des conteneurs intégrés, mais ceux-ci
leur étaient spécifiques. Désormais, EJB 3.1 contient la spécification d’un conteneur
intégré, ce qui assure la portabilité entre les différents serveurs.
Le principe d’un conteneur intégré est de pouvoir exécuter des applications EJB
dans un environnement Java SE afin de permettre aux clients de s’exécuter dans la
même JVM. Ceci permet notamment de faciliter les tests et l’utilisation des EJB
dans les applications classiques. L’API du conteneur intégré (définie dans javax.
ejb.embeddable) fournit le même environnement géré que le conteneur d’exécution
de Java EE et inclut les mêmes services : injection, accès à l’environnement d’un
composant, gestion des transactions, etc.
L’extrait de code suivant montre comment créer une instance d’un conteneur inté-
gré, obtenir un contexte JNDI, rechercher un EJB et appeler l’une de ses méthodes :
EJBContainer ec = EJBContainer.createEJBContainer();
Context ctx = ec.getContext();
BookEJB bookEJB = (BookEJB) ctx.lookup("java:global/BookEJB");
bookEJB.createBook(book);
Dans le prochain chapitre, nous verrons comment utiliser l’API de "bootstrap" pour
lancer le conteneur et exécuter les EJB.
Les EJB utilisent l’injection de dépendances pour accéder à différents types de res-
sources (autres EJB, destinations JMS, ressources d’environnement, etc.). Dans ce
modèle, le conteneur pousse les données dans le bean. Comme le montre le Lis-
ting 6.2, un client s’injecte une dépendance à un EJB à l’aide de l’annotation @EJB :
@EJB
private static BookEJB bookEJB;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 207
L’injection a lieu lors du déploiement. Si les données risquent de ne pas être utili-
sées, le bean peut éviter le coût de l’injection en effectuant à la place une recherche
JNDI. En ce cas, le code ne prend les données que s’il en a besoin au lieu d’accepter
des données qui lui sont transmises et dont il n’aura peut-être pas besoin.
JNDI est une API permettant d’accéder à différents types de services d’annuaires,
elle permet au client de lier et de rechercher des objets par nom. JNDI est définie
dans Java SE et est indépendante de l’implémentation sous-jacente, ce qui signifie
que les objets peuvent être recherchés dans un annuaire LDAP (Lightweight Direc-
tory Access Protocol) ou dans un DNS (Domain Name System) à l’aide d’une API
standard.
L’alternative au code précédent consiste donc à utiliser un contexte JNDI et à y
rechercher un EJB déployé portant le nom java:global/chapter06/BookEJB :
Context ctx = new InitialContext();
BookEJB bookEJB =
(BookEJB)ctx.lookup("java:global/chapter06/BookEJB");
JNDI existe depuis longtemps mais, bien que son API fût standardisée et portable
entre les serveurs d’applications, ce n’était pas le cas des noms JNDI, qui restaient
spécifiques aux plates-formes. Lorsqu’un EJB était déployé dans GlassFish ou
JBoss, son nom dans le service d’annuaire était différent et donc non portable : un
client devait rechercher un EJB avec un certain nom sous GlassFish et un autre sous
JBoss... EJB 3.1 a standardisé les noms JNDI afin qu’ils soient désormais portables.
Dans l’exemple précédent, le nom java:global/chapter06/BookEJB respecte cette
nouvelle convention de nommage :
java:global[/<nom-app>]/<nom-module>/<nom-bean>
„ [!<nom-interface-pleinement-qualifié>]
Le chapitre suivant montrera comment utiliser ce nom pour rechercher des EJB.
Le cycle de vie de tous les types d’EJB (sans et avec état, singleton et MDB) est géré
par le conteneur. Un EJB peut ainsi avoir des méthodes annotées (@PostConstruct, @
PreDestroy, etc.) ressemblant aux méthodes de rappel utilisées par les entités et qui
seront automatiquement appelées par le conteneur au cours des différentes étapes
de son cycle de vie. Ces méthodes peuvent initialiser l’état du bean, rechercher des
ressources avec JNDI ou libérer les connexions aux bases de données.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
208 Java EE 6 et GlassFish 3
Pour les problèmes transversaux, les développeurs peuvent utiliser des intercepteurs
qui reposent sur le modèle de la programmation par aspect, dans lequel l’appel d’une
méthode est automatiquement enrichi de fonctionnalités supplémentaires.
Les cycles de vie des EJB, les méthodes de rappel et les intercepteurs seront étudiés
au Chapitre 8 (le Chapitre 5 a présenté le cycle de vie des entités).
Assemblage
Comme la plupart des composants Java EE (servlets, pages JSF, services web, etc.),
les EJB doivent être assemblés avant d’être déployés dans un conteneur d’exécution.
Dans la même archive, on trouve généralement la classe bean métier, ses interfaces,
intercepteurs, les éventuelles superclasses ou superinterfaces, les exceptions, les
classes utilitaires et, éventuellement, un descripteur de déploiement (ejb-jar.xml).
Lorsque tous ces artéfacts sont assemblés dans un fichier jar, on peut les déployer
directement dans un conteneur. Une autre possibilité consiste à intégrer le fichier jar
dans un fichier ear (entreprise archive) et à déployer ce dernier.
Un fichier ear sert à assembler un ou plusieurs modules (des EJB ou des applications
web) en une archive unique afin que leur déploiement sur un serveur d’applications
soit simultané et cohérent. Comme le montre la Figure 6.2, pour déployer une appli-
cation web on peut assembler les EJB et les entités dans des fichiers jar séparés, les
servlets dans un fichier war et tout regrouper dans un fichier ear. Il suffit ensuite de
déployer ce fichier sur le serveur d’applications pour pouvoir manipuler les entités à
partir de la servlet en utilisant les EJB.
Figure 6.2 <<artifact>>
BookApplication.ear
Assemblage des EJB.
<<artifact>>
BookEJB.jar <<artifact>>
session/BookEJB.Class BookApplication.war
META-INF/ejb-jar.xml
session/BookEJB.class
<<artifact>> entity/Book.class
BookEntity.jar servlet.BookServlet
entity/Book.Class META INF/ejb jar.xml
META-INF/persitence.xml META INF/persistence.xml
WEB INF/web.xml
<<artifact>>
BookServlet.war
servlet/BookServlet.Class
WEB-INF/web.xml
Depuis EJB 3.1, les EJB peuvent également être assemblés directement dans un
module web (un fichier war). À droite de la Figure 6.2, la servlet, l’EJB et l’en-
tité sont tous assemblés dans le même fichier war, avec tous les descripteurs de
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 209
La spécification EJB 1.0 remonte à 1998 et EJB 3.1 est apparue en 2009 avec Java
EE 6. Au cours de ces dix années, la spécification a beaucoup évolué tout en conser-
vant ses bases solides. Des composants lourds aux POJO annotés en passant par les
beans entités et par JPA, les EJB se sont réinventés afin de mieux correspondre aux
besoins des développeurs et des architectures modernes.
Plus que jamais, la spécification EJB 3.1 permet d’éviter la dépendance vis-à-vis des
éditeurs en fournissant des fonctionnalités qui, auparavant, n’étaient pas standard
(les noms JNDI ou les conteneurs intégrés, par exemple). Elle est donc bien plus
portable que par le passé.
Historique
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
210 Java EE 6 et GlassFish 3
par référence) et ceux qui s’exécutaient dans un autre conteneur utilisaient l’inter-
face distante. Cette version a également introduit les MDB, et les beans entités ont
reçu le support des relations et d’un langage de requêtes (EJB QL).
Deux ans plus tard, EJB 2.1 (JSR 153) a ajouté le support des services web, per-
mettant ainsi aux beans de session d’être invoqués par SOAP/HTTP. Un service
timer fut également créé pour pouvoir appeler les EJB à des instants précis ou à des
intervalles donnés.
Trois ans se sont écoulés entre EJB 2.1 et EJB 3.0, ce qui a permis au groupe d’ex-
perts de remodéliser entièrement la conception. En 2006, la spécification EJB 3.0
(JSR 220) amorça une rupture avec les versions précédentes en s’attachant à la sim-
plicité d’utilisation grâce à des EJB ressemblant plus à des POJO. Les beans entités
furent remplacés par une toute nouvelle spécification (JPA) et les beans de session
n’eurent plus besoin d’interfaces "home" ou spécifiques. L’injection des dépendances,
les intercepteurs et les méthodes de rappel du cycle de vie firent leur apparition.
En 2009, la spécification EJB 3.1 (JSR 318) fut intégrée à Java EE 6 ; elle poursuit
dans la voie de la version précédente en simplifiant encore le modèle de program-
mation et en lui ajoutant de nouvelles fonctionnalités.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 211
EJB Lite
Les Enterprise Java Beans sont le modèle de composant prédominant de Java EE 6
car c’est la méthode la plus simple pour effectuer des traitements métiers transac-
tionnels et sécurisés. Cependant, EJB 3.1 continue de définir les beans entités, les
interfaces "home", EJB QL, etc., ce qui signifie qu’un nouvel éditeur qui implémen-
terait la spécification EJB 3.1 devrait également implémenter les beans entités. Les
développeurs débutant avec les EJB seraient donc submergés par de nombreuses
technologies dont ils n’ont finalement pas besoin.
Pour toutes ces raisons, la spécification définit EJB Lite, un sous-ensemble minimal de
l’API EJB. Ce sous-ensemble comprend un choix réduit mais efficace des fonctionna-
lités des EJB adaptées à l’écriture d’une logique métier portable, transactionnelle et
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
212 Java EE 6 et GlassFish 3
sécurisée. Toute application EJB Lite peut être déployée sur n’importe quel produit
JAVA EE implémentant EJB 3.1. Le Tableau 6.1 énumère ses composantes.
Implémentation de référence
GlassFish est un projet de serveur d’applications open-source conduit par Sun
Microsystems pour la plate-forme Java EE. Lancé en 2005, il est devenu l’implé-
mentation de référence de Java EE 5 en 2006. Aujourd’hui, GlassFish v3 est l’im-
plémentation de référence d’EJB 3.1. Ce produit est construit de façon modulaire
(il repose sur le runtime OSGi Felix d’Apache), ce qui lui permet de démarrer très
rapidement, et il utilise différents conteneurs d’’applications (Java EE 6, bien sûr,
mais également Ruby, PHP, etc.).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 213
Récapitulatif
<<stateless ejb>>
BookEJB <<entity>>
em : EntityManager Book
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
214 Java EE 6 et GlassFish 3
Pour utiliser les transactions, le bean de session sans état doit accéder à la base en
passant par une source de données (jdbc/chapter06DS) qui devra être créée dans
GlassFish et être liée à la base chapter06DB.
La structure de répertoire du projet respecte les conventions de Maven ; les classes
et les fichiers seront donc placés dans les répertoires suivants :
■■ src/main/java, pour l’entité Book, BookEJB, l’interface BookEJBRemote et la
classe Main ;
■■ src/main/resources :, pour le fichier persistence.xml, qui contient l’unité de
persistance pour le SGBDR Derby ;
■■ src/test/java, pour la classe des tests unitaires BookTest ;
■■ src/test/resources, pour le fichier persistence.xml, utilisé pour la base de
données intégrée Derby servant aux cas de tests ;
■■ pom.xml, le fichier Maven décrivant le projet et ses dépendances vis-à-vis d’autres
modules et composants externes.
L’entité Book
Le Listing 6.3 décrivant la même entité Book que celle du Chapitre 2 (voir Lis-
ting 2.3), nous n’y reviendrons pas. Notez toutefois que son fichier doit se trouver
dans le répertoire src/main/java.
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 215
BookEJB est un bean de session sans état qui sert de façade et gère les opérations
CRUD de l’entité Book. Le Listing 6.4 montre que cette classe doit être annotée par @
javax.ejb.Stateless et qu’elle implémente l’interface BookEJBRemote décrite dans
le Listing 6.5. Par injection de dépendances, l’EJB obtient une référence à un ges-
tionnaire d’entités qui est ensuite employé pour chacune des méthodes suivantes :
■■ findBooks utilise la requête nommée findAllBooks définie dans l’entité Book pour
récupérer toutes les instances de Book dans la base de données.
■■ findBookById invoque EntityManager.find() pour retrouver un livre dans la base
à partir de son identifiant.
■■ createBook rend persistante l’instance de Book qui lui est passée en paramètre.
■■ updateBook utilise la méthode merge() pour attacher au gestionnaire d’entités
l’objet Book détaché qui lui est passé en paramètre. Cet objet est alors synchro-
nisé avec la base de données.
■■ deleteBook est une méthode qui réattache l’objet qui lui est passé en paramètre
au gestionnaire d’entités, puis le supprime.
Listing 6.4 : Bean de session sans état servant de façade aux opérations CRUD
@Stateless
public class BookEJB implements BookEJBRemote {
@PersistenceContext(unitName = "chapter06PU")
private EntityManager em;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
216 Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 217
La classe Main
@EJB
private static BookEJBRemote bookEJB;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
218 Java EE 6 et GlassFish 3
Nous pouvons maintenant utiliser Maven pour compiler l’entité Book, le BookEJB,
l’interface BookEJBRemote et la classe Main, puis assembler le résultat dans un fichier
jar avec l’unité de persistance. Maven utilise un fichier pom.xml (voir Listing 6.8)
pour décrire le projet et les dépendances externes. Ici, on a besoin de l’API JPA
(javax.persistence) et de l’API EJB (javax.ejb). Les classes seront compilées
et assemblées (<packaging>jar</packaging>) dans un fichier jar nommé chap-
ter06- 1.0.jar. Comme le montre le Listing 6.8, l’élément maven-compiler-plu-
gin indique à Maven que l’on utilise Java SE 6.
<modelVersion>4.0.0</modelVersion>
<groupId> com.apress.javaee6</groupId>
<artifactId>chapter06</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<name>chapter06</name>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.ejb</artifactId>
<version>3.0</version>
</dependency>
<dependency>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 219
<groupId>org.glassfish.embedded</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>3.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifest>
<mainClass>
com.apress.javaee6.chapter06.Main
</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
220 Java EE 6 et GlassFish 3
Maintenant que le bean de session BookEJB a été assemblé dans une archive jar, nous
pouvons le déployer sur le serveur d’applications GlassFish (après nous être assu-
rés que GlassFish et Derby s’exécutent). La source de données jdbc/chapter06DS
nécessaire à l’unité de persistance doit être créée à l’aide de la console d’administra-
tion de GlassFish ou à partir de la ligne de commandes, l’utilisation de cette dernière
étant la plus rapide et la plus simple à reproduire.
Avant de créer une source de données, nous avons besoin d’un pool de connexions.
GlassFish définit un ensemble de pools prêts à l’emploi, mais nous pouvons créer le
nôtre à l’aide de la commande suivante :
asadmin create-jdbc-connection-pool
„ --datasourceclassname=org.apache.derby.jdbc.ClientDataSource
„ --restype=javax.sql.DataSource
„ --property portNumber=1527:password=APP:user=APP:
„ serverName=localhost:databaseName=chapter06DB:
„ connectionAttributes=;create\=true Chapter06Pool
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 221
Maintenant que l’EJB est déployé sur GlassFish avec l’entité et l’unité de persis-
tance, que Derby s’exécute et que la source de données a été créée, il est temps de
lancer la classe Main.
La classe Main (voir Listing 6.7) est une application autonome qui s’exécute à l’ex-
térieur du conteneur GlassFish, mais elle utilise l’annotation @EJB, qui a besoin d’un
conteneur pour injecter une référence à l’interface BookEJBRemote. La classe Main
doit s’exécuter dans un conteneur client d’application (ACC) ; nous aurions pu uti-
liser une recherche JNDI au lieu d’un ACC, mais ce dernier peut comprendre un
fichier jar pour lui donner accès aux ressources du serveur d’applications. Pour exé-
cuter l’ACC, il suffit d’utiliser le programme appclient fourni avec GlassFish en lui
passant le fichier jar en paramètre :
appclient -client chapter06-1.0.jar
N’oubliez pas que le fichier chapter06-1.0.jar est exécutable puisque nous avons
ajouté un élément Main-Class au fichier MANIFEST.MF. Avec la commande précé-
dente, l’ACC exécute la classe Main et injecte une référence à l’interface BookEJB
Remote, qui, à son tour, crée, modifie et supprime l’entité Book.
La classe BookEJBTest
Pour les équipes de développement modernes, la classe Main ne suffit pas – il faut
appliquer des tests unitaires aux classes. Avec la version précédente des EJB, tester
unitairement BookEJB n’était pas chose facile car il fallait utiliser des fonctionnalités
spécifiques de certains serveurs d’applications ou bricoler le code. Désormais, grâce
au nouveau conteneur intégré, un EJB devient une classe testable comme une autre
car elle peut s’exécuter dans un environnement Java SE. La seule exigence consiste
à ajouter un fichier jar spécifique au classpath, comme on l’a fait dans le fichier pom.
xml du Listing 6.8 avec la dépendance glassfish-embedded-all.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
222 Java EE 6 et GlassFish 3
Au Chapitre 2, nous avons déjà présenté tous les artéfacts requis par les tests uni-
taires avec une base de données intégrée. Pour tester unitairement l’EJB, nous
avons besoin de la base Derby intégrée, d’une unité de persistance différente et du
conteneur d’EJB intégré. Il suffit ensuite d’écrire une classe de test JUnit (voir Lis-
ting 6.9) pour initialiser l’EJBContainer (EJBContainer.createEJBContainer()),
lancer quelques tests (createBook()) et fermer le conteneur (ec.close()).
@BeforeClass
public static void initContainer() throws Exception {
ec = EJBContainer.createEJBContainer();
ctx = ec.getContext();
}
@AfterClass
public static void closeContainer() throws Exception {
ec.close();
}
@Test
public void createBook() throws Exception {
// Création d’une instance de Book
Book book = new Book();
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Science fiction comedy book");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);
// Recherche de l’EJB
BookEJBRemote bookEJB = (BookEJBRemote)
„ ctx.lookup("java:global/chapter06/BookEJBRemote");
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 6 Enterprise Java Beans 223
BookEJBTest s’exécute et Maven devrait vous informer que le test s’est bien passé :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 12.691 sec
Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ---------------------------------------------------------------- [INFO]
BUILD SUCCESSFUL [INFO] ------------------------------------------------------
---------- [INFO] Total time: 26 seconds
[INFO] Finished [INFO] Final Memory: 4M/14M [INFO] ---------------------------
-------------------------------------
Résumé
Ce chapitre a présenté EJB 3.1. À partir des versions 2.x, la spécification EJB a
évolué pour passer d’un modèle lourd – dans lequel il fallait assembler les inter-
faces home et distante/locale avec une grande quantité de fichiers XML – à une
simple classe Java sans interface et avec une seule annotation. La fonctionnalité
sous-jacente est pourtant toujours la même : fournir une logique métier transaction-
nelle et sécurisée.
EJB 3.1 permet de simplifier encore plus le modèle de programmation (vue sans
interface, déploiement war), l’enrichit (conteneur intégré, singletons, service timer,
appels asynchrones) et améliore sa portabilité entre les serveurs d’applications
(noms JNDI standardisés). La simplification la plus importante est probablement
la création d’EJB Lite, un sous-ensemble de l’API EJB qui offre une version d’EJB
plus simple mais néanmoins efficace pouvant être utilisée dans le profil web de
Java EE. Le conteneur EJB intégré, de son côté, facilite la mise en place et la porta-
bilité des tests unitaires. EJB 3.1 est donc le digne successeur d’EJB 3.0.
Le Chapitre 7 s’intéressera aux beans de session avec et sans état, aux singletons et
au service timer. Le Chapitre 8 présentera les méthodes de rappel et les intercepteurs.
Le Chapitre 9 sera consacré aux transactions et à la sécurité.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
7
Beans de session et service timer
Les Chapitres 2 à 5 se sont intéressés aux objets persistants qui utilisent les entités
JPA. Ces entités encapsulent les données, l’association avec le modèle relationnel et,
parfois, la logique de validation. Nous allons maintenant présenter le développement
d’une couche métier qui gère ces objets persistants avec les beans de session qui
prennent en charge les tâches complexes nécessitant des interactions avec d’autres
composants (entités, services web, messages, etc.). Cette séparation logique entre
entités et beans de session respecte le paradigme de "séparation des problèmes"
selon lequel une application est divisée en plusieurs composants dont les opérations
se recouvrent le moins possible.
Dans ce chapitre, nous présenterons les trois types de beans de session : sans état,
avec état et singleton. Les premiers sont les plus adaptables des trois car ils ne
mémorisent aucune information et effectuent toute la logique métier dans un seul
appel de méthode. Les seconds gèrent un état conversationnel avec un seul client.
Les beans de session singletons (une seule instance par application) ont été ajoutés
dans la spécification EJB 3.1.
La dernière section du chapitre sera consacrée à l’utilisation du service timer pour
planifier les tâches.
Les beans pilotés par messages (MDB), qui font également partie de la spécification
EJB, seront présentés au Chapitre 13 avec JMS (Java Message Service). Comme
nous le verrons aux Chapitres 14 et 15, un bean de session sans état peut être trans-
formé en service web SOAP ou REST. Ou, plus exactement, ces services web peu-
vent profiter de certaines fonctionnalités d’EJB comme les transactions, la sécurité,
les intercepteurs, etc.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
226 Java EE 6 et GlassFish 3
Beans de session
Les beans de session sont parfaits pour implémenter la logique métier, les processus
et le workflow mais, avant de les utiliser, vous devez choisir le type qui convient :
■■ Sans état. Ne mémorisent aucun état conversationnel pour l’application. Ils
servent à gérer les tâches qui peuvent s’effectuer à l’aide d’un seul appel de
méthode.
■■ Avec état. Mémorisent l’état et sont associés à un client précis. Ils servent à gérer
les tâches qui demandent plusieurs étapes.
■■ Singletons. Implémentent le patron de conception Singleton. Le conteneur
s’assurera qu’il n’en existe qu’une seule instance pour toute l’application.
Bien que ces trois types de beans de session aient des fonctionnalités spécifiques,
ils en ont aussi beaucoup en commun et, surtout, ils utilisent tous le même modèle
de programmation. Comme nous le verrons plus tard, un bean de session peut avoir
une interface locale ou distante, ou aucune interface. Les beans de session sont des
composants gérés par un conteneur et doivent donc être assemblés dans une archive
(un fichier jar, war ou ear) et déployés dans le conteneur. Ce dernier est responsable
de la gestion de leur cycle de vie (qui sera étudié au chapitre suivant), des transac-
tions, des intercepteurs et de bien d’autres choses encore. La Figure 7.1 montre un
schéma très synthétique des beans de session et du service timer dans un conteneur
EJB.
Figure 7.1
<<executionEnvironment>>
Beans de session et service Conteneur EJB
<<component>>
Service timer
Les beans sans état sont les beans de session les plus connus dans les applications
Java EE. Ils sont simples, puissants, efficaces et répondent aux besoins fréquents des
tâches métiers. "Sans état" signifie simplement qu’une tâche doit se réaliser par un
seul appel de méthode.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 227
Les beans sans état sont la solution idéale lorsque l’on doit implémenter une tâche
qui peut se réaliser en un seul appel de méthode. Si l’on reprend le code précédent et
que l’on y ajoute un composant sans état, il faut donc créer un objet Book, initialiser
ses attributs, puis utiliser un composant sans état pour invoquer une méthode qui
stockera le livre en un seul appel. L’état est donc géré par Book, non par le composant
sans état.
Book book = new Book();
book.setTitle("The Hitchhiker’s Guide to the Galaxy");
book.setPrice(12.5F);
book.setDescription("Science fiction by Douglas Adams.");
book.setIsbn("1-84023-742-2");
book.setNbOfPage(354);
book.setIllustrations(false);
statelessComponent.persistToDatabase(book);
Les beans de session sans état sont également les beans les plus efficaces car ils peu-
vent être placés dans un pool pour y être partagés par plusieurs clients – le conteneur
conserve en mémoire un certain nombre d’instances (un pool) de chaque EJB sans
état et les partage entre les clients. Ces beans ne mémorisant pas l’état des clients,
toutes leurs instances sont donc équivalentes. Lorsqu’un client appelle une méthode
d’un bean sans état, le conteneur choisit une instance du pool et l’affecte au client ;
lorsque ce dernier en a fini, l’instance retourne dans le pool pour y être réutilisée.
Comme le montre la Figure 7.2, il suffit donc d’un petit nombre de beans pour
gérer plusieurs clients (le conteneur ne garantit pas qu’il fournira toujours la même
instance du bean pour un client donné).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
228 Java EE 6 et GlassFish 3
Figure 7.2
<<executionEnvironment>>
Conteneur EJB
Clients accédant à des
beans sans état placés <<component>> <<executionEnvironment>>
Client 1 Pool
dans un pool.
<<component>> <<component>>
Client 2 Instance 1
...
<<component>>
<<component>>
Instance 2
Client n
Le Listing 7.1 montre qu’un EJB sans état ressemble à une simple classe Java avec
uniquement une annotation @Stateless. Il peut utiliser n’importe quel service du
conteneur dans lequel il se trouve, notamment l’injection de dépendances. L’anno-
tation @PersistenceContext sert à injecter une référence de gestionnaire d’entités.
Le contexte de persistance des beans de session sans état étant transactionnel, toutes
les méthodes appelées sur cet EJB (createBook(), createCD(), etc.) le seront égale-
ment (nous y reviendrons plus en détail au Chapitre 9). Vous remarquerez que toutes
les méthodes reçoivent les paramètres nécessaires au traitement de la logique métier
en un seul appel : createBook(), par exemple, prend un objet Book en paramètre et
le rend persistant sans avoir besoin d’aucune autre information.
@PersistenceContext(unitName = "chapter07PU")
private EntityManager em;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 229
Les beans de session sans état offrent souvent plusieurs méthodes métiers étroite-
ment liées. Le bean ItemEJB du Listing 7.1, par exemple, définit des méthodes qui
concernent les articles vendus par l’application CD-BookStore : vous y trouverez
donc les opérations de création, de modification ou de recherche de livres et de CD,
ainsi que d’autres traitements métiers apparentés.
Grâce à l’annotation @javax.ejb.Stateless, le POJO ItemEJB devient un bean de
session sans état – elle transforme donc une simple classe Java en composant pour
conteneur. Le Listing 7.2 contient la spécification de cette annotation.
Le paramètre name précise le nom du bean, qui est, par défaut, celui de la classe
(ItemEJB dans l’exemple du Listing 7.1). Ce paramètre peut être utilisé pour recher-
cher un EJB particulier avec JNDI, par exemple. description est une chaîne permet-
tant de décrire l’EJB et mappedName est le nom JNDI global affecté par le conteneur
– ce dernier est spécifique à l’éditeur et n’est donc pas portable. mappedName n’a
aucun rapport avec le nom JNDI global et portable que nous avons évoqué au cha-
pitre précédent et que nous décrirons en détail dans la section "Accès JNDI global",
plus loin dans ce chapitre.
Les beans de session sans état peuvent supporter un grand nombre de clients en
minimisant les ressources nécessaires : c’est la raison pour laquelle les applications
qui les utilisent sont plus adaptables. Les beans de session avec état, au contraire, ne
sont liés qu’à un et un seul client.
Les beans sans état fournissent des méthodes métiers aux clients mais n’entretien-
nent pas d’état conversationnel avec eux. Les beans de session avec état, par contre,
préservent cet état : ils permettent donc d’implémenter les tâches qui nécessitent
plusieurs étapes, chacune tenant compte de l’état de l’étape précédente. Prenons
comme exemple le panier virtuel d’un site de commerce en ligne : un client se
connecte (sa session débute), choisit un premier livre et l’ajoute à son panier, puis
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
230 Java EE 6 et GlassFish 3
Le code précédent montre bien comment fonctionne un bean de session avec état.
Il crée deux livres et les ajoute au panier virtuel d’un composant avec état. À la fin,
la méthode checkOutShoppingCart() se fie a l’état mémorisé pour commander les
deux livres.
Quand un client invoque un bean avec état sur le serveur, le conteneur EJB doit four-
nir la même instance à chaque appel de méthode – ce bean ne peut pas être réutilisé
par un autre client. La Figure 7.3 montre la relation 1–1 qui s’établit entre l’instance
et le client ; du point de vue du développeur, aucun code supplémentaire n’est néces-
saire car cette relation est gérée automatiquement par le conteneur.
Figure 7.3
<<executionEnvironment>>
Clients accédant Conteneur EJB
<<component>> <<component>>
Client 2 Instance 2
Cette relation 1–1 a évidemment un prix : si l’on a 1 million de clients, ceci signifie
que l’on aura 1 million de beans en mémoire. Pour réduire cette occupation, les beans
doivent donc être supprimés temporairement de la mémoire entre deux requêtes
– cette technique est appelée passivation et activation. La passivation consiste à
supprimer une instance de la mémoire et à la sauvegarder dans un emplacement
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 231
persistant (un fichier sur disque, une base de données, etc.) : elle permet de libérer la
mémoire et les ressources. L’activation est le processus inverse : elle restaure l’état
et l’applique à une instance. Ces deux opérations sont réalisées par le conteneur : le
développeur n’a pas à s’en occuper – comme nous le verrons au prochain chapitre, il
doit simplement se charger de la libération des ressources (connexion à une base de
données ou à une fabrique JMS, etc.) avant que la passivation n’ait lieu.
Le Listing 7.3 applique l’exemple du panier virtuel à un bean avec état. Un client
se connecte au site web, parcourt le catalogue des articles et ajoute deux livres au
panier (à l’aide de la méthode addItem()). L’attribut cartItems stocke le contenu
du panier. Supposons que le client décide alors d’aller chercher un café : pendant
ce temps, le conteneur peut passiver l’instance pour libérer la mémoire, ce qui
entraîne la sauvegarde du contenu du panier dans une zone de stockage permanente.
Quelques minutes plus tard, le client revient et veut connaître le montant total de
son panier (avec la méthode getTotal()) avant de passer commande. Le conteneur
active donc l’EJB pour restaurer les données dans le panier et le client peut alors
commander (méthode checkout()) ses livres. Lorsqu’il se déconnecte, sa session se
termine et le conteneur libère la mémoire en supprimant définitivement l’instance
du bean en mémoire.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
232 Java EE 6 et GlassFish 3
@Remove
public void checkout() {
// Code métier
cartItems.clear();
}
@Remove
public void empty() {
cartItems.clear();
}
}
Ce que nous venons de voir pour le panier virtuel représente l’utilisation classique
des beans avec état, dans laquelle le conteneur gère automatiquement l’état conver-
sationnel. Ici, la seule annotation nécessaire est @javax.ejb.Stateful, qui utilise
les mêmes paramètres que @Stateless (voir Listing 7.2).
Nous avons également utilisé les annotations facultatives @javax.ejb.State-
fulTimeout et @javax.ejb.Remove. @Remove décore les méthodes checkout() et
empty() : leur appel provoquera désormais la suppression définitive de l’instance de
la mémoire. @StatefulTimeout met en place un délai d’expiration en millisecondes
– si le bean ne reçoit aucune demande du client au bout de ce délai, il sera supprimé
par le conteneur. Il est également possible de se passer de ces annotations en se fiant
au fait que le conteneur supprime automatiquement une instance lorsqu’une session
client se termine ou expire, mais s’assurer que le bean est détruit au moment adéquat
permet de réduire l’occupation mémoire, ce qui peut se révéler essentiel pour les
applications à haute concurrence.
Singletons
Un bean singleton est simplement un bean de session qui n’est instancié qu’une
seule fois par application. C’est donc une implémentation du fameux patron de
conception du Gang of Four, décrit dans l’ouvrage Design Patterns: Elements of
Reusable Object-Oriented Software, d’Erich Gamma, Richard Helm, Ralph John-
son et John M. Vlissides (Addison-Wesley, 1995). Il garantit qu’une seule instance
d’une classe existera dans l’application et fournit un point d’accès global vers cette
classe. Les objets singletons sont nécessaires dans toutes les situations où l’on n’a
besoin que d’un seul exemplaire d’un objet – pour décrire une souris, un gestion-
naire de fenêtres, un spooler d’impression, un système de fichiers, etc.
Un autre cas d’utilisation des singletons est la création d’un cache unique pour
toute l’application afin d’y stocker des objets. Dans un environnement géré par
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 233
private CacheSingleton() {
}
EJB 3.1 introduit les beans de session singletons qui respectent le patron de concep-
tion Singleton : une fois instancié, le conteneur garantit qu’il n’y aura qu’une seule
instance du singleton pour toute la durée de l’application. Comme le montre la
Figure 7.4, une instance est partagée par plusieurs clients. Les beans singletons
mémorisent leur état entre les appels des clients.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
234 Java EE 6 et GlassFish 3
Figure 7.4
<<executionEnvironment>>
Clients accédant Conteneur EJB
INFO
Les singletons ne sont pas compatibles avec les clusters. Un cluster est un groupe de conte-
neurs fonctionnant de concert (ils partagent les mêmes ressources, les mêmes EJB, etc.).
Lorsqu’il y a plusieurs conteneurs répartis en cluster sur des machines différentes, chaque
conteneur aura donc sa propre instance du singleton.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 235
Comme vous pouvez le constater, les beans de session sans état, avec état et sin-
gletons sont très simples à écrire puisqu’il suffit d’une seule annotation. Les sin-
gletons, toutefois, ont plus de possibilités : ils peuvent être initialisés au lancement
de l’application, chaînés ensemble, et il est possible de personnaliser leurs accès
concurrents.
Initialisation
Lorsqu’une classe client veut appeler une méthode d’un bean singleton, le conteneur
s’assure de créer l’instance ou d’utiliser celle qui existe déjà. Parfois, cependant,
l’initialisation d’un singleton peut être assez longue : CacheEJB peut, par exemple,
devoir accéder à une base de données pour charger un millier d’objets. En ce cas, le
premier appel au bean prendra du temps et le premier client devra attendre la fin de
son initialisation.
Pour éviter ce temps de latence, vous pouvez demander au conteneur d’initialiser un
bean singleton dès le démarrage de l’application en ajoutant l’annotation @Startup
à la déclaration du bean :
@Singleton
@Startup
public class CacheEJB {
// ...
}
Chaînage de singletons
Dans certains cas, l’ordre explicite des initialisations peut avoir une importance
lorsque l’on a plusieurs beans singletons. Supposons que le bean CacheEJB ait
besoin de stocker des données provenant d’un autre bean singleton (un CountryCo-
deEJB renvoyant tous les codes ISO des pays, par exemple) : ce dernier doit donc
être initialisé avant le CacheEJB. L’annotation @javax.ejb.DependsOn est justement
prévue pour exprimer les dépendances entre les singletons :
@Singleton
public class CountryCodeEJB {
...
}
@DependsOn("CountryCodeEJB")
@Singleton
public class CacheEJB {
...
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
236 Java EE 6 et GlassFish 3
@Singleton
public class ZipCodeEJB {
...
}
@DependsOn("CountryCodeEJB", "ZipCodeEJB")
@Startup
@Singleton
public class CacheEJB {
...
}
Comme vous pouvez le constater dans le code précédent, il vous est même possible
de combiner ces dépendances avec une initialisation lors du démarrage de l’appli-
cation : CacheEJB étant initialisé dès le lancement (car il est annoté par @Startup),
CountryCodeEJB et ZipCodeEJB le seront également, mais avant lui.
Concurrence
Un singleton n’ayant qu’une seule instance partagée par plusieurs clients, les accès
concurrents peuvent être contrôlés de trois façons différentes par l’annotation
@ConcurrencyManagement :
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 237
@AccessTimeout(2000)
@Lock(LockType.WRITE)
public Object getFromCache(Long id) {
if (cache.containsKey(id))
return cache.get(id);
else
return null;
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
238 Java EE 6 et GlassFish 3
Concurrence interdite
Les accès concurrents peuvent également être interdits sur une méthode ou sur
l’ensemble du bean : en ce cas, un client appelant une méthode en cours d’utilisation
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 239
@ConcurrencyManagement(ConcurrencyManagementType.CONCURRENCY_NOT_ALLOWED)
public void addToCache(Long id, Object object) {
if (!cache.containsKey(id))
cache.put(id, object);
}
@AccessTimeout(2000)
@Lock(LockType.WRITE)
public Object getFromCache(Long id) {
if (cache.containsKey(id))
return cache.get(id);
else
return null;
}
}
Pour l’instant, les exemples de beans de session que nous avons présentés utilisaient
le modèle de programmation le plus simple : un POJO annoté sans interface. En
fonction de vos besoins, les beans peuvent vous offrir un modèle bien plus riche
vous permettant de réaliser des appels distants, l’injection de dépendances ou des
appels asynchrones.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
240 Java EE 6 et GlassFish 3
Figure 7.5 Local
<<component>> <<component>>
Les beans de session Client Bean de session
Distant
peuvent avoir différents
types d’interfaces.
pas d'interface
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 241
<<executionEnvironment>>
<<executionEnvironment>> Conteneur client d'applications
Serveur d'applications
<<component>>
Application cliente
<<executionEnvironment>> <<executionEnvironment>> *
Conteneur web Conteneur EJB
<<component>>
<<component>> <<component>> Application cliente
*
Servlet Bean de session
Appel local
Appel <<executionEnvironment>>
local * Serveur d'applications
<<component>> <<component>>
JSF Bean de session
<<component>>
Composant quelconque
* : Appel distant
Figure 7.6
Beans de session appelés par plusieurs clients.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
242 Java EE 6 et GlassFish 3
Listing 7.9 : Bean de session sans état implémentant une interface distante et locale
@Local
public interface ItemLocal {
List<Book> findBooks();
List<CD> findCDs();
}
@Remote
public interface ItemRemote {
List<Book> findBooks();
List<CD> findCDs();
Book createBook(Book book);
CD createCD(CD cd);
}
@Stateless
public class ItemEJB implements ItemLocal, ItemRemote {
...
}
Listing 7.10 : Une classe bean définissant une interface locale et une interface distante
public interface ItemLocal {
List<Book> findBooks();
List<CD> findCDs();
}
@Stateless
@Remote (ItemRemote)
@Local (ItemLocal)
public class ItemEJB implements ItemLocal, ItemRemote {
...
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 243
Chapitres 14 et 15, nous ne les citons ici que parce qu’ils font partie des diffé-
rentes façons d’appeler un bean de session sans état, simplement en implémentant
des interfaces annotées différentes. Le Listing 7.11 présente un bean sans état avec
une interface locale, un service web SOAP (@WebService) et un service web REST
(@Path).
Listing 7.11 : Bean de session sans état implémentant une interface de services web
@Local
public interface ItemLocal {
List<Book> findBooks();
List<CD> findCDs();
}
@WebService
public interface ItemWeb {
List<Book> findBooks();
List<CD> findCDs();
Book createBook(Book book);
CD createCD(CD cd);
}
@Path(/items)
public interface ItemRest {
List<Book> findBooks();
}
Classes bean
Un bean de session sans état est une classe Java classique qui implémente une
logique métier. Pour qu’elle devienne une classe bean de session, elle doit satisfaire
les obligations suivantes :
■■ Elle doit être annotée par @Stateless, @Stateful, @Singleton ou leurs équivalents
XML dans un descripteur de déploiement.
■■ Elle doit implémenter les méthodes de ses éventuelles interfaces.
■■ Elle doit être publique et ni finale ni abstraite.
■■ Elle doit fournir un constructeur public sans paramètre qui servira à créer les
instances.
■■ Elle ne doit pas définir de méthode finalize().
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
244 Java EE 6 et GlassFish 3
■■ Les noms des méthodes métiers ne doivent pas commencer par ejb et ne peuvent
être ni finals ni statiques.
■■ Le paramètre et la valeur de retour d’une méthode distante doivent être d’un type
reconnu par RMI.
Vue cliente
Maintenant que nous avons vu des exemples de beans de session et de leurs diffé-
rentes interfaces, nous pouvons étudier la façon dont le client les appelle. Le client
d’un bean de session peut être n’importe quel type de composant : un POJO, une
interface graphique (Swing), une servlet, un bean géré par JSF, un service web (SOAP
ou REST) ou un autre EJB (déployé dans le même conteneur ou dans un autre).
Pour appeler une méthode d’un bean de session, un client n’instancie pas directe-
ment le bean avec l’opérateur new. Pourtant, il a besoin d’une référence à ce bean
(ou à l’une de ses interfaces) : il peut en obtenir une via l’injection de dépendances
(avec l’annotation @EJB) ou par une recherche JNDI. Sauf mention contraire, un
client invoque un bean de façon synchrone mais, comme nous le verrons plus loin,
EJB 3.1 autorise maintenant les appels de méthodes asynchrones.
@EJB
Java EE utilise plusieurs annotations pour injecter des références de ressources
(@Resource), de gestionnaires d’entités (@PersistenceContext), de services web (@Web-
ServiceRef), etc. L’annotation @javax.ejb.EJB, en revanche, est spécialement conçue
pour injecter des références de beans de session dans du code client. L’injection de
dépendances n’est possible que dans des environnements gérés, comme les conte-
neurs EJB, les conteneurs web et les conteneurs clients d’application.
Reprenons nos premiers exemples dans lesquels les beans de session n’avaient pas
d’interface. Pour qu’un client invoque une vue de bean sans interface, il doit obtenir
une référence à la classe elle-même. Dans le code suivant, par exemple, le client
obtient une référence à la classe ItemEJB en utilisant l’annotation @EJB :
@Stateless
public class ItemEJB {
...
}
// Code client
@EJB ItemEJB itemEJB;
Si le bean de session implémente plusieurs interfaces, par contre, le client devra indi-
quer celle qu’il veut référencer. Dans le code qui suit, le bean ItemEJB implémente
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 245
deux interfaces et le client peut invoquer cet EJB via son interface locale ou distante,
mais il ne peut plus l’invoquer directement.
@Stateless
@Remote (ItemRemote)
@Local (ItemLocal)
public class ItemEJB implements ItemLocal, ItemRemote {
...
}
// Code client
@EJB ItemEJB itemEJB; // Impossible
@EJB ItemLocal itemEJBLocal;
@EJB ItemRemote itemEJBRemote;
Si le bean expose au moins une interface, il doit préciser qu’il propose également
une vue sans interface en utilisant l’annotation @LocalBean. Comme vous pouvez
le constater dans le code suivant, le client peut maintenent appeler le bean via son
interface locale, distante, ou directement via sa classe.
@Stateless
@Remote (ItemRemote)
@Local (ItemLocal)
@LocalBean
public class ItemEJB implements ItemLocal, ItemRemote {
...
}
// Code client
@EJB ItemEJB itemEJB;
@EJB ItemLocal itemEJBLocal;
@EJB ItemRemote itemEJBRemote;
Si l’injection n’est pas possible (lorsque le composant n’est pas géré par le conte-
neur), vous pouvez utiliser JNDI pour rechercher les beans de session à partir de leur
nom JNDI portable.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
246 Java EE 6 et GlassFish 3
Les différentes parties d’un nom JNDI ont les significations suivantes :
■■ <nom-app> est facultative car cette partie ne s’applique que si le bean est assem-
blé dans un fichier ear. En ce cas, <nom-app> est, par défaut, le nom du fichier ear
(sans l’extension .ear).
■■ <nom-module> est le nom du module dans lequel a été assemblé le bean de ses-
sion. Ce module peut être un module EJB dans un fichier jar autonome ou un
module web dans un fichier war. Par défaut, <nom-module> est le nom du fichier
archive, sans son extension.
■■ <nom-bean> est le nom du bean de session.
■■ <nom-interface-pleinement-qualifié> est le nom pleinement qualifié de chaque
interface métier qui a été définie. Dans le cas des vues sans interface, ce nom est
le nom pleinement qualifié de la classe du bean.
Pour illustrer cette convention, prenons l’exemple du bean ItemEJB. ItemEJB est
le <nom-bean> et est assemblé dans l’archive cdbookstore.jar (le <nom-module>).
L’EJB a une interface distante et une vue sans interface (signalée par l’annota-
tion @LocalBean). Lorsqu’il sera déployé, le conteneur créera donc les noms JNDI
suivants :
package com.apress.javaee6;
@Stateless
@LocalBean
@Remote (ItemRemote)
public class ItemEJB implements ItemRemote {
...
}
// noms JNDI
java:global/cdbookstore/ItemEJB!com.apress.javaee6.ItemEJB
java:global/cdbookstore/ItemEJB!com.apress.javaee6.ItemRemote
Outre cette convention, si le bean n’expose qu’une seule interface cliente (ou n’a
qu’une vue sans interface), le conteneur enregistre une entrée JNDI pour cette vue
avec la syntaxe suivante :
java:global[/<nom-app>]/<nom-module>/<nom-bean>
Le code qui suit représente le bean ItemEJB avec seulement une vue sans interface.
Le nom JNDI est alors uniquement composé du nom du module (cdbookstore) et
de celui du bean.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 247
package com.apress.javaee6;
@Stateless
public class ItemEJB {
...
}
// Nom JNDI
} java:global/cdbookstore/ItemEJB
Contexte de session
Les beans de session sont des composants métiers résidant dans un conteneur.
Généralement, ils n’accèdent pas au conteneur et n’utilisent pas directement ses
services (transactions, sécurité, injection de dépendances, etc.), qui sont prévus pour
être gérés de façon transparente par le conteneur pour le compte du bean. Parfois,
cependant, le bean a besoin d’utiliser explicitement les services du conteneur (pour
annuler explicitement une transaction, par exemple) : en ce cas, il doit passer par
l’interface javax.ejb.SessionContext, qui donne accès au contexte d’exécution
qui lui a été fourni. SessionContext hérite de l’interface javax.ejb.EJBContext ;
une partie des méthodes de son API est décrite dans le Tableau 7.1.
Méthode Description
getCallerPrincipal Renvoie le java.security.Principal associé à l’appel.
getRollbackOnly Teste si la transaction courante a été marquée pour annulation.
getTimerService Renvoie l’interface javax.ejb.TimerService. Cette méthode
ne peut être utilisée que par les beans sans état et singletons. Les
beans avec état ne peuvent pas utiliser les services timer.
getUserTransaction Renvoie l’interface javax.transaction.UserTransaction
permettant de délimiter les transactions. Cette méthode ne peut être
utilisée que par les beans de session avec des transactions gérées
par les beans (BMT).
isCallerInRole Teste si l’appelant a fourni un rôle de sécurité précis.
setRollbackOnly Autorise le bean à marquer la transaction pour annulation. Cette
méthode ne peut être utilisée que par les beans avec BMT.
wasCancelCalled Teste si un client a appelé la méthode cancel() sur l’objet Future
client correspondant à la méthode métier asynchrone en cours
d’exécution.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
248 Java EE 6 et GlassFish 3
Un bean de session peut avoir accès à son contexte d’environnement en injectant une
référence SessionContext à l’aide d’une annotation @Resource.
@Stateless
public class ItemEJB {
@Resource
private SessionContext context;
...
public Book createBook(Book book) {
...
if (cantFindAuthor())
context.setRollbackOnly();
}
}
Descripteur de déploiement
Les composants Java EE 6 utilisent une configuration par exception, ce qui signifie
que le conteneur, le fournisseur de persistance ou le serveur de message appli-
queront un ensemble de services par défaut à ces composants. Si l’on souhaite
disposer d’un comportement particulier, il faut explicitement utiliser une anno-
tation ou son équivalent XML : c’est ce que nous avons déjà fait avec les entités
JPA pour personnaliser les associations. Ce principe s’applique également aux
beans de session : une seule annotation (@Stateless, @Stateful, etc.) suffit pour
que le conteneur applique certains services (transaction, cycle de vie, sécurité,
intercepteurs, concurrence, asynchronisme, etc.) mais, si vous voulez les modi-
fier, d’autres annotations (ou leurs équivalents XML) sont à votre disposition. Les
annotations et les descripteurs de déploiement XML permettent en effet d’attacher
des informations supplémentaires à une classe, une interface, une méthode ou une
variable.
Un descripteur de déploiement XML est une alternative aux annotations, ce qui
signifie que toute annotation a un marqueur XML équivalent. Lorsque les deux
mécanismes sont utilisés, la configuration décrite dans le descripteur de déploie-
ment a priorité sur les annotations. Nous ne rentrerons pas ici dans les détails de la
structure d’un descripteur de déploiement XML (stocké dans un fichier nommé ejb-
jar.xml) car il est facultatif et peut être très verbeux. Le Listing 7.12 montre à quoi
pourrait ressembler le fichier ejb-jar.xml d’ItemEJB (voir Listing 7.9). Il définit
la classe bean, l’interface locale et distante, son type (Stateless) et indique qu’il
utilise des transactions gérées par le conteneur (CMT). L’élément <env-entry> défi-
nit les entrées de l’environnement du bean de session. Nous y reviendrons dans la
section "Contexte de nommage de l’environnement".
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 249
Injection de dépendances
Nous avons déjà évoqué l’injection de dépendances, et vous la rencontrerez encore
plusieurs fois dans les prochains chapitres. Il s’agit d’un mécanisme simple mais
puissant utilisé par Java EE 6 pour injecter des références de ressources dans des
attributs : au lieu que l’application recherche les ressources dans JNDI, celles-ci
sont injectées par le conteneur.
Les conteneurs peuvent injecter différents types de ressources dans les beans de
session à l’aide de plusieurs annotations (ou descripteurs de déploiement) :
■■ @EJB injecte dans la variable annotée une référence de la vue locale, distante ou
sans interface de l’EJB.
■■ @PersistenceContext et @PersistenceUnit expriment, respectivement, une
dépendance sur un EntityManager et sur une EntityManagerFactory.
■■ @WebServiceRef injecte une référence à un service web.
■■ @Resource injecte plusieurs ressources, comme les sources de données JDBC,
les contextes de session, les transactions utilisateur, les fabriques de connexion
JMS et les destinations, les entrées d’environnement, le service timer, etc.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
250 Java EE 6 et GlassFish 3
L’extrait de code du Listing 7.13 montre un extrait de bean de session sans état
utilisant plusieurs annotations pour injecter différentes ressources dans les attributs.
Vous remarquerez que ces annotations peuvent porter sur les variables d’instance
ainsi que sur les méthodes setter.
@PersistenceContext(unitName = "chapter07PU")
private EntityManager em;
@EJB
private CustomerEJB customerEJB;
@WebServiceRef
private ArtistWebService artistWebService;
@Resource
public void setCtx(SessionContext ctx) {
this.ctx = ctx;
}
...
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 251
Comme vous l’aurez compris, coder en dur ces paramètres implique de modifier le
code, de le recompiler et de redéployer le composant pour chaque pays ayant une
monnaie différente. Une autre possibilité consisterait à utiliser une base de données
à chaque appel de la méthode convertPrice(), mais cela gaspillerait des ressources.
En réalité, on veut simplement stocker ces paramètres à un endroit où ils pourront
être modifiés lors du déploiement : le descripteur de déploiement est donc un empla-
cement de choix.
Avec EJB 3.1, le descripteur de déploiement (ejb-jar.xml) est facultatif, mais son
utilisation est justifiée lorsque l’on a des paramètres liés à l’environnement. Ces
entrées peuvent en effet être placées dans le fichier et être accessibles via l’injec-
tion de dépendances (ou par JNDI). Elles peuvent être de type String, Character,
Byte, Short, Integer, Long, Boolean, Double et Float. Le Listing 7.15, par exemple,
montre que le fichier ejb-jar.xml d’ItemConverterEJB définit deux entrées : cur-
rencyEntry, de type String et de valeur Euros, et changeRateEntry, de type Float
et de valeur 0.80.
Maintenant que les paramètres de l’application ont été externalisés dans le descrip-
teur de déploiement, ItemConverterEJB peut utiliser l’injection de dépendances pour
obtenir leurs valeurs. Dans le Listing 7.16, @Resource(name = "currencyEntry")
injecte la valeur de l’entrée currencyEntry dans l’attribut currency ; si les types
de l’entrée et de l’attribut ne sont pas compatibles, le conteneur lève une exception.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
252 Java EE 6 et GlassFish 3
@Resource(name = "currencyEntry")
private String currency;
@Resource(name = "changeRateEntry")
private Float changeRate;
Appels asynchrones
Par défaut, les appels des beans de session via des vues distantes, locales et sans
interface sont synchrones : un client qui appelle une méthode reste bloqué pendant
la durée de cet appel. Un traitement asynchrone est donc souvent nécessaire lorsque
l’application doit exécuter une opération qui dure longtemps. L’impression d’une
commande, par exemple, peut prendre beaucoup de temps si des dizaines de docu-
ments sont déjà dans la file d’attente de l’imprimante, mais un client qui appelle une
méthode d’impression d’un document souhaite simplement déclencher un processus
qui imprimera ce document, puis continuer son traitement.
Avant EJB 3.1, les traitements asynchrones pouvaient être pris en charge par JMS
et les MDB (voir Chapitre 13). Il fallait créer des objets administrés (fabriques et
destinations JMS), utiliser l’API JMS de bas niveau afin d’envoyer un message à
un destinataire, puis développer un MDB pour consommer et traiter le message.
Cela fonctionnait mais se révélait assez lourd dans la plupart des cas car il fallait
mettre en œuvre un système MOM pour simplement appeler une méthode de façon
asynchrone.
Depuis EJB 3.1, on peut appeler de façon asynchrone une méthode de bean de ses-
sion en l’annotant simplement avec @javax.ejb.Asynchronous. Le Listing 7.17
montre le bean OrderEJB, qui dispose d’une méthode pour envoyer un e-mail à un
client et d’une autre pour imprimer la commande. Ces deux méthodes durant long-
temps, elles sont toutes les deux annotées par @Asynchronous.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 253
@Asynchronous
private void sendEmailOrderComplete(Order order,
Customer customer) {
// Envoie un mail
}
@Asynchronous
private void printOrder(Order order) {
// Imprime une commande
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
254 Java EE 6 et GlassFish 3
@Resource
SessionContext ctx;
if (ctx.wasCancelCalled()) {
return new AsyncResult<Integer>(2);
// Traitement
return new AsyncResult<Integer>(status);
}
}
Conteneurs intégrés
L’avantage des beans de session est qu’ils sont gérés par un conteneur : ce dernier
s’occupe de tous les services (transaction, cycle de vie, asynchronisme, intercep-
teurs, etc.), ce qui permet au développeur de se concentrer sur le code métier. L’in-
convénient est qu’il faut toujours exécuter les EJB dans un conteneur, même pour
les tester. Pour résoudre ce problème, on finit généralement par bricoler le code
métier afin de pouvoir le tester : on ajoute des interfaces distantes alors que l’EJB
n’a besoin que d’un accès local, on crée une façade TestEJB distante qui délègue les
appels aux véritables EJB ou l’on utilise des fonctionnalités spécifiques à un éditeur
– d’une façon ou d’une autre, on doit exécuter un conteneur avec les EJB déployés.
Ce problème a été résolu par EJB 3.1 avec la création d’un conteneur EJB inté-
grable. EJB 3.1 ajoute en effet une API standardisée pour exécuter les EJB dans un
environnement Java SE. Celle-ci (l’API javax.ejb.embeddable) permet à un client
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 255
@BeforeClass
public static void initContainer() throws Exception {
ec = EJBContainer.createEJBContainer();
ctx = ec.getContext();
}
@AfterClass
public static void closeContainer() throws Exception {
ec.close();
}
@Test
public void createBook() throws Exception {
// Recherche de l’EJB
ItemEJB bookEJB = (ItemEJB)
„ ctx.lookup("java:global/chapter07/ItemEJB");
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
256 Java EE 6 et GlassFish 3
Le service timer
Certaines applications Java EE ont besoin de planifier des tâches pour être averties
à des instants donnés. L’application CD-BookStore, par exemple, veut envoyer tous
les ans un e-mail d’anniversaire à ses clients, afficher les statistiques mensuelles des
ventes, produire des rapports toutes les nuits sur l’état du stock et rafraîchir un cache
toutes les 30 secondes.
Pour ce faire, EJB 2.1 a introduit un service timer car les clients ne pouvaient pas
utiliser directement l’API Thread, mais il était moins riche que d’autres outils ou cer-
tains frameworks (l’utilitaire cron d’Unix, Quartz, etc.). Il a fallu attendre EJB 3.1
pour voir apparaître une amélioration considérable de ce service, qui s’est inspiré de
cron et d’autres outils reconnus. Désormais, il peut répondre à la plupart des besoins
de planification.
Le service timer EJB est un service conteneur qui permet aux EJB de s’enregis-
trer pour être rappelés. Les notifications peuvent être planifiées pour intervenir à
une date ou une heure données, après un certain délai ou à intervalles réguliers.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 257
<<executionEnvironment>>
Conteneur EJB
Figure 7.7
Interaction entre le service timer et un bean de session.
Les timers sont destinés aux processus métiers longs et ils sont donc persistants par
défaut, ce qui signifie qu’ils survivent aux arrêts du serveur : lorsqu’il redémarre, les
timers s’exécutent comme s’il ne s’était rien passé. Selon vos besoins, vous pouvez
également demander des timers non persistants.
INFO
Le service de timer peut enregistrer des beans sans état et singletons ainsi que des MDB,
mais pas de beans avec état. Ces derniers ne doivent donc pas utiliser l’API de planification.
Expressions calendaires
Le service timer utilise une syntaxe calendaire inspirée de celle du programme cron
d’Unix. Cette syntaxe est utilisée pour la création des timers par programme (avec
la classe ScheduleExpression) ou pour les créations automatiques (via l’annotation
@Schedule ou le descripteur de déploiement). Le Tableau 7.2 énumère les attributs
de création des expressions calendaires.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
258 Java EE 6 et GlassFish 3
Valeur par
Attribut Description Valeurs possibles
défaut
timezone Une zone horaire particulière Liste des zones horaires fournies
par la base de données zoneinfo
(ou tz)
Chaque attribut d’une expression calendaire (second, minute, hour, etc.) permet
d’exprimer les valeurs sous différentes formes. Vous pouvez, par exemple, avoir une
liste de jours ou un intervalle entre deux années. Le Tableau 7.3 présente les diffé-
rentes formes que peut prendre un attribut.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 259
Cette syntaxe devrait sembler familière à ceux qui connaissent cron, mais elle est
bien plus simple. Comme le montre le Tableau 7.4, elle permet d’exprimer quasi-
ment n’importe quel type d’expression calendaire.
Exemple Expression
Tous les mercredis à minuit dayOfWeek="Wed"
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
260 Java EE 6 et GlassFish 3
Exemple Expression
Toutes les cinq minutes minute="0,5,10,15,20,25,30,35,40,45,
50,55", hour="*"
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 261
@Schedules({
@Schedule(hour = "2"),
@Schedule(hour = "14", dayOfWeek = "Wed")
})
public void generateReport() {
// ...
}
Pour créer un timer par programme, l’EJB doit accéder à l’interface javax.
ejb.TimerService en utilisant soit l’injection de dépendances, soit l’EJBContext
(EJBContext.getTimerService()), soit une recherche JNDI. L’API TimerService
définit plusieurs méthodes permettant de créer quatre sortes de timers :
■■ createTimer crée un timer reposant sur des dates, des intervalles ou des durées.
Ces méthodes n’utilisent pas les expressions calendaires.
■■ createSingleActionTimer crée un timer simple-action qui expire à un instant
donné ou après une certaine durée. Le conteneur supprime le timer après l’appel
à la méthode de rappel.
■■ createIntervalTimer crée un timer intervalle dont la première expiration inter-
vient à un instant donné et les suivantes, après les intervalles indiqués.
■■ createCalendarTimer crée un timer utilisant les expressions calendaires à l’aide
de la classe ScheduleExpression.
La classe ScheduleExpression permet de créer des expressions calendaires par
programme. Ses méthodes sont liées aux attributs du Tableau 7.2 et permettent
de programmer tous les exemples du Tableau 7.4. Voici quelques exemples :
new ScheduleExpression().dayOfMonth("Mon").month("Jan");
new ScheduleExpression().second("10,30,50").minute("*/5").
„ hour("10-14");
new ScheduleExpression().dayOfWeek("1,5").
„ timezone("Europe/Lisbon");
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
262 Java EE 6 et GlassFish 3
@Resource
TimerService timerService;
@PersistenceContext(unitName = "chapter07PU")
private EntityManager em;
@Timeout
public void sendBirthdayEmail(Timer timer) {
Customer customer = (Customer) timer.getInfo();
// ...
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 7 Beans de session et service timer 263
Une fois le timer créé, le conteneur invoquera tous les ans la méthode @Timeout
(sendBirthdayEmail()) en lui passant l’objet Timer. Le timer ayant été sérialisé
avec l’objet customer, la méthode peut y accéder en appelant getinfo().
Résumé
Ce chapitre a été consacré aux beans de session et au service timer (les MDB seront
présentés au Chapitre 13, les services web SOAP, au Chapitre 14 et les services
web REST, au Chapitre 15). Les beans de session sont des composants gérés par un
conteneur qui permettent de développer des couches métiers. Il existe trois types de
beans de session : sans état, avec état et singletons. Les beans sans état s’adaptent
facilement car ils ne mémorisent aucune information, sont placés dans un pool et
traitent les tâches qui peuvent être réalisées par un seul appel de méthode. Les beans
avec état sont en relation 1–1 avec un client et peuvent être temporairement ôtés
de la mémoire grâce aux mécanismes de passivation et d’activation. Les singletons
n’ont qu’une seule instance partagée par plusieurs clients et peuvent être initialisés
au lancement de l’application, chaînés ensemble ; en outre, leurs accès concurrents
peuvent s’adapter en fonction des besoins.
Malgré ces différences, tous les beans de session partagent le même modèle de
programmation. Ils peuvent avoir une vue locale, distante ou sans interface, utiliser
des annotations ou être déployés avec un descripteur de déploiement. Les beans de
session peuvent utiliser l’injection de dépendances pour obtenir des références à
plusieurs ressources (sources de données JDBC, contexte persistant, entrées d’envi-
ronnement, etc.) ; ils peuvent également se servir de leur contexte d’environnement
(l’objet SessionContext). Depuis EJB 3.1, vous pouvez appeler des méthodes de
façon asynchrone, rechercher les EJB à l’aide de noms JNDI portables ou utiliser un
conteneur EJB intégré dans l’environnement Java SE. EJB 3.1 a également amélioré
le service timer, qui peut désormais rivaliser avec les autres outils de planification.
Le chapitre suivant présente le cycle de vie des beans de session et explique com-
ment interagir avec les annotations de rappel. Les intercepteurs, qui permettent de
mettre en œuvre la programmation orientée aspect (POA) avec les beans de session,
y sont également détaillés.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
8
Méthodes de rappel
et intercepteurs
Le chapitre précédent a montré que les beans sont des composants gérés par un
conteneur. Ils résident dans un conteneur EJB qui encapsule le code métier avec
plusieurs services (injection de dépendances, gestion des transactions, de la sécu-
rité, etc.). La gestion du cycle de vie et l’interception font également partie de ces
services.
Le cycle de vie signifie qu’un bean de session passe par un ensemble d’états bien
précis, qui dépendent du type de bean (sans état, avec état ou singleton). À chaque
phase de ce cycle. le conteneur peut invoquer les méthodes qui ont été annotées
comme méthodes de rappel. Vous pouvez utiliser ces annotations pour initialiser les
ressources de vos beans de session ou pour les libérer avant leur destruction.
Les intercepteurs permettent d’ajouter des traitements transverses à vos beans.
Lorsqu’un client appelle une méthode d’un bean de session, le conteneur peut
intercepter l’appel et traiter la logique métier avant que la méthode du bean ne soit
invoquée.
Ce chapitre présente les différents cycles de vie des beans de session, ainsi que les
annotations de rappel que vous pouvez utiliser pour traiter la logique métier au cours
des différentes phases. Nous verrons également comment intercepter les appels de
méthodes et les encapsuler par notre propre code.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
266 Java EE 6 et GlassFish 3
Comme nous l’avons vu au chapitre précédent, un client ne crée pas une instance
d’un bean de session à l’aide de l’opérateur new : il obtient une référence à ce bean
via l’injection de dépendances ou par une recherche JNDI. C’est le conteneur qui
crée l’instance et qui la détruit, ce qui signifie que ni le client ni le bean ne sont res-
ponsables du moment où l’instance est créée, où les dépendances sont injectées et
où l’instance est supprimée. La responsabilité de la gestion du cycle de vie du bean
incombe au conteneur.
Tous les beans de session passent par deux phases évidentes de leur cycle de vie :
leur création et leur destruction. En outre, les beans avec état passent par les phases
de passivation et d’activation que nous avons décrites au chapitre précédent.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et intercepteurs 267
Prêt
4. Appel de méthode
Bien qu’ils partagent le même cycle de vie, les beans sans état et singletons ne sont
pas créés et détruits de la même façon.
Lorsqu’un bean de session sans état est déployé, le conteneur en crée plusieurs ins-
tances et les place dans un pool. Quand un client appelle une méthode de ce bean,
le conteneur choisit une instance dans le pool, lui délègue l’appel de méthode et la
replace dans le pool. Lorsque le conteneur n’a plus besoin de l’instance (parce que,
par exemple, il veut réduire le nombre d’instances du pool), il la supprime.
INFO
GlassFish permet de paramétrer le pool des EJB. Vous pouvez ainsi fixer une taille (nombre
initial, minimal et maximal de beans dans le pool), le nombre de beans à supprimer du pool
lorsque son temps d’inactivité a expiré et le nombre de millisecondes du délai d’expiration
du pool.
La création des beans singletons varie selon qu’ils ont été instanciés dès le démar-
rage (@Startup) ou non, ou qu’ils dépendent (@DependsOn) d’un autre singleton déjà
créé : dans ce cas, une instance sera créée au moment du déploiement ; sinon le
conteneur créera l’instance lorsqu’un client appellera une méthode métier. Comme
les singletons durent tout le temps de l’application, leur instance n’est détruite que
lorsque le conteneur se termine.
Du point de vue du programme, les beans de session avec état ne sont pas très
différents des beans sans état ou singletons : seules leurs métadonnées changent
(@Stateful au lieu de @Stateless ou @Singleton). La véritable différence réside
dans le fait que les beans avec état mémorisent l’état conversationnel avec leurs
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
268 Java EE 6 et GlassFish 3
clients et qu’ils ont donc un cycle de vie légèrement différent. Le conteneur produit
une instance et ne l’affecte qu’à un seul client. Ensuite, chaque requête de ce client
sera transmise à la même instance. Selon ce principe et en fonction de l’application,
il peut finalement s’établir une relation 1–1 entre un client et un bean avec état (un
millier de clients simultanés peuvent produire un millier de beans avec état). Si un
client n’invoque pas son instance de bean au cours d’une période suffisamment lon-
gue, le conteneur doit le supprimer avant que la JVM ne soit à court de mémoire,
préserver l’état de cette instance dans une zone de stockage permanente, puis la
rappeler lorsque son état redevient nécessaire. Pour ce faire, le conteneur utilise le
mécanisme de passivation et activation.
Comme on l’a expliqué au Chapitre 7, la passivation consiste à sérialiser l’instance
du bean sur un support de stockage permanent (fichier sur disque, base de données,
etc.) au lieu de la maintenir en mémoire. L’activation, qui est l’opération opposée, a
lieu lorsque l’instance est redemandée par le client. Le conteneur désérialise alors le
bean et le replace en mémoire. Ceci signifie donc que les attributs du bean doivent
être sérialisables (donc être d’un type Java primitif ou qui implémente l’interface
java.io.Serializable). Le cycle de vie d’un bean avec état passe donc par les
étapes suivantes, qui sont représentées par la Figure 8.2 :
1. Le cycle de vie démarre lorsqu’un client demande une référence au bean (soit
par injection de dépendances, soit par une recherche JNDI) : le conteneur crée
alors une nouvelle instance du bean de session et la stocke en mémoire.
2. Si la nouvelle instance utilise l’injection de dépendances via des annotations
(@Resource, @EJB, @PersistenceContext, etc.) ou des descripteurs de déploie-
ment, le conteneur injecte les ressources nécessaires.
3. Si l’instance contient une méthode annotée par @PostConstruct, le conteneur
l’appelle.
4. Le bean exécute l’appel demandé et reste en mémoire en attente d’autres
requêtes du client.
5. Si le client reste inactif pendant un certain temps, le conteneur appelle la
méthode annotée par @PrePassivate, s’il y en a une, et stocke le bean sur un
support de stockage permanent.
6. Si le client appelle un bean qui a été passivé, le conteneur le replace en mémoire
et appelle la méthode annotée par @PostActivate, s’il y en a une.
7. Si le client n’invoque pas une instance passivée avant la fin du délai d’expiration
de la session, le conteneur supprime cette instance.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et intercepteurs 269
8. Alternative à l’étape 7 : si le client appelle une méthode annotée par @Remove,
le conteneur invoque alors la méthode annotée par @PreDestroy, s’il y en a une,
et met fin au cycle de vie de l’instance.
Cycle de vie
d’un bean avec état. 1. Nouvelle instance
2. Injection de dépendances 8. @Remove et @Predestroy
3. @PostConstruct
6. PostActivate
Prêt Passivé
5. @PrePassivate
4. Appel de méthode
Dans certains cas, un bean avec état contient des ressources ouvertes comme
des sockets ou des connexions de bases de données. Un conteneur ne pouvant
garder ces ressources ouvertes pour chaque bean, vous devez fermer et rouvrir
ces ressources avant et après la passivation : c’est là que les méthodes de rappel
interviennent.
Méthodes de rappel
Comme nous venons de le voir, le cycle de vie de chaque bean de session est géré par
son conteneur. Ce dernier permet de greffer du code métier aux différentes phases de
ce cycle : les passages d’un état à l’autre sont alors interceptés par le conteneur, qui
appellera les méthodes annotées par l’une des annotations du Tableau 8.1.
Annotation Description
@PostConstruct Indique la méthode à appeler immédiatement après la création de
l’instance et l’injection de dépendances par le conteneur. Cette méthode
sert le plus souvent à réaliser les initialisations.
@PreDestroy Indique la méthode à appeler immédiatement avant la suppression de
l’instance par le conteneur. Cette méthode sert le plus souvent à libérer
les ressources utilisées par le bean. Dans le cas des beans avec état, cette
méthode est appelée après la fin de l’exécution de la méthode annotée
par @Remove.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
270 Java EE 6 et GlassFish 3
Annotation Description
@PrePassivate Indique la méthode à appeler avant que le conteneur passive
l’instance. Elle donne généralement l’occasion au bean de se préparer
à la sérialisation et de libérer les ressources qui ne peuvent pas être
sérialisées (connexions à une base de données, serveur de message,
socket réseau, etc.).
@PostActivate Indique la méthode à appeler immédiatement après la réactivation de
l’instance par le conteneur. Elle lui permet de réinitialiser les ressources
qu’il a fermées au cours de la passivation.
INFO
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et intercepteurs 271
Ces méthodes de rappel servent généralement à allouer et/ou à libérer les ressources
du bean. Le Listing 8.1, par exemple, montre que le bean singleton CacheEJB utilise
une annotation @PostConstruct pour initialiser son cache : immédiatement après la
création de l’unique instance de ce bean, le conteneur invoquera donc la méthode
initCache().
@Singleton
public class CacheEJB {
@PostConstruct
private void initCache() {
// Initialise le cache
}
Le Listing 8.2 présente un extrait de code pour un bean avec état. Le conteneur
gère l’état conversationnel, qui peut contenir des ressources importantes comme une
connexion à une base de données. L’ouverture d’une telle connexion étant coûteuse,
elle devrait être partagée par tous les appels, mais libérée lorsque le bean est inactif
(ou passivé).
Après la création de l’instance du bean, le conteneur injecte la référence d’une
source de données dans l’attribut ds. Il pourra ensuite appeler la méthode annotée
par @PostConstruct (init()), qui crée une connexion vers une base de données. Si
le conteneur passive l’instance, la méthode close() (annotée par @PrePassivate)
sera d’abord invoquée afin de fermer la connexion JDBC, qui ne sert plus pendant
la passivation. Lorsque le client appelle une méthode métier du bean, le conteneur
l’active et appelle à nouveau la méthode init() (car elle est également annotée par
@PostActivate). Si le client invoque la méthode checkout() (annotée par @Remove),
le conteneur supprime l’instance après avoir appelé à nouveau la méthode close()
(car elle est aussi annotée par @PreDestroy).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
272 Java EE 6 et GlassFish 3
@PostConstruct
@PostActivate
private void init() {
connection = ds.getConnection();
}
@PreDestroy
@PrePassivate
private void close() {
connection.close();
}
// ...
@Remove
public void checkout() {
cartItems.clear();
}
}
Pour plus de lisibilité, la gestion des exceptions SQL a été omise dans les méthodes
de rappel.
Intercepteurs
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et intercepteurs 273
INFO
Les intercepteurs s’appliquent aux beans de session et aux beans pilotés par messages (MDB).
Aux Chapitres 14 et 15, nous verrons qu’un service web SOAP ou REST peut également être
implémenté comme un EJB (en ajoutant l’annotation @Stateless). En ce cas, ces services
web peuvent également utiliser des intercepteurs.
La Figure 8.3 montre que plusieurs intercepteurs sont appelés entre le client et
l’EJB. En fait, vous pouvez considérer qu’un conteneur EJB est lui-même une
chaîne d’intercepteurs : lorsque vous développez un bean de session, vous vous
concentrez sur le code métier mais, en coulisse, le conteneur intercepte les appels
de méthodes effectués par le client et applique différents services (gestion du cycle
de vie, transactions, sécurité, etc.). Grâce aux intercepteurs, vous pouvez ajou-
ter vos propres traitements transverses et les appliquer au code métier de façon
transparente.
<<executionEnvironment>>
Conteneur EJB
Figure 8.3
Conteneur interceptant un appel et invoquant un intercepteur.
Il existe trois types d’intercepteurs (que nous décrirons dans la section suivante) :
■■ intercepteurs autour des appels ;
■■ intercepteurs des méthodes métiers ;
■■ intercepteurs des méthodes de rappel du cycle de vie.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
274 Java EE 6 et GlassFish 3
INFO
La spécification EJB 3.1 (JSR 318) est composée de deux documents : la spécification EJB
centrale et le document énonçant les besoins des intercepteurs. Ce dernier définit le fonc-
tionnement des intercepteurs et la façon de les utiliser. La raison de cette séparation est liée
au fait que, dans de prochaines versions, les intercepteurs pourraient être traités indépen-
damment des EJB.
@PersistenceContext(unitName = "chapter08PU")
private EntityManager em;
private Logger logger = Logger.getLogger("com.apress.javaee6");
@AroundInvoke
private Object logMethod(InvocationContext ic)
throws Exception {
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et intercepteurs 275
logger.entering(ic.getTarget().toString(),
ic.getMethod().getName());
try {
return ic.proceed();
} finally {
logger.exiting(ic.getTarget().toString(),
ic.getMethod().getName());
}
}
Bien qu’elle soit annotée par @AroundInvoke, la méthode logMethod() doit avoir
une signature bien précise :
@AroundInvoke
Object <nom-méthode>(InvocationContext ic) throws Exception;
Une méthode intercepteur autour des appels doit respecter les règles suivantes :
■■ Elle peut être public, private, protected ou avoir un accès paquetage, mais ne
peut pas être static ou final.
■■ Elle doit avoir un paramètre javax.interceptor.InvocationContext et renvoyer
un Object, qui est le résultat de l’appel de la méthode cible (si cette méthode
renvoie void, cet objet vaudra null).
■■ Elle peut lever une exception contrôlée.
L’objet InvocationContext permet aux intercepteurs de contrôler le comporte-
ment de la chaîne des appels. Lorsque plusieurs intercepteurs sont chaînés, c’est
la même instance d’InvocationContext qui est passée à chacun d’eux, ce qui peut
impliquer un traitement de ces données contextuelles par les autres intercepteurs.
Le Tableau 8.2 décrit l’API d’InvocationContext.
Méthode Description
getContextData Permet de passer des valeurs entre les mêmes méthodes intercepteurs
dans la même instance d’InvocationContext à l’aide d’une Map.
getMethod Renvoie la méthode du bean pour laquelle l’intercepteur a été
invoqué.
getParameters Renvoie les paramètres qui seront utilisés pour invoquer la méthode
métier.
getTarget Renvoie l’instance du bean à laquelle appartient la méthode
interceptée.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
276 Java EE 6 et GlassFish 3
Méthode Description
getTimer Renvoie le timer associé à une méthode @Timeout.
proceed Appelle la méthode intercepteur suivante de la chaîne. Renvoie le
résultat de la méthode suivante. Si une méthode est de type void,
proceed renvoie null.
4 : proceed
5 : createCustomer
6 : exiting
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et intercepteurs 277
Intercepteurs de méthode
Le Listing 8.3 définit un intercepteur qui n’est disponible que pour CustomerEJB
mais, la plupart du temps, on souhaite isoler un traitement transverse dans une classe
distincte et demander au conteneur d’intercepter les appels de méthodes de plusieurs
beans de session. L’enregistrement de journaux est un exemple typique de situation
dans laquelle on veut enregistrer les entrées et les sorties de toutes les méthodes de
tous les EJB. Pour disposer de ce type d’intercepteur, on doit créer une classe dis-
tincte et informer le conteneur de l’appliquer à un bean précis ou à une méthode de
bean particulière.
Le Listing 8.4 isole la méthode logMethod() du Listing 8.3 dans une classe à part,
LoggingInterceptor, qui est un simple POJO disposant d’une méthode annotée par
@AroundInvoke.
@AroundInvoke
public Object logMethod(InvocationContext ic) throws Exception {
logger.entering(ic.getTarget().toString(),
ic.getMethod().getName());
try {
return ic.proceed();
} finally {
logger.exiting(ic.getTarget().toString(),
ic.getMethod().getName());
}
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
278 Java EE 6 et GlassFish 3
@Interceptors(LoggingInterceptor.class)
public void createCustomer(Customer customer) {
em.persist(customer);
}
Si vous voulez que toutes les méthodes, sauf une, soient interceptées, utilisez l’an-
notation javax.interceptor.ExcludeClassInterceptors pour exclure la méthode
concernée. Dans le code suivant, l’appel à updateCustomer() ne sera pas intercepté
alors que les appels à toutes les autres méthodes le seront :
@Stateless
@Interceptors(LoggingInterceptor.class)
public class CustomerEJB {
public void createCustomer(Customer customer) { ... }
public Customer findCustomerById(Long id) { ... }
public void removeCustomer(Customer customer) { ... }
@ExcludeClassInterceptors
public Customer updateCustomer(Customer customer) { ... }
}
Dans la première partie de ce chapitre, nous avons vu comment gérer les méthodes
de rappel dans un EJB. Avec une annotation de rappel, vous pouvez demander au
conteneur d’appeler une méthode lors d’une phase précise du cycle de vie (@Post-
Construct, @PrePassivate, @PostActivate et @PreDestroy). Si vous souhaitez, par
exemple, ajouter une entrée dans un journal à chaque fois qu’une instance d’un bean
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et intercepteurs 279
est créée, il suffit de placer l’annotation @PostConstruct sur une méthode du bean
et d’ajouter un peu de code pour enregistrer l’entrée dans le journal. Mais comment
faire pour capturer les événements du cycle de vie entre plusieurs types de beans ?
Les intercepteurs du cycle de vie permettent d’isoler du code dans une classe et de
l’invoquer lorsque l’un de ces événements se déclenche.
Les intercepteurs du cycle de vie ressemblent à ce que nous venons de voir dans
le Listing 8.4, sauf que les méthodes utilisent des annotations de rappel au lieu de
@AroundInvoke. Le Listing 8.6 présente une classe ProfileInterceptor avec deux
méthodes : logMethod(), qui sera appelée après la construction d’une instance et
profile(), qui sera invoquée avant la destruction d’une instance.
@PostConstruct
public void logMethod(InvocationContext ic) {
logger.entering(ic.getTarget().toString(),
ic.getMethod().getName());
try {
return ic.proceed(); }
finally {
logger.exiting(ic.getTarget().toString(),
ic.getMethod().getName());
}
}
@PreDestroy
public void profile(InvocationContext ic) {
long initTime = System.currentTimeMillis();
try {
return ic.proceed();
} finally {
long diffTime = System.currentTimeMillis() - initTime;
logger.fine(ic.getMethod() + " took " + diffTime +
" millis");
}
}
}
Comme vous pouvez le voir dans le Listing 8.6, les méthodes intercepteurs du cycle
de vie prennent en paramètre un objet InvocationContext, renvoient void au lieu
d’Object (car, comme on l’a expliqué dans la section "Méthodes de rappel", les
méthodes de rappel du cycle de vie renvoient void) et ne peuvent pas lancer d’excep-
tions contrôlées.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
280 Java EE 6 et GlassFish 3
@PersistenceContext(unitName = "chapter08PU")
private EntityManager em;
@PostConstruct
public void init() {
// ...
}
Les méthodes de rappel du cycle de vie et les méthodes @AroundInvoke peuvent être
définies dans la même classe intercepteur.
Nous venons de voir comment intercepter les appels dans un seul bean (avec
@AroundInvoke) et entre plusieurs beans (avec @Interceptors). EJB 3.1 permet
également de chaîner plusieurs intercepteurs et de définir des intercepteurs par
défaut qui s’appliqueront à tous les beans de session.
En fait, il est possible d’attacher plusieurs intercepteurs avec l’annotation @Inter-
ceptors en lui passant en paramètre une liste d’intercepteurs séparés par des vir-
gules. En ce cas, l’ordre dans lequel ils seront invoqués est déterminé par leur ordre
d’apparition dans cette liste. Le code du Listing 8.8, par exemple, utilise @Intercep-
tors à la fois au niveau du bean et au niveau des méthodes.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 8 Méthodes de rappel et intercepteurs 281
Le caractère joker * dans l’élément <ejb-name> signifie que tous les EJB applique-
ront l’intercepteur défini dans l’élément <interceptor-class>. Si vous déployez le
bean CustomerEJB du Listing 8.7 avec cet intercepteur par défaut, le ProfileInter-
ceptor sera invoqué avant tous les autres intercepteurs.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
282 Java EE 6 et GlassFish 3
<<executionEnvironment>>
Conteneur EJB
Figure 8.5
Chaînage des différents types d’intercepteurs.
Résumé
Dans ce chapitre, nous avons vu que les beans de session sans état et singletons par-
tagent le même cycle de vie et que celui des beans avec état est légèrement différent.
En effet, ces derniers mémorisent l’état conversationnel avec leur client et doivent
temporairement sérialiser cet état sur un support de stockage permanent (passiva-
tion). Nous avons également vu que les annotations de rappel permettent d’ajouter
de la logique métier aux beans, qui s’exécutera avant ou après la survenue d’un
événement (@PostConstruct, @PreDestroy, etc.).
Les intercepteurs sont des mécanismes permettant de mettre en œuvre la POA avec les
EJB car ils permettent au conteneur d’invoquer des traitements transverses sur l’appli-
cation. Ils sont simples à utiliser, puissants et peuvent être chaînés pour appliquer plu-
sieurs traitements à la suite. Il est également possible de définir des intercepteurs par
défaut qui s’appliqueront à toutes les méthodes de tous les beans d’une application.
Un conteneur EJB peut lui-même être considéré comme une chaîne d’intercepteurs :
les appels de méthodes sont interceptés par le conteneur, qui applique alors plusieurs
services comme la gestion des transactions et de la sécurité. Le chapitre suivant est
consacré à ces deux services.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
9
Transactions et sécurité
Transactions
Les données sont cruciales et elles doivent être correctes, quelles que soient les
opérations effectuées et le nombre d’applications qui y accèdent. Une transaction
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
284 Java EE 6 et GlassFish 3
sert à garantir que les données resteront dans un état cohérent. Elle représente un
groupe logique d’opérations qui doivent être exécutées de façon atomique – elle
forme donc ce que l’on appelle une unité de traitement. Les opérations qui la consti-
tuent peuvent impliquer le stockage de données dans une ou plusieurs bases, l’envoi
de messages ou l’appel de services web. Les sociétés utilisent quotidiennement les
transactions pour les applications bancaires ou de commerce en ligne, ainsi que pour
les interactions B2B (business-to-business) avec leurs partenaires.
Ces opérations métiers indivisibles s’exécutent en séquence ou en parallèle pendant
une durée relativement courte. Pour qu’une transaction réussisse, toutes ses opéra-
tions doivent réussir (on dit alors que la transaction est validée – committed). Il suf-
fit que l’une des opérations échoue pour que la transaction échoue également (la
transaction est annulée – rolled back). Les transactions doivent garantir un certain
niveau de fiabilité et de robustesse et respecter les propriétés ACID.
ACID
ACID est un acronyme des quatre propriétés qui définissent une transaction fiable :
atomicité, cohérence, isolement et durée (voir Tableau 9.1). Pour expliquer chacune
d’elles, prenons l’exemple classique d’un transfert bancaire dans lequel on débite un
compte épargne pour créditer un compte courant.
Propriété Description
Atomicité Une transaction est composée d’une ou de plusieurs opérations regroupées dans
une unité de traitement. À la fin de la transaction, soit toutes les opérations se
sont déroulées correctement (transaction validée – commit), soit il s’est passé un
problème inattendu, auquel cas aucune ne sera réalisée (transaction annulée –
rollback).
Cohérence À la fin d’une transaction, les données sont dans un état cohérent.
Isolement L’état intermédiaire d’une transaction n’est pas visible aux applications externes.
Durée Lorsqu’une transaction est validée, les modifications apportées aux données sont
visibles aux autres applications.
On peut imaginer que le transfert d’un compte vers un autre représente une suite
d’accès à la base de données : le compte épargne est débité à l’aide d’une instruction
update de SQL, le compte courant est crédité par une autre instruction update et un
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 285
enregistrement est ajouté dans une autre table afin de garder la trace de ce transfert.
Ces opérations doivent s’effectuer dans la même unité de traitement (atomicité) car
il ne faut pas que le débit ait lieu et qu’il n’y ait pas de crédit correspondant. Du
point de vue d’une application externe interrogeant les comptes, les deux opérations
ne seront visibles que lorsqu’elles se seront toutes les deux correctement réalisées
(isolement). Lorsque la transaction est validée ou annulée, la cohérence des données
est assurée par les contraintes d’intégrité de la base de données (clés primaires,
relations ou champs). Lorsque le transfert est terminé, il est possible d’accéder aux
données par les autres applications (durée).
Transactions locales
Pour que les transactions fonctionnent et respectent les propriétés ACID, plusieurs
composants doivent être mis en place. Commençons par l’exemple le plus simple qui
soit d’une application effectuant plusieurs modifications sur une ressource unique
(une base de données, par exemple). Lorsqu’une seule ressource transactionnelle est
nécessaire, il suffit d’utiliser une transaction locale – on peut utiliser des transac-
tions distribuées à la JTA, mais ce n’est pas strictement nécessaire. La Figure 9.1
représente l’interaction entre une application et une ressource via un gestionnaire de
transactions et un gestionnaire de ressources.
Figure 9.1
Application
Transaction n’impliquant
qu’une seule ressource. JTA
Gestionnaire
de transactions
JTA
Gestionnaire
de ressources
Ressource
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
286 Java EE 6 et GlassFish 3
Interface Description
UserTransaction Définit les méthodes qu’une application peut utiliser pour contrôler
par programme les frontières de transactions. Les EJB BMT (bean-
managed transaction) s’en servent pour lancer, valider ou annuler une
transaction (voir la section "Transactions gérées par les beans").
TransactionManager Permet au conteneur EJB de délimiter les frontières de transaction du
côté EJB.
Transaction Permet d’effectuer des opérations sur la transaction dans un objet
Transaction.
XA et transactions distribuées
Comme nous venons de le voir, une transaction qui n’utilise qu’une seule ressource
(comme à la Figure 9.1) est une transaction locale. Cependant, de nombreuses appli-
cations d’entreprise utilisent plusieurs ressources : si l’on revient à l’exemple du
transfert de fonds, le compte épargne et le compte courant pourraient se trouver dans
deux bases de données distinctes. Il faut alors gérer les transactions entre ces diffé-
rentes ressources ou entre des ressources distribuées sur le réseau. Ces transactions
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 287
Figure 9.2 Application
Transaction XA impliquant
JTA
deux ressources.
Gestionnaire
de transactions
Ressource JTA / XA
Gestionnaire Gestionnaire
de ressources de ressources
Ressource Ressource
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
288 Java EE 6 et GlassFish 3
Gestionnaire Gestionnaire
de transactions de transactions
prêt validée
Gestionnaire Gestionnaire
de ressources de ressources
prépare valide
La plupart du temps, les ressources sont distribuées sur le réseau (voir Figure 9.4).
Un tel système utilise JTS, qui implémente la spécification OTS (Object Transaction
Service) de l’OMG (Object Management Group) permettant aux gestionnaires de
transactions de participer aux transactions distribuées via IIOP (Internet Inter-ORB
Protocol). JTS est conçu pour les éditeurs qui fournissent les infrastructures de sys-
tèmes de transaction. Les développeurs EJB n’ont pas à s’en soucier : il suffit qu’ils
utilisent JTA, qui s’interface avec JTS à un niveau supérieur.
Figure 9.4 Application
Une transaction
JTA
distribuée XA.
Gestionnaire JTS / OTS Gestionnaire
de transactions de transactions
JTA / XA JTA / XA
Gestionnaire Gestionnaire
de ressources de ressources
Ressource Ressource
Lorsque l’on développe de la logique métier avec les EJB, il n’est pas nécessaire de
se soucier de la structure interne des gestionnaires de transactions ou de ressources
car JTA abstrait la plus grosse partie de la complexité sous-jacente. Grâce aux EJB,
le développement d’une application transactionnelle est donc très simple car c’est le
conteneur qui implémente les protocoles de bas niveau pour les transactions, comme
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 289
@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@EJB
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
290 Java EE 6 et GlassFish 3
Vous pourriez vous demander ce qui rend le code du Listing 9.1 transactionnel :
la réponse est le conteneur. La Figure 9.5 montre ce qui se passe quand un client
invoque la méthode createBook() : son appel est intercepté par le conteneur, qui
vérifie immédiatement avant l’appel de cette méthode si un contexte de transaction
est associé à cet appel. Dans la négative, le conteneur ouvre par défaut une nou-
velle transaction avant d’entrer dans la méthode, puis invoque celle-ci. À la fin de la
méthode, le conteneur valide automatiquement la transaction (ou l’annule automa-
tiquement si une exception particulière est lancée, comme nous le verrons dans la
section "Traitement des exceptions").
1 : createBook
2 : begin
3 : createBook
4 : additem
5 : validation
ou annulation
Figure 9.5
Le conteneur gère la transaction.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 291
Attribut Description
REQUIRED Cet attribut, qui est celui par défaut, signifie qu’une méthode doit
toujours être invoquée dans une transaction. Le conteneur en crée une
nouvelle si la méthode a été appelée par un client non transactionnel.
Si le client dispose d’un contexte de transaction, la méthode métier
s’exécute dans celui-ci. On utilise REQUIRED lorsque l’on modifie des
données et que l’on ne sait pas si le client a lancé ou non une transaction.
REQUIRES_NEW Le conteneur crée toujours une nouvelle transaction avant d’exécuter
une méthode, que le client s’exécute ou non dans une transaction. Si le
client est dans une transaction, le conteneur la suspend temporairement,
en crée une seconde, la valide, puis revient à la première. Ceci signifie
que le succès ou l’échec de la seconde transaction n’a pas d’effet sur la
transaction existante du client. On utilise REQUIRED_NEW lorsque l’on ne
souhaite pas qu’une annulation de la transaction ait un effet sur le client.
SUPPORTS La méthode de l’EJB hérite du contexte de transaction du client. Si ce
contexte est disponible, il est utilisé par la méthode ; sinon le conteneur
invoque la méthode sans contexte de transaction. On utilise SUPPORTS
lorsque l’on a un accès en lecture seule à la table de la base de données.
MANDATORY Le conteneur exige une transaction avant d’appeler la méthode
métier, mais n’en créera pas de nouvelle. Si le client a un contexte
de transaction, celui-ci est propagé ; sinon une exception javax.
ejb.exception" EJBTransactionRequiredException est levée.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
292 Java EE 6 et GlassFish 3
@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@EJB
private InventoryEJB inventory;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 293
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Book createBook(Book book) {
em.persist(book);
inventory.addItem(book);
return book;
}
}
INFO
Le contexte de transaction du client ne se propage pas lors des appels de méthodes asyn-
chrones. En outre, comme nous le verrons au Chapitre 13, les MDB n’autorisent que les
attributs REQUIRED et NOT_SUPPORTED.
@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@Resource
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
294 Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 295
Avec cette définition, nous savons maintenant que le conteneur annulera la transaction
s’il détecte une exception système comme ArithmeticException, ClassCastEx-
ception, IllegalArgumentException ou NullPointerException. Les exceptions
d’application dépendent en revanche de nombreux facteurs. À titre d’exemple, le
Listing 9.4 modifie le code du Listing 9.3 et utilise une exception d’application.
@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
if (inventoryLevel(item) == 0)
throw new InventoryLevelTooLowException();
}
}
public InventoryLevelTooLowException() { }
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
296 Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 297
Pour désactiver la démarcation CMT par défaut et basculer dans le mode BMT, un
bean doit simplement utiliser l’annotation @javax.ejb.TransactionManagement (ou
son équivalent XML dans le fichier ejb-jar.xml) :
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class ItemEJB {
...
}
Interface Description
begin Débute une nouvelle transaction et l’associe au thread
courant
commit Valide la transaction attachée au thread courant
rollback Annule la transaction attachée au thread courant
setRollbackOnly Marque la transaction courante pour annulation
getStatus Récupère le statut de la transaction courante
setTransactionTimeout Modifie le délai d’expiration de la transaction courante
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
298 Java EE 6 et GlassFish 3
@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@Resource
private UserTransaction ut;
em.merge(item);
item.decreaseAvailableStock();
sendShippingMessage();
if (inventoryLevel(item) == 0)
ut.rollback();
else
ut.commit();
} catch (Exception e) {
ut.setRollbackOnly();
}
sendInventoryAlert();
}
}
Dans le code CMT du Listing 9.3, c’est le conteneur qui débutait la transaction avant
l’exécution de la méthode et la validait immédiatement après. Avec le code BMT du
Listing 9.6, c’est vous qui définissez manuellement les frontières de la transaction
dans la méthode elle-même.
Sécurité
La sécurisation des applications est (ou devrait être) un souci majeur pour les socié-
tés. Ceci peut aller de la sécurisation d’un réseau au chiffrement des transferts de
données, en passant par l’octroi de certaines permissions aux utilisateurs d’un sys-
tème. Au cours de notre navigation quotidienne sur Internet, nous rencontrons de
nombreux sites où nous devons entrer un nom d’utilisateur et un mot de passe pour
avoir accès à certaines parties d’une application. La sécurité est devenue une néces-
sité sur le Web et, en conséquence, Java EE a défini plusieurs mécanismes pour
sécuriser les applications.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 299
Principal et rôle
Les "principaux" et les rôles tiennent une place importante dans la sécurité logi-
cielle. Un principal est un utilisateur qui a été authentifié (par un nom et un mot de
passe stockés dans une base de données, par exemple). Les principaux peuvent être
organisés en groupes, appelés rôles, qui leur permettent de partager un ensemble de
permissions (accès au système de facturation ou possibilité d’envoyer des messages
dans un workflow, par exemple).
La Figure 9.7 montre comment les utilisateurs peuvent être représentés dans un
système sécurisé. Comme vous pouvez le constater, un utilisateur authentifié est
lié à un principal qui a un identifiant unique et qui peut être associé à plusieurs
rôles. Le principal de l’utilisateur Frank, par exemple, est lié aux rôles Employé et
Admin.
Figure 9.7
Principaux et rôles.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
300 Java EE 6 et GlassFish 3
Authentification et habilitation
JAAS
Authentifie
Système
d'authentification
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 301
Le but principal du modèle de sécurité EJB est de contrôler l’accès au code métier.
Comme nous venons de le voir, l’authentification est prise en charge par la couche
web (ou une application cliente) ; le principal et ses rôles sont ensuite transmis à la
couche EJB et le service sécurité du conteneur EJB vérifie si le rôle d’un utilisateur
authentifié l’autorise à accéder à une méthode. Comme la gestion des transactions,
celle des habilitations peut s’effectuer de façon déclarative ou par programme.
Dans le cas des habilitations déclaratives, le contrôle des accès est assuré par le
conteneur EJB, tandis qu’avec les habilitations par programme c’est le code qui s’en
charge en utilisant l’API JAAS.
Sécurité déclarative
La politique de sécurité déclarative peut être définie dans le bean à l’aide d’annota-
tions ou dans le descripteur de déploiement XML. Elle consiste à déclarer les rôles,
à affecter des permissions aux méthodes (ou à tout le bean) ou à modifier temporai-
rement une identité de sécurité. Tous ces contrôles s’effectuent par les annotations
du Tableau 9.6, chacune d’elles pouvant porter sur une méthode et/ou sur le bean
entier.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
302 Java EE 6 et GlassFish 3
INFO
@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@RolesAllowed("admin")
public void deleteBook(Book book) {
em.remove(em.merge(book));
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 303
Les annotations @PermitAll et @DenyAll s’appliquent à tous les rôles : vous pouvez
donc utiliser @PermitAll pour annoter un EJB ou une méthode particulière pour
qu’ils puissent être invoqués par n’importe quel rôle. Inversement, @DenyAll interdit
l’accès à une méthode à tous les rôles.
Comme vous pouvez le constater dans le Listing 9.8, la méthode findBookById() est
désormais accessible à n’importe quel rôle, pas simplement à utilisateur, employé
ou admin. Par contre, la méthode findConfidentialBook() n’est pas accessible du
tout.
@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@PermitAll
public Book findBookById(Long id) {
return em.find(Book.class, id);
}
@RolesAllowed("admin")
public void deleteBook(Book book) {
em.remove(em.merge(book));
}
@DenyAll
public Book findConfidentialBook(Long secureId){
return em.find(ConfidentialBook.class, id);
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
304 Java EE 6 et GlassFish 3
Lorsqu’elles sont utilisées toutes les deux, c’est l’ensemble des rôles de @Declare-
Roles et @RolesAllowed qui est déclaré. Ceci dit, les rôles étant généralement décla-
rés pour l’ensemble d’une application d’entreprise, il est plus judicieux de le faire
dans le descripteur de déploiement qu’avec une annotation @DeclareRoles.
Lorsque le bean ItemEJB du Listing 9.9 est déployé, les cinq rôles HR, deptVentes,
utilisateur, employé et admin sont déclarés. Puis, avec l’annotation @Roles
Allowed, certains d’entre eux permettent d’accéder à certaines méthodes.
@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@RolesAllowed("admin")
public void deleteBook(Book book) {
em.remove(em.merge(book));
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 305
@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@EJB
private InventoryEJB inventory;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
306 Java EE 6 et GlassFish 3
@PersistenceContext(unitName = "chapter09PU")
private EntityManager em;
@Resource
private SessionContext ctx;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 9 Transactions et sécurité 307
Résumé
Dans ce dernier chapitre consacré aux EJB, nous avons vu comment gérer les tran-
sactions et la sécurité. Ces deux services très importants peuvent être définis de
façon déclarative ou par programmation.
Les transactions permettent à la couche métier de maintenir les données dans un
état cohérent, même lorsque plusieurs applications y accèdent de façon concurrente.
Elles respectent les propriétés ACID et peuvent être distribuées entre plusieurs res-
sources (bases de données, destinations JMS, services web, etc.). CMT permet de
personnaliser aisément la démarcation des transactions effectuée par le conteneur
EJB et vous pouvez influencer son comportement en marquant une transaction pour
annulation en vous servant du contexte EJB ou des exceptions. Il est également
possible d’utiliser BMT et JTA si vous avez besoin d’un contrôle plus fin sur la
démarcation des transactions.
Concernant la sécurité, n’oubliez pas que la couche métier n’authentifie pas les uti-
lisateurs : elle autorise des rôles à accéder aux méthodes. La sécurité déclarative
s’effectue au moyen d’un nombre relativement réduit d’annotations et permet de
traiter la plupart des situations auxquelles sera confrontée une application d’entre-
prise. Là aussi, vous pouvez utiliser une sécurité par programmation et manipuler
directement l’API JAAS.
Les trois chapitres qui suivent expliquent comment développer une couche présen-
tation avec JSF. Les pages JSF utilisent des beans gérés pour invoquer les méthodes
métiers des EJB.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
10
JavaServer Faces
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
310 Java EE 6 et GlassFish 3
Introduction à JSF
Lorsque l’on connaît déjà des frameworks web, l’architecture de JSF est facile à
comprendre (voir Figure 10.1). Les applications JSF sont des applications web clas-
siques qui interceptent HTTP via la servlet Faces et produisent du HTML. En cou-
lisse, cette architecture permet de greffer n’importe quel langage de déclaration de
page (PDL), de l’afficher sur des dispositifs différents (navigateur web, terminaux
mobiles, etc.) et de créer des pages au moyen d’événements, d’écouteurs et de com-
posants, comme en Swing. Ce dernier est un toolkit graphique intégré à Java depuis
sa version 1.6 – c’est un framework permettant de créer des applications de bureau
(pas des applications web) à l’aide de composants graphiques (widgets) et en utili-
sant le modèle événement-écouteur pour traiter les entrées des utilisateurs. JSF four-
nit également un ensemble de widgets standard (boutons, liens hypertextes, cases à
cocher, zones de saisie, etc.) et facilite son extension par l’ajout de composants tiers.
La Figure 10.1 représente son architecture au niveau le plus abstrait.
Figure 10.1 XUL
Requête JSP
Architecture de JSF.
Moteur de rendu
HTTP XHTML
(Ajax) Composant Convertisseur
Navigation
Bean géré
faces-config.xml
(facultatif)
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 311
Cette figure représente les parties importantes de JSF qui rendent cette architecture
aussi riche et aussi souple :
■■ FacesServlet et faces-config.xml. FacesServlet est la servlet principale de
l’application et peut éventuellement être configurée par un fichier descripteur
faces-config.xml.
■■ Pages et composants. JSF permet d’utiliser plusieurs PDL (Presentation
Description Language), comme JSP ou Facelets.
■■ Moteurs de rendu. Ils sont responsables de l’affichage d’un composant et de la
traduction de la valeur saisie par l’utilisateur en valeur pour le composant.
■■ Convertisseurs. Ils effectuent les conversions entre les valeurs de composants
(Date, Boolean, etc.) et les valeurs de marqueurs (String), et réciproquement.
■■ Validateurs. Ils garantissent que la valeur saisie par l’utilisateur est correcte.
■■ Bean géré et navigation. La logique métier s’effectue dans des beans gérés (ou
"managés") qui contrôlent également la navigation entre les pages.
■■ Support d’Ajax. Comme l’explique le Chapitre 12, JSF 2.0 reconnaît nativement
Ajax.
FacesServlet et faces-config.xml
Pages et composants
Le framework JSF doit envoyer une page sur le dispositif de sortie du client (un
navigateur, par exemple) et exige donc une technologie d’affichage appelée PDL.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
312 Java EE 6 et GlassFish 3
Une application JSF peut utiliser plusieurs technologies pour son PDL, comme JSP
ou Facelets. Une implémentation conforme à la spécification JSF 2.0 doit inclure
une implémentation complète de JSP, qui était le PDL par défaut de JSF 1.1 et
JSF 1.2 – JSF 2.0 lui préfère désormais Facelets.
JSP et Facelets sont tous les deux formés d’une arborescence de composants (égale-
ment appelés widgets ou contrôles) fournissant des fonctionnalités spécifiques pour
interagir avec l’utilisateur (champs de saisie, boutons, listes, etc.). JSF dispose d’un
ensemble standard de composants et permet de créer facilement les vôtres. Pour
gérer cette arborescence, une page passe par un cycle de vie complexe (initiali
sation, événements, affichage, etc.).
Le code du Listing 10.1 est une page Facelets en XHTML qui utilise les mar-
queurs JSF (xmlns:h="http:// java.sun.com/jsf/html") pour afficher un formu-
laire avec deux champs de saisie (l’ISBN et le titre d’un livre) et un bouton. Cette
page est composée de plusieurs composants JSF : certains n’ont pas d’apparence
visuelle, comme ceux qui déclarent l’en-tête (<h:head>), le corps (<h:body>) ou
le formulaire (<h:form>). D’autres ont une représentation graphique et affichent
un label (<h:outputLabel>), un champ de saisie (<h:inputText>) ou un bou-
ton (<h:commandButton>). Vous remarquerez que l’on peut également utiliser des
marqueurs HTML purs (<table>, <tr>, <hr/>, etc.) dans la page.
<tr>
<td><h:outputLabel value="ISBN : "/></td>
<td>
<h:inputText value="#{bookController.book.isbn}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Title :"/></td>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 313
<td>
<h:inputText value="#{bookController.book.title}"/>
</td>
</tr>
</table>
<h:commandButton value="Create a book"
action="#{bookController.doCreateBook}"
styleClass="submit"/>
</h:form>
<hr/>
<i>APress - Beginning Java EE 6</i>
</h:body>
</html>
Moteurs de rendu
Convertisseurs et validateurs
Lorsque la page est affichée, l’utilisateur peut s’en servir pour entrer des données.
Comme il n’y a pas de contraintes sur les types, un moteur de rendu ne peut pas pré-
voir l’affichage de l’objet. Voilà pourquoi les convertisseurs existent : ils traduisent
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
314 Java EE 6 et GlassFish 3
un objet (Integer, Date, Enum, Boolean, etc.) en chaîne afin qu’il puisse s’afficher et,
inversement, construisent un objet à partir d’une chaîne qui a été saisie. JSF fournit
un ensemble de convertisseurs pour les types classiques dans le paquetage javax.
faces.convert, mais vous pouvez développer les vôtres ou ajouter des types pro-
venant de tierces parties.
Parfois, les données doivent également être validées avant d’être traitées par le back-
end : c’est le rôle des validateurs ; on peut ainsi associer un ou plusieurs validateurs
à un composant unique afin de garantir que les données saisies sont correctes. JSF
fournit quelques validateurs (LengthValidator, RegexValidator, etc.) et vous per-
met d’en créer d’autres en utilisant vos propres classes annotées. En cas d’erreur de
conversion ou de validation, un message est envoyé dans la réponse à afficher.
Tous les concepts que nous venons de présenter – qu’est-ce qu’une page, qu’est-ce
qu’un composant, comment sont-ils affichés, convertis et validés – sont liés à une
page unique, mais les applications web sont généralement formées de plusieurs
pages et doivent réaliser un traitement métier (en appelant une couche EJB, par
exemple). Le passage d’une page à une autre, l’invocation d’EJB et la synchronisa-
tion des données avec les composants sont pris en charge par les beans gérés.
Un bean géré est une classe Java spécialisée qui synchronise les valeurs avec les
composants, traite la logique métier et gère la navigation entre les pages. On associe
un composant à une propriété ou à une action spécifique d’un bean géré en utilisant
EL (Expression Language). Voici un extrait de l’exemple précédent :
<h:inputText value="#{bookController.book.isbn}"/>
<h:commandButton value="Create"
action="#{bookController.doCreateBook}"/>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 315
@EJB
private BookEJB bookEJB;
INFO
Un bean géré est la classe qui agit comme un contrôleur, navigue d’une page à l’autre, ap-
pelle les EJB, etc. Les backing beans sont les objets qui contiennent les propriétés liées aux
composants. Dans cet exemple, nous pourrions donc dire que BookController est un bean
géré et que l’attribut book est le "backing bean".
Support d’Ajax
Une application web doit fournir une interface riche et rapide. Cette réactivité peut
être obtenue en ne modifiant que de petites parties de la page de façon asynchrone,
et c’est exactement pour cela qu’Ajax a été conçu. Les versions précédentes de JSF
n’offraient pas de solution toute prête et des bibliothèques tierces, comme a4jsf,
sont donc venues combler ce manque. À partir de JSF 2.0, le support d’Ajax a été
ajouté sous la forme d’une bibliothèque JavaScript (jsf.js) définie dans la spécifi-
cation. Le code suivant, par exemple, utilise la fonction request pour soumettre un
formulaire de façon asynchrone :
<h:commandButton id="submit" value="Création d’un livre"
onclick="jsf.ajax.request(this, event,
{execute:’isbn title price description nbOfPage
illustrations’, render:’booklist’}); return false;"
actionListener="#{bookController.doCreateBook}" />
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
316 Java EE 6 et GlassFish 3
Au début du Web, les pages étaient statiques : un utilisateur demandait une ressource
(une page, une image, une vidéo, etc.) et le serveur la lui renvoyait – simple, mais
très limité. Avec l’augmentation de l’activité commerciale sur le Web, les sociétés se
sont trouvées obligées de fournir du contenu dynamique à leurs clients. La première
solution a donc consisté à utiliser CGI (Common Gateway Interface) : en utilisant
des pages HTML et des scripts CGI écrits dans différents langages (allant de Perl à
Visual Basic), une application pouvait accéder à des bases de données et servir ainsi
du contenu dynamique. Mais CGI était de trop bas niveau (il fallait gérer les en-têtes
HTTP, appeler les commandes HTTP, etc.) et une solution plus élaborée semblait
nécessaire.
En 1995, Java fit son apparition avec une API d’interface utilisateur indépendante
des plates-formes, appelée AWT (Abstract Window Toolkit). Plus tard, avec Java
SE 1.2, AWT, qui reposait sur l’interface utilisateur du système d’exploitation, fut
remplacé par l’API Swing (qui dessine ses propres widgets en utilisant Java 2D).
Dès les premiers jours de Java, le navigateur Netscape Navigator proposa le support
de ce nouveau langage, ce qui marqua le début de l’ère des applets – des applications
qui s’exécutent sur le client, dans un navigateur. Les applets permettent d’écrire
des applications AWT ou Swing et de les intégrer dans une page web, mais leur
utilisation ne décolla jamais vraiment. De son côté, Netscape avait également créé
un langage de script appelé JavaScript qui s’exécutait directement dans le naviga-
teur : malgré certaines incompatibilités entre les navigateurs, ce langage est toujours
très utilisé actuellement car c’est un moyen efficace de créer des applications web
dynamiques.
Après l’échec des applets, Sun présenta les servlets comme un moyen de créer des
clients web dynamiques légers. Les servlets étaient une alternative aux scripts CGI
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 317
car elles offraient une bibliothèque de plus haut niveau pour gérer HTTP, permet-
taient d’accéder à toute l’API de Java (ce qui incluait donc les accès aux bases de
données, les appels distants, etc.) et pouvaient créer une réponse en HTML pouvant
s’afficher chez le client.
En 1999, Sun présenta JSP comme une amélioration du modèle des servlets mais,
comme les JSP mélangeaient du code Java et du code HTML, un framework open-
source, Struts, vit le jour en 2001 et proposa une nouvelle approche. Ce framework
étendait l’API des servlets et encourageait les développeurs à adopter une archi-
tecture MVC. L’histoire récente est remplie d’autres frameworks tentant, chacun,
de combler les lacunes des précédents (Tapestry, Wicket, WebWork, DWR, etc.).
Aujourd’hui, le framework web conseillé pour Java EE 6 est JSF 2.0, qui rivalise
avec Struts et Tapestry dans le monde Java. Rails et Grails rivalisent un peu partout
avec JSF, tout comme Java rivalise avec Ruby et Groovy. Par ailleurs, GWT (Google
Web Toolkit), Flex et JavaFX peuvent être complémentaires de JSF.
Du point de vue de leur architecture, les JSP sont une abstraction de haut niveau
des servlets et ont été implémentées comme une extension de Servlet 2.1. JSP 1.2
et Servlet 2.3 ont été spécifiées ensemble dans la JSR 53 alors qu’en même temps
JSTL (JSP Standard Tag Library) faisait l’objet de la JSR 52.
Depuis 2002, la spécification JSP 2.0 a évolué séparément dans la JSR 152. En
2006, JSP 2.1 a été ajoutée à Java EE 5 et a facilité l’intégration entre JSF et JSP en
introduisant un langage d’expressions (EL) unifié. Avec Java EE 6, les spécifications
de JSP et EL sont passées à la version 2.0 – l’une des principales modifications est
qu’il est désormais possible d’invoquer une méthode avec EL.
JSF 2.0
JSF est une spécification publiée par le JCP (Java Community Process) et a été
créée en 2001 par la JSR 127. Sa version de maintenance 1.1 est apparue en 2004
et ce n’est qu’en 2006 que JSF 1.2 a été ajoutée dans Java EE par la JSR 252 (avec
Java EE 5). Le plus gros défi de cette version consistait à préserver la compatibilité
ascendante et à intégrer JSP avec un EL unifié. Malgré ces efforts, JSF et JSP ne
fonctionnent pas très bien ensemble et d’autres frameworks comme Facelets ont
donc été introduits pour fournir une alternative aux JSP.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
318 Java EE 6 et GlassFish 3
JSF 2.0 est une version majeure (JSR 314) et est désormais le choix conseillé pour
le développement web avec Java EE 6 (JSP reste maintenue, mais n’a reçu aucune
amélioration importante avec Java EE 6). JSF 2.0 s’est inspiré de nombreux fra-
meworks web open-source et leur ajoute de nouvelles fonctionnalités.
Avec ses nouvelles fonctionnalités, JSF 2.0 est une évolution de 1.2, mais il va éga-
lement au-delà – les Facelets sont préférées à JSP, par exemple. Parmi les ajouts de
JSF 2.0, citons :
■■ une autre technologie de présentation que JSP, reposant sur Facelets ;
■■ un nouveau mécanisme de gestion des ressources (pour les images, les scripts
JavaScript, etc.) ;
■■ des portées supplémentaires (portée de vue et portée de composant) ;
■■ le développement plus simple grâce aux annotations pour les beans gérés, les
moteurs de rendu, les convertisseurs, les validateurs, etc. ;
■■ la réduction de la configuration XML en exploitant les annotations et la configu-
ration par exception (le fichier faces-config.xml est facultatif) ;
■■ le support d’Ajax ;
■■ le développement de composant facilité.
Implémentation de référence
Mojarra, qui est le nom d’une famille de poissons des Caraïbes, est l’implémenta-
tion de référence open-source de JSF 2.0. Elle est disponible via les mises à jour de
GlassFish V3 et permet de développer des applications web JSF 2.0 en invoquant
une couche métier EJB 3.1 et une couche de persistance JPA 2.0. C’est elle que nous
utiliserons dans notre récapitulatif.
Récapitulatif
Nous allons écrire une petite application web proposant deux pages web : l’une
qui affiche un formulaire afin de pouvoir créer un livre (newBook.xhtml), l’autre qui
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 319
énumère tous les livres présents dans la base (listBooks.xhtml). Ces deux pages
utilisent le bean géré BookController pour stocker les propriétés nécessaires et pour
la navigation. En utilisant JPA pour la persistance et EJB pour la logique métier, tout
s’emboîte : le bean géré délègue tous les traitements métier à BookEJB, qui contient
deux méthodes, l’une pour stocker un livre dans une base de données (create-
Book()), une autre pour récupérer tous les livres (findBooks()). Ce bean de session
sans état utilise l’API EntityManager pour manipuler une entité Book. La navigation
est très simple : lorsqu’un livre est créé, on affiche la liste. Un lien sur la page de la
liste permet de revenir ensuite à la page newBook.xhtml et de créer un autre livre.
La Figure 10.2 montre l’interaction des composants de cette application ; ceux-ci
assemblés dans un fichier war et déployés sur une instance de GlassFish et une base
de données Derby.
Figure 10.2 <<entity>>
Book
Pages et classes -id : Long
impliquées dans -title : String
-price : Float
l’application newBook -description : String
web. .xhtml <<managed bean>> -nbOfPage : Integer
BookController
-illustrations : Boolean
+doList() : String
+doCreateBook() : String
<<stateless ejb>>
listBook BookEJB
.xhtml -cm : EntityManager
+findBooks() : List<Book>
+createBook(book : Book) : Book
Cette application web utilisant la structure de répertoires de Maven, les classes, les
fichiers et les pages web doivent donc être placés dans les répertoires suivants :
■■ src/main/java contient l’entité Book, l’EJB BookEJB et le bean géré
BookController.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
320 Java EE 6 et GlassFish 3
L’entité Book
Nous ne détaillerons pas beaucoup le Listing 10.3 car vous devriez maintenant
comprendre le code de l’entité Book. Outre les annotations de mapping, notez la
requête nommée findAllBooks, qui permet de récupérer les livres à partir de la base
de données.
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
Comme vous le savez désormais, cette entité doit également être associée à un fichier
persistence.xml que, pour simplifier, nous ne reproduirons pas ici.
L’EJB BookEJB
Le Listing 10.4 représente un bean de session sans état avec une vue sans interface,
ce qui signifie que le client (c’est-à-dire le bean géré) n’a pas besoin d’interface
(locale ou distante) et peut invoquer directement l’EJB. Ce dernier obtient par injec-
tion une référence à un gestionnaire d’entités grâce auquel il peut rendre persistante
une entité Book (avec la méthode createBook()) et récupérer tous les livres de la
base (avec la requête nommée findAllBooks). Cet EJB n’a besoin d’aucun descripteur
de déploiement.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 321
@PersistenceContext(unitName = "chapter10PU")
private EntityManager em;
L’un des rôles d’un bean géré consiste à interagir avec les autres couches de l’ap-
plication (la couche EJB, par exemple) ou à effectuer des validations. Dans le Lis-
ting 10.5, BookController est un bean géré car il est annoté par @ManagedBean. La
seconde annotation, @RequestScoped, définit la durée de vie du bean : ici, il vivra
le temps de la requête (on peut également choisir d’autres portées). Ce bean géré
contient deux attributs qui seront utilisés par les pages :
■■ bookList est la liste des livres récupérés à partir de la base de données, qui doit
s’afficher dans la page listBooks.xhtml.
■■ book est l’objet qui sera associé au formulaire (dans la page newBook.xhtml) et
rendu persistant.
Tout le traitement métier (création et récupération des livres) s’effectue via BookEJB.
Le bean géré obtient une référence à l’EJB par injection, via l’annotation @EJB, et
dispose de deux méthodes qui seront invoquées par les pages :
■■ doNew(). Cette méthode n’effectue aucun traitement mais permet de naviguer
vers newBook.xhtml. Comme nous le verrons au Chapitre 12, il existe plusieurs
moyens de naviguer de page en page : le plus simple consiste à renvoyer le nom
de la page cible.
■■ doCreateBook(). Cette méthode permet de créer un livre en invoquant l’EJB
sans état et en lui passant l’attribut book. Puis elle appelle à nouveau l’EJB pour
obtenir tous les livres de la base et stocke la liste dans l’attribut bookList du
bean géré. Ensuite, la méthode renvoie le nom de la page vers laquelle elle doit
naviguer.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
322 Java EE 6 et GlassFish 3
@EJB
private BookEJB bookEJB;
// Getters, setters
}
La page newBook.xhtml
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 323
<hr/>
<h:form>
<table border="0">
<tr>
<td><h:outputLabel value="ISBN : "/></td>
<td>
<h:inputText value="#{bookController.book.isbn}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Title :"/></td>
<td>
<h:inputText value="#{bookController.book.title}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Price : "/></td>
<td>
<h:inputText value="#{bookController.book.price}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Description : "/></td>
<td><h:inputTextarea
value="#{bookController.book.description}"
cols="20" rows="5"/></td>
</tr>
<tr>
<td><h:outputLabel value="Number of pages : "/></td>
<td>
<h:inputText value="#{bookController.book.nbOfPage}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Illustrations : "/></td>
<td><h:selectBooleanCheckbox
value="#{bookController.book.illustrations}"/></td>
<tr>
</table>
<h:commandButton value="Create a book"
action="#{bookController.doCreateBook}"/>
</h:form>
<hr/>
<i>APress - Beginning Java EE 6</i>
</h:body>
</html>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
324 Java EE 6 et GlassFish 3
Comme le montre la Figure 10.3, la plupart des informations sont entrées dans des
champs de saisie, sauf la description, qui utilise une zone de texte et les illustrations
qui sont indiquées par une case à cocher.
Figure 10.3
Create a new book
La page newBook.xhtml.
ISBN :
Tiltle :
Price :
Description :
Number of pages :
Illustrations :
Create a book
Bien que ce code ait été simplifié, il contient l’essentiel. Il déclare d’abord l’espace de
noms h pour les composants HTML de JSF : pour les utiliser, il faudra donc les pré-
fixer par cet espace de noms (<h:body>, <h:outputText>, <h:commandButton>, etc.).
Le langage d’expressions EL permet ensuite de lier dynamiquement la valeur du
composant à la propriété correspondante du bean géré. Le code suivant, par exemple :
<h:inputText value="#{bookController.book.isbn}"/>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 325
La page listBooks.xhtml
La méthode doCreateBook() du bean géré est appelée lors du clic sur le bouton de
soumission de la page newBook.xhtml (voir Figure 10.3) ; elle stocke le livre dans la
base et, si aucune exception n’a été lancée, renvoie le nom de la page à afficher ensuite,
listBooks.xhtml, qui affiche tous les livres de la base (voir Figure 10.4). Un lien sur
cette page permet ensuite de revenir à newBook.xhtml pour créer un autre livre.
Figure 10.4
List of the books
La page
listBooks.xhtml.
ISBN Title Price Description Number Of Pages Illustrations
1234 234 H2G2 12.0 Scifi IT book 241 false
564 694 Robots 18.5 Asimov Best seller 317 true
256 6 56 Dune 23.25 The trilogy 529 false
Create a new book
L’élément <h:dataTable> est lié à l’attribut bookList du bean géré (une ArrayList
de livres) et déclare la variable bk qui permettra de parcourir cette liste. Dans cet
élément, on peut ensuite utiliser des expressions comme #{bk.isbn} pour obtenir
l’attribut isbn d’un livre. Chaque colonne du tableau est définie par un élément
<h:column>. Le marqueur <h:commandLink> en bas de la page crée un lien qui,
lorsqu’on clique dessus, appelle la méthode doNew() du bean géré (celle-ci permet
de revenir à la page newBook.xhtml).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
326 Java EE 6 et GlassFish 3
<h:column>
<f:facet name="header">
<h:outputText value="ISBN"/>
</f:facet>
<h:outputText value="#{bk.isbn}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Title"/>
</f:facet>
<h:outputText value="#{bk.title}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Price"/>
</f:facet>
<h:outputText value="#{bk.price}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Description"/>
</f:facet> <h:outputText value="#{bk.description}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Number Of Pages"/>
</f:facet>
<h:outputText value="#{bk.nbOfPage}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Illustrations"/>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 327
</f:facet>
<h:outputText value="#{bk.illustrations}"/>
</h:column>
</h:dataTable>
<h:form>
<h:commandLink action="#{bookController.doNew}">
Create a new book
</h:commandLink>
</h:form>
<hr/>
<i>APress - Beginning Java EE 6</i>
</h:body>
</html>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
</web-app>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
328 Java EE 6 et GlassFish 3
L’application web doit être compilée et assemblée dans un fichier war (<packaging>war
</packaging>). Le fichier pom.xml du Listing 10.9 déclare toutes les dépendances
nécessaires à la compilation du code (jsf-api, javax.ejb et javax.persistence)
et précise que cette compilation utilisera la version 1.6 du JDK. Avec JSF 2.0, le
fichier faces-config.xml n’est plus obligatoire et nous ne l’utilisons pas ici.
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapter10</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>javax.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.ejb</artifactId>
<version>3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 10 JavaServer Faces 329
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
L’application web assemblée doit ensuite être déployée dans GlassFish. Après avoir
vérifié que Derby s’exécute et écoute sur son port par défaut, ouvrez un interpréteur
en ligne de commande, placez-vous dans le répertoire target contenant le fichier
chapter10-1.0.war et entrez la commande suivante :
Exécution de l’application
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
330 Java EE 6 et GlassFish 3
Le fichier pointé est newBook.faces, pas newBook.xhtml, car avec l’extension .faces
JSF sait qu’il doit traiter la page avant de l’afficher (voir l’association de.faces avec
FacesServlet dans le Listing 10.8). Lorsque la page newBook s’affiche, saisissez les
informations et cliquez sur le bouton d’envoi du formulaire pour être redirigé sur la
page listBooks.
Résumé
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
11
Pages et composants
Nous vivons dans le monde de l’Internet. Munis d’un backend transactionnel qui
traite des milliers de requêtes et communique avec des systèmes hétérogènes au
moyen de services web, nous avons maintenant besoin d’une couche de présenta-
tion pour interagir avec les utilisateurs – de préférence une interface qui s’exécute
dans un navigateur car les navigateurs sont partout et parce que les interfaces web
sont plus riches, plus dynamiques et plus simples à utiliser. Les RIA (Rich Internet
Applications) sont de plus en plus appréciées car les utilisateurs peuvent profiter de
leur connaissance de leurs navigateurs : ils ont besoin de consulter des catalogues de
livres et de CD, mais ils veulent également accéder au courrier électronique et à des
documents, recevoir des notifications par courrier ou voir une partie de leur naviga-
teur se mettre à jour en fonction des informations reçues du serveur. Ajoutons à cela
que la philosophie du Web 2.0 est de faire partager toutes sortes d’informations à des
groupes d’amis qui peuvent interagir les uns avec les autres et l’on comprend que les
interfaces web soient de plus en plus compliquées à développer.
Aux premiers jours de Java, les développeurs émettaient directement du HTML à
partir des servlets. Puis nous sommes passés des servlets à JSP (Java Server Pages),
qui utilise des marqueurs personnalisés. Désormais, Java EE 6 et sa nouvelle version
de JSF simplifie encore plus le développement des interfaces web.
Dans ce chapitre, nous présenterons différentes technologies utilisées par Java EE 6
pour créer des pages web. Nous expliquerons d’abord quelques concepts de base
comme HTML, CSS et JavaScript, puis nous passerons à JSP, EL et JSTL. Nous
introduirons alors Facelets, le langage de présentation (PDL) conseillé pour JSF. Le
reste du chapitre s’intéressera à la création d’interfaces web avec JSF ou des com-
posants personnalisés. Le chapitre suivant expliquera comment naviguer entre les
pages et interagir avec un backend pour afficher des données dynamiques.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
332 Java EE 6 et GlassFish 3
Pages web
Lorsque l’on crée une application web, on affiche généralement un contenu dyna-
mique : une liste d’articles d’un catalogue (des CD et des livres, par exemple), les infor-
mations associées à un identifiant de client, un panier virtuel contenant les articles
que l’utilisateur veut acheter, etc. Inversement, le contenu statique, comme l’adresse
d’un éditeur et les FAQ expliquant comment acheter ou se faire livrer les articles,
change rarement, voire jamais – ce contenu peut également être une image, une
vidéo ou un dessin.
Le but ultime de la création d’une page est son affichage dans un navigateur. Elle
doit donc utiliser les langages compris par les navigateurs : HTML, XHTML, CSS
et JavaScript.
HTML
Hypertext Markup Language (HTML) est le langage qui prédomine dans les pages
web. Il repose sur SGML (Standard Generalized Markup Language), un métalan-
gage standard permettant de définir des langages à marqueurs. HTML utilise des
balises, ou marqueurs, pour structurer le texte en paragraphes, listes, liens, boutons,
zones de texte, etc.
Une page HTML est un document texte utilisé par les navigateurs pour présenter du
texte et des images : ce sont des fichiers texte portant souvent l’extension .html ou
.htm. Une page web est formée d’un contenu, de marqueurs permettant de changer
certains aspects de ce contenu et d’objets externes comme des images, des vidéos,
du code JavaScript ou des fichiers CSS.
La section "Récapitulatif" du chapitre précédent a montré deux pages JSF, dont
l’une affichait un formulaire pour créer un nouveau livre. Le Listing 11.1 montre
cette page écrite en HTML pur, sans utiliser aucun marqueur JSF.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 333
<td>Title :</td>
<td><input type=text/></td>
</tr>
<tr>
<td>Price :</td>
<td><input type=text/></td>
</tr>
<tr>
<td>Description :
<td><textarea name=textarea cols=20 rows=5></textarea>
</tr>
<TR>
<TD>Number of pages :
<td><input type=text/>
</tr>
<tr>
<td>Illustrations :
<td><input type=checkbox/>
</tr>
</table>
<input type=submit value=Create>
<hr>
<i>APress - Beginning Java EE 6</i>
Normalement, une page HTML valide commence par un marqueur <html> qui agit
comme un conteneur du document. Il est suivi des marqueurs <head> et <body>. Ce
dernier contient la partie visible – ici, un tableau constitué de labels et de champs
de saisie, et un bouton. Comme vous pouvez le constater, le fichier newBook.html
du Listing 11.1 ne respecte pas ces règles mais les navigateurs peuvent afficher des
pages HTML non valides (jusqu’à un certain point). Le résultat affiché ressemblera
donc à la Figure 11.1.
Figure 11.1
Create a new book
Représentation graphique
de la page newBook.html.
ISBN :
Tiltle :
Price :
Description :
Number of pages :
Illustrations :
Create
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
334 Java EE 6 et GlassFish 3
XHTML
XHTML a été créé peu de temps après HTML 4.01. Ses racines puisent dans HTML,
mais avec une reformulation en XML strict. Ceci signifie qu’un document XHTML
est un document XML qui respecte un certain schéma et peut être représenté gra-
phiquement par les navigateurs – un fichier XHTML (qui porte l’extension .xhtml)
peut être directement utilisé comme du XML ou être affiché dans un navigateur. Par
rapport à HTML, il a l’avantage de permettre une validation et une manipulation du
document à l’aide d’outils XML standard (XSL ou eXtensible Stylesheet Language ;
XSLT ou XSL Transformations ; etc.).
Le Listing 11.2 montre la version XHTML de la page web du Listing 11.1.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 335
<td>ISBN :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Title :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Price :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Description :</td>
<td><textarea name="textarea" cols="20"
rows="5"></textarea></td>
</tr>
<tr>
<td>Number of pages :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Illustrations :</td>
<td><input type="checkbox"/></td>
</tr>
</table>
<input name="" type="submit" value="Create"/>
<hr/>
<i>APress - Beginning Java EE 6</i>
</body>
</html>
Notez les différences entre les Listings 11.1 et 11.2 : ce dernier respecte une struc-
ture stricte et contient les marqueurs <html>, <head> et <body> ; tous les marqueurs
sont fermés, même les vides (chaque <td> est fermé et on utilise <hr/> au lieu de
<hr>) ; les valeurs des attributs sont toujours entre apostrophes ou entre guillemets
(<table border="0"> ou <table border=’0’>, mais pas <table border=0>) ; tous
les marqueurs sont en minuscules (<tr> au lieu de <TR>).
Le respect strict des règles syntaxiques de XML et les contraintes de schéma ren-
dent XHTML plus facile à maintenir et à traiter que HTML, et c’est la raison pour
laquelle il est désormais le langage préféré pour les pages web.
CSS
Les navigateurs utilisent des langages côté client comme HTML, XHTML, CSS et
JavaScript. CSS (Cascading Style Sheets) sert à décrire la présentation d’un docu-
ment écrit en HTML ou en XHTML. Il permet de définir les couleurs, les polices, la
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
336 Java EE 6 et GlassFish 3
disposition et les autres aspects de la présentation d’un document et, donc, de séparer
son contenu (écrit en XHTML) de sa présentation (écrite en CSS). Comme HTTP,
HTML et XHTML, les spécifications de CSS sont édictées par le W3C (World Wide
Web Consortium).
Supposons, par exemple, que vous vouliez modifier les labels de la page newBook.
xhtml pour qu’ils soient tous en italique (font-style: italic;), de couleur bleue
(color: #000099;) et dans une taille de police plus grande (font-size: 22px;). Au
lieu de répéter ces modifications pour chaque marqueur, il suffit de définir un style
CSS (dans un marqueur <style type="text/css">) et de lui donner un alias (title
et row, par exemple) : la page appliquera alors ce style pour tous les éléments qui
utilisent cet alias afin de modifier leur présentation (<h1 class="title">).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 337
<td><input type="text"/></td>
</tr>
<tr>
<td class="row">Description :</td>
<td><textarea name="textarea" cols="20"
rows="5"></textarea></td>
</tr>
<tr>
<td class="row">Number of pages :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td class="row">Illustrations :</td>
<td><input type="checkbox"/></td>
</tr>
</table>
<input name="" type="submit" value="Create"/>
<hr/>
<i>APress - Beginning Java EE 6</i>
</body>
</html>
Dans cet exemple, le code CSS est intégré à la page XHTML mais, dans une vraie
application, tous les styles seraient placés dans un fichier distinct qui serait importé
par la page web. Le webmestre peut ainsi créer un ou plusieurs fichiers CSS pour
différents groupes de pages et les contributeurs de contenu peuvent écrire ou modi-
fier leurs pages sans être concernés par l’aspect final de leurs documents.
À la Figure 11.2, tous les labels sont désormais en italique et le titre de la page
apparaît en bleu.
Figure 11.2
Create a new book
Représentation graphique
de la page newBook.xhtml.
ISBN :
Tiltle :
Price :
Description :
Number of pages :
Illustrations :
Create
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
338 Java EE 6 et GlassFish 3
DOM
Une page XHTML est un document XML et a donc une représentation DOM (Docu-
ment Object Model). DOM est une spécification du W3C pour accéder et modifier le
contenu et la structure des documents XML ainsi qu’une API abstraite pour interro-
ger, parcourir et manipuler ce type de document – il peut être considéré comme une
représentation arborescente de la structure d’un document. La Figure 11.3 montre
une représentation DOM de la page newBook.xhtml : la racine est le marqueur html,
ses deux fils sont head et body et ce dernier a lui-même un fils table avec une liste
de fils tr.
Figure 11.3
Représentation
arborescente de la page
newBook.xhtml.
DOM fournit un moyen standard d’interaction avec les documents XML. Grâce à
lui, vous pouvez parcourir l’arbre d’un document et modifier le contenu d’un nœud.
Moyennant un peu de code JavaScript, il est possible d’ajouter un comportement
dynamique à une page web. Comme nous le verrons au chapitre suivant, Ajax utilise
JavaScript sur la représentation DOM d’une page.
JavaScript
Les langages que nous avons évoqués jusqu’à maintenant permettent de représen-
ter le contenu statique et les aspects graphiques d’une page web. Cependant, une
page doit souvent interagir avec l’utilisateur en affichant du contenu dynamique. Ce
contenu dynamique peut être traité par des technologies côté serveur comme JSP ou
JSF, mais les navigateurs peuvent également en produire de leur côté en exécutant
du code JavaScript.
JavaScript est un langage de script pour le développement web côté client. Contrai-
rement à ce que son nom pourrait laisser supposer, il n’a rien à voir avec le langage
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 339
de programmation Java car c’est un langage interprété et faiblement typé. Avec Java
Script, il est possible de créer des applications web dynamiques en écrivant des fonc-
tions qui agissent sur le DOM d’une page. Il a été standardisé par l’ECMA (European
Computer Manufacturers Association) sous le nom d’ECMAScript. Toute page écrite
en respectant les standards XHTML, CSS et JavaScript devrait s’afficher et se com-
porter de façon quasiment identique avec tout navigateur respectant ces normes.
Le Listing 11.4 contient un exemple de code JavaScript manipulant le DOM de la
page newBook.xhtml qui affiche un formulaire permettant de saisir des informations
sur un livre. Le prix du livre doit être fourni par l’utilisateur côté client avant d’at-
teindre le serveur : une fonction JavaScript (priceRequired()) permet de valider ce
champ en testant s’il est vide ou non.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
340 Java EE 6 et GlassFish 3
Figure 11.4
Create a new book
La page newBook.html
affiche un message
ISBN :
d’erreur.
Tiltle :
Description :
Number of pages :
Illustrations :
Create
JavaScript est un langage puissant : nous n’en avons présenté qu’une petite partie
pour montrer son interaction avec DOM mais il est important de comprendre qu’une
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 341
fonction JavaScript peut accéder à un nœud de la page (par son nom ou son identi-
fiant) et modifier dynamiquement son contenu côté client. Nous donnerons plus de
détails dans la section "Ajax" du prochain chapitre.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
342 Java EE 6 et GlassFish 3
■■ renvoyant les réponses aux clients (ces réponses ne contiennent que des marqueurs
HTML ou XHTML pour pouvoir s’afficher dans un navigateur) ;
■■ déchargeant la JSP et arrêtant de lui envoyer des requêtes (lorsque le serveur
s’arrête, par exemple).
Une page JSP pouvant produire du code HTML ou XHTML, vous pouvez utili-
ser des extensions différentes pour l’indiquer – .jsp pour HTML et .jspx pour
XHTML, par exemple. Examinons le code suivant :
<html>
<head>
<title>Lists all the books</title>
</head>
<body>
<h1>Lists all the books</h1>
<hr/>
</body>
</html>
Comme vous pouvez le constater, une JSP valide peut ne contenir que des mar-
queurs HTML : vous pourriez sauvegarder ce code dans un fichier listBooks.jsp
et le déployer dans un conteneur de servlets qui renverrait alors une simple page
HTML. En fait, une page JSP ressemble à du HTML, mais elle peut également
contenir des marqueurs supplémentaires qui permettent d’ajouter du contenu dyna-
mique afin que les réponses produites dépendent des requêtes. La spécification JSP
définit les éléments suivants :
■■ directives ;
■■ scripts ;
■■ actions.
Comme nous le verrons, il existe deux syntaxes pour ces éléments : la syntaxe XML
pour les pages XHTML (<jsp:directive attributs/>) et la syntaxe JSP, qui n’est
pas conforme à XML (<%@ attributs %>).
Directives
Les directives fournissent des informations sur la JSP et ne produisent rien. Il existe
trois directives : page, include et taglib. Les deux syntaxes possibles sont :
<%@ directive attributs %>
<jsp:directive attributs />
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 343
La directive page sert à indiquer les attributs de page tels que le langage de program-
mation de la page (Java, ici), le type MIME, l’encodage des caractères de la réponse,
si la JSP est une page d’erreur, etc.
<%@ page contentType=" text/html; ISO-8859-1" language="java" %>
<jsp:directive.page contentType="text/html; ISO-8859-1"
language="java"/>
La directive include sert à inclure une autre page (HTML, XHTML ou JSP) dans la
page courante. Vous pouvez l’utiliser pour inclure une page standard (un en-tête ou
un pied de page, par exemple) dans plusieurs JSP.
<%@ include file="header.jsp"%>
<jsp:directive.include file="header.jsp" />
La section "Bibliothèque des marqueurs JSP standard" montre que l’on peut étendre
les JSP à l’aide d’une bibliothèque de marqueurs. La directive taglib déclare qu’une
page utilise l’une de ces bibliothèques en l’identifiant de façon unique par une URI
et un préfixe. Avec la syntaxe XML, ces deux informations sont regroupées dans un
espace de noms unique (xmlns). Dans l’exemple suivant, la bibliothèque de mar-
queurs http://java.sun.com/jstl/core est disponible pour la page via le préfixe c :
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<jsp:root xmlns:c="http://java.sun.com/jstl/core">
Scripts
Les scripts incluent du code Java permettant de manipuler des objets et d’effectuer
des traitements affectant le contenu. Ils peuvent utiliser les deux syntaxes suivantes :
<%! déclaration %>
<jsp:declaration>ceci est une déclaration</jsp:declaration>
<% scriptlet %>
<jsp:scriptlet>ceci est un scriptlet</jsp:scriptlet>
<%= expression %>
<jsp:expression>ceci est une expression</jsp:expression>
Les déclarations permettent de déclarer les variables ou les méthodes qui seront dispo-
nibles pour tous les autres scripts de la page. La déclaration n’apparaît que dans la JSP
traduite (c’est-à-dire dans la servlet), pas dans ce qui est envoyé au client. Le code sui-
vant, par exemple, déclare une instance d’ArrayList qui sera globale à toute la page :
<%! ArrayList books = new ArrayList(); %>
<jsp:declaration>
ArrayList books = new ArrayList();
</jsp:declaration>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
344 Java EE 6 et GlassFish 3
Les scriptlets contiennent du code Java permettant de décrire les actions à réaliser
en réponse aux requêtes. Ils peuvent servir à effectuer des itérations ou à exécuter
conditionnellement d’autres éléments de la JSP. Comme les déclarations, le code
d’un scriptlet n’apparaît que dans la JSP traduite (la servlet). Le code suivant, par
exemple, ajoute un objet Book à l’ArrayList déclarée plus haut :
<%
books.add(new Book("H2G2", 12f, "Scifi IT book",
"1234-234", 241, true));
%>
<jsp:scriptlet>
books.add(new Book("H2G2", 12f, "Scifi IT book",
"1234-234", 241, true));
</jsp:scriptlet>
Les expressions servent à envoyer la valeur d’une expression Java au client. Elles
sont évaluées au moment de la réponse et leur résultat est converti en chaîne de
caractères puis inséré dans le flux affiché par le navigateur. Le fragment de code
suivant, par exemple, affichera l’ISBN d’un livre :
<%= book.getIsbn()%> <jsp:expression>book.getIsbn()</jsp:expression>
Les déclarations, les scriptlets et les expressions doivent contenir du code Java cor-
rect. Si vous choisissez d’utiliser la syntaxe XML, leur contenu doit également être
du XML valide. Le code suivant, par exemple, déclare une ArrayList de livres en
utilisant une classe générique :
<%! ArrayList<Book> books = new ArrayList<Book>(); %>
Si vous voulez faire la même déclaration dans un format XML strict, vous ne pou-
vez pas utiliser les symboles < et > car ils sont réservés à l’ouverture et à la ferme-
ture des marqueurs XML. Vous devez donc utiliser une section CDATA (qui signifie
Character DATA) afin que le parser XML ne tente pas de l’analyser :
<jsp:declaration><![CDATA[
ArrayList<Book> books = new ArrayList<Book>();
]]></jsp:declaration>
Actions
Les actions standard sont définies par la spécification JSP et forcent la page à effec-
tuer certaines actions (inclure des ressources externes, faire suivre une requête vers
une autre page ou utiliser les propriétés d’objets Java). Elles ressemblent à des mar-
queurs HTML car elles sont représentées par des éléments XML préfixés par jsp
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 345
Action Description
useBean Associe une instance d’objet à une portée donnée et à un identifiant.
setProperty Fixe la valeur d’une propriété d’un bean.
getProperty Affiche la valeur d’une propriété d’un bean.
include Permet d’inclure des ressources statiques et dynamiques dans le même
contexte que celui de la page courante.
forward Fait suivre la requête courante à une ressource statique, une JSP ou une
servlet dans le même contexte que celui de la page courante.
param Utilisé avec les éléments include, forward et params. La page incluse ou
transférée verra l’objet requête initial avec les paramètres originaux, plus
les nouveaux.
plugin Permet à une JSP de produire du HTML contenant des constructions
spécifiques au navigateur (OBJECT ou EMBED), qui provoquera le
téléchargement d’une extension.
params Passe des paramètres. Fait partie de l’action plugin.
element Définit dynamiquement la valeur du marqueur d’un élément XML.
attribute Définit un attribut XML. Fait partie de l’action element.
body Définit le corps d’un élément XML. Fait partie de l’action element.
Récapitulatif
Tous ces éléments permettent d’invoquer du code Java et toutes sortes de compo-
sants (EJB, bases de données, services web, etc.). À titre d’exemple, nous allons
créer une page qui affichera une liste de livres stockés dans une ArrayList. Ici, nous
n’accéderons pas à une base de données : nous nous contenterons d’une ArrayList
initialisée avec un nombre déterminé d’objets Book, que nous parcourrons pour
afficher les attributs de chaque livre (ISBN, titre, description, etc.). La Figure 11.5
montre le résultat attendu.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
346 Java EE 6 et GlassFish 3
Figure 11.5
List of the books
La page listBooks.jsp
affiche une liste de livres.
ISBN Title Price Description Number Of Pages Illustrations
1234 234 H2G2 12.0 Scifi IT book 241 true
56 694 Robots 18.5 Best seller 317 true
256 6 56 Dune 23.25 The trilogy 529 true
Nous avons besoin de plusieurs éléments pour construire cette page. Comme le
montre le Listing 11.5, il faut importer les classes java.util.ArrayList et Book
avec une directive (<%@ page import="java.util.ArrayList" %>). Puis on déclare
un attribut books, instance d’ArrayList, afin qu’il soit accessible à toute la page
(<%! ArrayList<Book> books = new ArrayList<Book>(); %>). Ensuite, un scri-
plet ajoute des objets livres dans une ArrayList et un autre parcourt cette liste avec
une instruction for. Pour afficher les attributs de chaque livre, nous utilisons des
éléments expression (<%= book. getTitle()%>). Le Listing 11.5 présente le code
complet de cette page.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 347
</tr>
<%
Book book;
for (int i = 0; i < books.size(); i++) {
book = books.get(i);
%>
<tr>
<td><%= book.getIsbn()%></td>
<td><%= book.getTitle()%></td>
<td><%= book.getPrice()%></td>
<td><%= book.getDescription()%></td>
<td><%= book.getNbOfPage()%></td>
<td><%= book.getIllustrations()%></td>
</tr>
<%
} // fin de l’instruction for
%>
</table>
<hr/>
<i>APress - Beginning Java EE 6</i>
</body>
</html>
Vous remarquerez que l’on peut librement entrelacer du code Java, des marqueurs
HTML et du texte. Tout ce qui est dans un scriptlet (entre <% et %>) est du code Java
qui sera exécuté sur le serveur et tout ce qui est à l’extérieur est du texte qui sera affi-
ché dans la page de réponse. Notez également que le bloc de l’instruction for com-
mence et se termine dans des scriptlets différents. Une JSP peut donc rapidement
devenir difficile à relire si l’on commence à trop mélanger des marqueurs HTML
avec du code Java. En outre, il n’y a pas de séparation entre la logique métier et la
présentation, ce qui complique la maintenance des pages car on mélange deux lan-
gages destinés à deux catégories d’intervenants : Java pour les développeurs métiers
et XHTML/CSS pour les concepteurs web.
Les JSP peuvent utiliser des bibliothèques de marqueurs et le langage d’expressions
(EL). JSTL (JSP Standard Tag Library) standardise un certain nombre d’actions clas-
siques en utilisant un langage à marqueurs familier pour les développeurs web, tandis
qu’EL utilise une syntaxe plus simple pour effectuer certaines actions des scripts JSP.
Nous venons de voir comment utiliser des scripts pour intégrer du code dans une
page JSP. Les instructions EL fournissent une syntaxe plus simple pour effectuer des
actions similaires et elles sont plus faciles à utiliser pour ceux qui ne développent
pas en Java. Elles permettent d’afficher les valeurs des variables ou d’accéder aux
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
348 Java EE 6 et GlassFish 3
où expr est une expression valide qui est analysée et interprétée. À partir de JSP 2.1,
vous pouvez également utiliser la syntaxe suivante :
#{expr}
■■ relationnels, == (eq), != (ne), < (lt), > (gt), <= (le), >= (ge) ;
L’opérateur point permet d’accéder à un attribut d’un objet. Une autre syntaxe pos-
sible consiste à utiliser l’opérateur []. On peut donc accéder à l’attribut isbn à l’aide
de ces deux syntaxes :
#{book.isbn}
#{book[isbn]}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 349
Avec EL 2.2, il est désormais possible d’appeler des méthodes. Le fragment de code
qui suit montre comment acheter un livre en appelant la méthode book.buy() et
comment lui passer un paramètre (la monnaie utilisée) :
#{book.buy}
#{book.buy(’EURO’)}
Les instructions EL peuvent être employées avec les pages JSP et JSF, le fichier
faces-config.xml, les scriptlets ou JSTL.
Fonctions http://java.sun.com/jsp/jstl/functions fn
Avant de pouvoir utiliser ces actions, la JSP doit importer l’URI de la bibliothèque
et choisir un préfixe, soit en utilisant une directive JSP avec le système de marqueurs
de JSP, soit en utilisant une syntaxe XML :
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
350 Java EE 6 et GlassFish 3
Avec cette déclaration, vous pouvez ensuite utiliser toutes les actions de la biblio-
thèque des marqueurs fondamentaux en utilisant le préfixe c :
<c:set var="upperLimit" value="20"/>
Actions fondamentales
Action Description
<c:out> Évalue une expression et affiche son résultat.
<c:set> Initalise la valeur d’un objet.
<c:remove> Supprime une variable.
<c:catch> Capture une exception java.lang.Throwable lancée par l’une de ses
actions imbriquées.
<c:if> Teste si une expression est vraie.
<c:choose> Fournit plusieurs alternatives exclusives.
<c:when> Représente une alternative dans une action <c:choose>.
<c:otherwise> Représente la dernière alternative d’une action <c:choose>.
<c:forEach> Répète son corps pour chaque élément d’une collection ou un nombre fixé
de fois.
<c:forTokens> Itère sur une liste de tokens (lexèmes) séparés par des virgules.
<c:import> Importe une ressource.
<c:url> Encode une URL.
<c:param> Ajoute des paramètres de requête à une URL.
<c:redirect> Redirige vers une URL précise.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 351
<c:choose>
<c:when test="${i%2 == 0}">
<c:out value="${i} is even"/><br/>
</c:when>
<c:otherwise>
<c:out value="${i} is odd"/><br/>
</c:otherwise>
</c:choose>
</c:forEach>
</body>
</html>
Pour utiliser la bibliothèque des marqueurs fondamentaux, la page doit importer son
URI avec un préfixe. Puis elle affecte la valeur 20 à la variable upperLimit à l’aide
du marqueur <c:set> et itère sur tous les nombres compris entre 3 et 15 (nous avons
volontairement ajouté une expression arithmétique ${upperLimit – 5}). La valeur
de l’indice (la variable i) est testée à chaque tour de boucle pour savoir si elle est
paire ou impaire (<c:when test="${i%2 == 0}">). Le marqueur <c:out> affiche
ensuite la valeur de l’indice, accompagnée du texte "est pair" ou "est impair".
Vous pouvez remarquer que tout le traitement s’effectue grâce aux marqueurs, que
cette page est conforme à XML et qu’elle peut être comprise par les développeurs
qui ne connaissent pas Java.
Actions de formatage
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
352 Java EE 6 et GlassFish 3
Action Description
<fmt:message> Internationalise une JSP en extrayant un message en
fonction de la langue.
<fmt:param> Fournit un paramètre à <fmt:message>.
<fmt:bundle> Indique le paquetage contenant les messages par langue.
<fmt:setLocale> Fixe la langue à utiliser.
<fmt:requestEncoding> Fixe l’encodage des caractères de la requête.
<fmt:timeZone> Précise la zone horaire du format de l’heure.
<fmt:setTimeZone> Stocke la zone horaire indiquée dans une variable.
<fmt:formatNumber> Formate une valeur numérique (nombre, monnaie,
pourcentage) selon la locale.
<fmt:parseNumber> Analyse la représentation textuelle des nombres, des valeurs
monétaires et des pourcentages.
<fmt:formatDate> Formate les dates et les heures selon la langue.
<fmt:parseDate> Analyse la représentation textuelle des dates et des heures.
Currency
<fmt:setLocale value="en_us"/>
<fmt:formatNumber value="20.50" type="currency"/>
<fmt:setLocale value="en_gb"/>
<fmt:formatNumber value="20.50" type="currency"/>
</body>
</html>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 353
Currency
$20.50
£20.50
Actions SQL
Les actions SQL de la JSTL permettent d’effectuer des requêtes sur une base de
données (insertions, modifications et suppressions), d’accéder aux résultats de ces
requêtes et même de mettre en place un contexte transactionnel. Nous avons déjà vu
comment accéder à une base de données avec les entités JPA et les EJB mais, pour
des applications spécifiques, on a parfois besoin d’accéder à une base à partir d’une
page web (pour une application web d’administration non critique utilisée occasion-
nellement par un unique utilisateur, par exemple) : dans ce cas, les marqueurs de la
bibliothèque SQL (voir Tableau 11.5) peuvent se révéler utiles.
Action Description
<sql:query> Interroge une base de données.
<sql:update> Exécute une instruction SQL INSERT, UPDATE ou DELETE.
<sql:transaction> Établit un contexte transactionnel pour les marqueurs
<sql:query> et <sql:update>.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
354 Java EE 6 et GlassFish 3
Action Description
<sql:param> Fixe les valeurs des marqueurs d’emplacements (?) d’une
instruction SQL.
<sql:dateParam> Fixe les valeurs des marqueurs d’emplacements (?) d’une
instruction SQL pour les valeurs de type java.util.Date.
La page JSP du Listing 11.8 accède à une base de données, récupère toutes les lignes
de la table BOOK et les affiche.
Listing 11.8 : JSP accédant à une base de données pour récupérer tous les livres
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
<html>
<head>
<title>Lists all the books</title>
</head>
<body>
<h1>Lists all the books</h1>
<hr/>
<sql:setDataSource dataSource="jdbc/__default"/>
<sql:query var="books">
select * from book
</sql:query>
<table border="1">
<tr>
<th>ISBN</th>
<th>Title</th>
<th>Price</th>
<th>Description</th>
<th>Number of pages</th>
<th>Illustrations</th>
</tr>
<c:forEach var="row" items="${books.rows}">
<tr>
<td><c:out value="${row.isbn}"/></td>
<td><c:out value="${row.title}"/></td>
<td><c:out value="${row.price}"/></td>
<td><c:out value="${row.description}"/></td>
<td><c:out value="${row.nbOfPage}"/></td>
<td><c:out value="${row.illustrations}"/></td>
</tr>
</c:forEach>
</table>
<hr/>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 355
Cette JSP doit d’abord importer la bibliothèque sql avec une directive <%@ taglib>
puis indiquer la source de données (ici, la source par défaut jdbc/__default fournie
avec GlassFish). Le résultat de l’exécution de la requête select * from book est
stocké dans la variable books et toutes les lignes obtenues se trouvent dans la collec-
tion books.rows, que l’on parcourt avec le marqueur <c:forEach>. Le résultat sera
identique à celui que nous avons déjà présenté à la Figure 11.5.
Actions XML
Action Description
<x:parse> Analyse un document XML.
<x:out> Évalue une expression XPATH et produit son résultat.
<x:set> Évalue une expression XPATH et stocke son résultat dans une
variable.
<x:if> Évalue l’expression XPATH si l’expression est vraie.
<x:choose> Fournit plusieurs alternatives exclusives.
<x:when> Représente une alternative d’une action <x:choose>.
<x:otherwise> Représente la dernière alternative d’une action <x:choose>.
<x:forEach> Évalue une expression XPATH et répète son contenu sur le résultat.
<x:transform> Applique une feuille de style XSLT à un document XML.
<x:param> Fixe les paramètres de la transformation <x:transform>.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
356 Java EE 6 et GlassFish 3
Nous avons besoin d’un document XML pour effectuer les transformations réalisées
par ces marqueurs. Le fichier books.xml du Listing 11.9 contient une liste de livres
stockés sous forme d’éléments et d’attributs XML.
Ce fichier doit être déployé avec la JSP ou être importé par une URL. La page JSP
du Listing 11.10 l’analyse et affiche tous les livres.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 357
<td><x:out select="$b/@price"/></td>
<td><x:out select="$b/description"/></td>
<td><x:out select="$b/@nbOfPage"/></td>
<td><x:out select="$b/@illustrations"/></td>
</tr>
</x:forEach>
</table>
</body>
</html>
Pour commencer, cette JSP doit importer la bibliothèque de marqueurs XML (avec
la directive <%@ taglib>) puis charger le fichier books.xml dans la variable bookUrl
à l’aide du marqueur <c:import>. bookUrl contient le texte brut, qui doit être ana-
lysé par le marqueur <x:parse> – le DOM résultant sera stocké dans la variable
doc. Une fois que le document a été analysé, nous pouvons le parcourir et afficher
les valeurs en utilisant des expressions XPATH avec <x:out> (/@isbn représente un
attribut XML et /title, un élément). Le résultat obtenu sera identique à celui de la
Figure 11.5.
Fonctions
Les fonctions ne sont pas des marqueurs mais sont quand même définies dans la
spécification JSTL. Elles peuvent être utilisées avec EL et sont principalement
employées pour traiter les chaînes de caractères :
${fn:contains("H2G2", "H2")}
Ce code teste si une chaîne contient une sous-chaîne particulière : ici, cet appel ren-
verra true car H2G2 contient H2. L’appel suivant renvoie la longueur d’une chaîne
ou d’une collection : dans cet exemple précis son résultat sera 4.
${fn:length("H2G2")}
Une JSP peut afficher les résultats des fonctions (avec un marqueur <c:out>) ou les
utiliser dans un test ou une boucle :
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fn" prefix="fn" %>
<html>
<body>
<c:out value="${fn:toLowerCase(sentence)}" />
<c:if test="${fn:length(’H2G2’) == 4}">
H2G2 is four caracters long
</c:if>
</body>
</html>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
358 Java EE 6 et GlassFish 3
Fonction Description
fn:contains Teste si une chaîne contient la sous-chaîne indiquée.
fn:containsIgnoreCase Idem, sans tenir compte de la casse.
fn:endsWith Teste si une chaîne se termine par le suffixe indiqué.
fn:escapeXml Protège les caractères pouvant être interprétés comme du XML.
fn:indexOf Renvoie l’indice de la première occurrence d’une sous-chaîne
dans une chaîne.
fn:join Joint tous les éléments d’un tableau pour former une chaîne.
fn:length Renvoie le nombre d’éléments d’une collection ou le nombre
de caractères d’une chaîne.
fn:replace Renvoie une chaîne où toutes les occurrences de la sous-
chaîne indiquée ont été remplacées par une autre chaîne.
fn:split Découpe une chaîne pour obtenir un tableau de sous-chaînes.
fn:startsWith Teste si une chaîne commence par le préfixe indiqué.
fn:substring Renvoie une sous-chaîne.
fn:substringAfter Renvoie la sous-chaîne située après la sous-chaîne indiquée.
fn:substringBefore Renvoie la sous-chaîne située avant la sous-chaîne indiquée.
fn:toLowerCase Convertit une chaîne en minuscules.
fn:toUpperCase Convertit une chaîne en majuscules.
fn:trim Supprime les espaces aux deux extrémités d’une chaîne.
Facelets
Lorsque JSF a été créé, le but consistait à réutiliser JSP comme PDL principal car
elle faisait déjà partie de Java EE. JSP utilisait EL et JSTL et l’idée consistait donc
à réutiliser toutes ces technologies avec JSF. JSP est un langage de page et JSF, une
couche de composants située au-dessus. Cependant, les cycles de vie de JSP et de
JSF ne s’accordent pas. Pour produire une réponse, les marqueurs de JSP sont traités
de haut en bas, dans l’ordre où ils apparaissent ; alors que le cycle de vie de JSF est
un peu plus compliqué puisque la production de l’arborescence des composants et
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 359
leur traitement ont lieu dans des phases différentes. Facelets entre donc en jeu pour
correspondre au cycle de vie des JSF.
Facelets est une alternative open-source à JSP. À la différence de JSP, EL et JSTL,
Facelets n’a pas de JSR et ne fait pas partie de Java EE : c’est un remplaçant de JSP
qui fournit une alternative XML (XHTML) pour les pages d’une application JSF.
Facelets ayant été conçu en tenant compte de JSF, il fournit un modèle de program-
mation plus simple que celui de JSP.
Facelets dispose d’une bibliothèque de marqueurs permettant d’écrire l’interface
utilisateur et reconnaît en partie les marqueurs JSTL. Bien que la bibliothèque de
fonctions soit intégralement disponible, seuls quelques marqueurs fondamentaux
(c:if, c:forEach, c:catch et c:set) sont reconnus. La caractéristique essentielle de
Facelets est son mécanisme de templates de pages, qui est bien plus souple que celui
de JSP. Il permet également de créer des composants personnalisés utilisables dans
le modèle arborescent de JSF.
La bibliothèque des marqueurs Facelets est définie par l’URI http://java.sun.
com/jsf/facelets et utilise généralement le préfixe ui. Ces marqueurs sont présentés
dans le Tableau 11.8.
Marqueur Description
<ui:composition> Définit une composition qui utilise éventuellement un template.
Plusieurs compositions peuvent utiliser le même template.
<ui:component> Crée un composant.
<ui:debug> Capture les informations de débogage.
<ui:define> Définit le contenu inséré dans une page par un template.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
360 Java EE 6 et GlassFish 3
Les pages Facelets sont écrites en XHTML et ressemblent à ce que nous avons déjà
vu avec le Listing 11.2. Nous verrons comment utiliser les templates dans la section
qui leur est consacrée dans ce chapitre.
JavaServer Faces
Nous avons commencé ce chapitre en présentant JSP, JSTL et Facelets car ces tech-
nologies sont essentielles pour comprendre JSF 2.0. Vous devez choisir un PDL pour
écrire une page JSF : jusqu’à JSF 1.2, le PDL préféré était JSP mais, les cycles de
vie des pages JSP et JSF étant différents, Facelets (pages en XHTML) est désormais
le PDL conseillé. Ceci ne signifie pas que vous ne puissiez pas utiliser JSP mais, si
vous le faites, vous serez très limité en termes de marqueurs et de fonctionnalités
(vous n’aurez pas accès aux marqueurs de Facelets, notamment).
Le cycle de vie des JSP est relativement simple. Un source JSP est compilé en ser-
vlet, à laquelle le conteneur web passera les requêtes HTTP qu’il reçoit. La servlet
traite la requête puis renvoie la réponse au client.
JSF a un cycle de vie plus complexe, et c’est la raison pour laquelle, dans la section
"Langage d’expressions (EL)", nous avons présenté deux syntaxes différentes : l’une
utilisant le symbole Dollar (${expression}), l’autre, le symbole dièse (#{expres-
sion}). $ est utilisé pour les expressions qui peuvent s’exécuter immédiatement
(lorsque l’on sait que les objets de l’expression sont disponibles) alors que # sert aux
expressions différées (qui doivent être évaluées plus tard dans le cycle de vie). Avec
JSF 1.2, les deux syntaxes étaient unifiées, mais cela impliquait trop de confusions
et beaucoup d’erreurs.
Le Tableau 11.9 énumère toutes les bibliothèques de marqueurs auxquelles a accès
une page utilisant Facelets comme PDL. On y retrouve la bibliothèque fondamen-
tale et celle des fonctions que nous avons présentées dans la section consacrée à
JSTL, les marqueurs Facelets (avec le préfixe ui) et les nouveaux marqueurs fonda-
mentaux, html et composites de JSF. Les autres marqueurs JSTL (formatage, SQL
et XML) ne sont pas reconnus par Facelets.
Intéressons-nous aux composants HTML de JSF permettant de créer des interfaces
web riches. Le Chapitre 12 présentera l’essentiel de la bibliothèque fondamentale
de JSF avec les convertisseurs et les validateurs, mais examinons d’abord le cycle
de vie d’une page JSF.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 361
Cycle de vie
Une page JSF est une arborescence de composants avec un cycle de vie spécifique
qu’il faut bien avoir compris pour savoir à quel moment les composants sont validés
ou quand le modèle est mis à jour. Un clic sur un bouton provoque l’envoi d’une
requête du navigateur vers le serveur et cette requête est traduite en événement qui
peut être traité par l’application sur le serveur. Toutes les données saisies par l’uti-
lisateur passent par une étape de validation avant que le modèle soit mis à jour et
que du code métier soit appelé. JSF se charge alors de vérifier que chaque compo-
sant graphique (composants parent et fils) est correctement rendu par le naviga-
teur. Les différentes phases du cycle de vie d’une page JSF sont représentées par la
Figure 11.6.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
362 Java EE 6 et GlassFish 3
Fin de Fin de
la réponse la réponse
Fin de
la réponse Traitement Traitement Modification
Appel de des valeurs
Réponse des l'application des
événements événements du modèle
1. Restauration de la vue. JSF trouve la vue cible et lui applique les entrées de
l’utilisateur. S’il s’agit de la première visite, JSF crée la vue comme un com-
posant UIViewRoot (racine de l’arborescence de composants, qui constitue une
page particulière). Pour les requêtes suivantes, il récupère l’UIViewRoot précé-
demment sauvegardée pour traiter la requête HTTP courante.
2. Application des valeurs de la requête. Les valeurs fournies avec la requête
(champs de saisie, d’un formulaire, valeurs des cookies ou à partir des en-têtes
HTTP) sont appliquées aux différents composants de la page. Seuls les compo-
sants UI modifient leur état, non les objets métiers qui forment le modèle.
3. Validations. Lorsque tous les composants UI ont reçu leurs valeurs, JSF tra-
verse l’arborescence de composants et demande à chacun d’eux de s’assurer
que la valeur qui leur a été soumise est correcte. Si la conversion et la validation
réussissent pour tous les composants, le cycle de vie passe à la phase suivante.
Sinon il passe à la phase de Rendu de la réponse avec les messages d’erreur de
validation et de conversion appropriés.
4. Modification des valeurs du modèle. Lorsque toutes les valeurs des compo-
sants ont été affectées et validées, les beans gérés qui leur sont associés peuvent
être mis à jour.
5. Appel de l’application. On peut maintenant exécuter la logique métier. Les
actions qui ont été déclenchées seront exécutées sur le bean géré. La navigation
entre en jeu car c’est la valeur qu’elle renvoie qui déterminera la réponse.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 363
L’architecture JSF est conçue pour être indépendante de tout protocole ou langage
à marqueurs particulier et pour écrire des applications pour les clients HTML qui
communiquent via HTTP. Une interface utilisateur pour une page web donnée est
créée en assemblant des composants qui fournissent des fonctionnalités spécifiques
afin d’interagir avec l’utilisateur (labels, cases à cocher, etc.) – JSF met à disposition
un certain nombre de classes composants couvrant la plupart des besoins classiques.
Une page est une arborescence de classes héritant de javax.faces.component.
UIComponent et ayant des propriétés, des méthodes et des événements. La racine de
l’arbre est une instance de UIViewRoot et tous les autres composants respectent une
relation d’héritage. Intéressons-nous à ces composants dans une page web.
Commandes
Les commandes sont les contrôles sur lesquels l’utilisateur peut cliquer pour déclen-
cher une action. Ces composants sont généralement représentés sous forme de bou-
tons ou de liens hypertextes, indiqués par les marqueurs du Tableau 11.10.
Marqueur Description
<h:commandButton> Représente un élément HTML pour un bouton de type submit ou
reset.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
364 Java EE 6 et GlassFish 3
Par défaut, un commandButton est de type submit. Pour utiliser une image comme
bouton, utilisez non pas l’attribut value (qui est le nom du bouton) mais l’attribut
image pour indiquer le chemin d’accès du fichier image que vous souhaitez afficher.
Voici le résultat graphique que produira ce code :
Les boutons et les liens ont tous les deux un attribut action permettant d’appeler
une méthode d’un bean géré. Voici comment invoquer la méthode doNew() du boo-
kController, par exemple :
<h:commandLink action="#{bookController.doNew}">
Create a new book
</h:commandLink>
Entrées
Les entrées sont des composants qui affichent leur valeur courante et permettent à
l’utilisateur de saisir différentes informations textuelles. Il peut s’agir de champs de
saisie, de zones de texte ou de composants pour entrer un mot de passe ou des données
cachées. Leurs marqueurs sont énumérés dans le Tableau 11.11.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 365
Marqueur Description
<h:inputHidden> Représente un élément d’entrée HTML de type caché (non affiché).
<h:inputSecret> Représente un élément d’entrée HTML de type mot de passe. Pour
des raisons de sécurité, tout ce qui a été saisi ne sera pas affiché,
sauf si la propriété redisplay vaut true.
<h:inputText> Représente un élément d’entrée HTML de type texte.
<h:inputTextarea> Représente une zone de texte HTML.
De nombreuses pages web contiennent des formulaires pour que l’utilisateur puisse
saisir des données ou se connecter en fournissant un mot de passe. En outre, les
composants d’entrée utilisent plusieurs attributs permettant de modifier leur longueur,
leur contenu ou leur aspect :
<h:inputHidden value="Hidden data"/>
<h:inputSecret maxlength="8"/>
<h:inputText value="An input text"/>
<h:inputText size="40" value="A longer input text"/>
<h:inputTextarea rows="4" cols="20" value="A text area"/>
Tous les composants ont un attribut value pour fixer leur valeur par défaut. L’attribut
maxLength permet de s’assurer que le texte saisi ne dépasse pas une longueur don-
née et l’attribut size modifie la taille par défaut du composant. Le code précédent
produira donc le résultat suivant :
An input text
A longer input text
A text area
Sorties
Les composants de sortie affichent une valeur qui peut éventuellement avoir été obte-
nue à partir d’un bean géré, une expression valeur ou un texte littéral. L’utilisateur
ne peut pas modifier ce contenu car il n’est qu’en lecture seule. Le Tableau 11.12
énumère les marqueurs de sortie disponibles.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
366 Java EE 6 et GlassFish 3
Marqueur Description
<h:outputLabel> Produit un élément <label> de HTML
<h:outputLink> Produit un élément <a>
<h:outputText> Produit un texte littéral
La plupart des pages web affichent du texte. Pour ce faire, vous pouvez utiliser des
éléments HTML classiques mais, grâce à EL, les marqueurs de sortie de JSF per-
mettent d’afficher le contenu d’une variable liée à un bean géré. Vous pouvez ainsi
afficher du texte avec <h:outputText> et des liens hypertextes avec <h:outputLink>.
Notez que les marqueurs <h:commandLink> et <h:outputLink> sont différents car le
dernier affiche le lien sans invoquer de méthode lorsqu’on clique dessus – il crée
simplement un lien externe ou une ancre.
<h:outputLabel value="#{bookController.book.title}"/> <h:outputText value="A
text"/>
<h:outputLink value="http://www.apress.com/">
A link
</h:outputLink>
Sélections
Les composants de sélection (voir Tableau 11.13) permettent de choisir une ou plu-
sieurs valeurs dans une liste. Graphiquement, ils sont représentés par des cases à
cocher, des boutons radio, des listes ou des combo box.
Marqueur Description
<h:selectBooleanCheckbox> Produit une case à cocher représentant une valeur
booléenne unique. Cette case sera initialement cochée ou
décochée selon la valeur de sa propriété checked.
<h:selectManyCheckbox> Produit une liste de cases à cocher.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 367
Marqueur Description
<h:selectManyListbox> Produit un composant à choix multiples, dans lequel on
peut choisir une ou plusieurs options.
<h:selectManyMenu> Produit un élément <select> HTML.
<h:selectOneListbox> Produit un composant à choix unique, dans lequel on ne
peut choisir qu’une seule option.
<h:selectOneMenu> Produit un composant à choix unique, dans lequel on ne
peut choisir qu’une seule option. N’affiche qu’une option
à la fois.
<h:selectOneRadio> Produit une liste de boutons radio.
Les marqueurs de ce tableau ont une représentation graphique mais ont besoin
d’imbriquer d’autres marqueurs (<f:selectItem> ou <f:selectItems>) pour
contenir les options disponibles. Pour représenter une combo box contenant une
liste de genres littéraires, par exemple, il faut imbriquer un ensemble de marqueurs
<f:selectItem> dans un marqueur <h:selectOneMenu> :
<h:selectOneMenu>
<f:selectItem itemLabel="History" />
<f:selectItem itemLabel="Biography"/>
<f:selectItem itemLabel="Literature"/>
<f:selectItem itemLabel="Comics"/>
<f:selectItem itemLabel="Child"/>
<f:selectItem itemLabel="Scifi"/>
</h:selectOneMenu>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
368 Java EE 6 et GlassFish 3
History
Biography
Literature
h:selectManyListbox
Comics
Child
Scifi
h:selectManyMenu History
History
Biography
Literature
h:selectOneListbox
Comics
Child
Scifi
h:selectOneMenu History
Figure 11.7
Les différentes représentations graphiques des listes.
Graphiques
Il n’existe qu’un seul composant pour afficher les images : <h:graphicImage>. Ce
marqueur utilise un élément HTML <img> pour afficher une image que les utili-
sateurs n’auront pas le droit de manipuler. Ses différents attributs permettent de
modifier la taille de l’image, de l’utiliser comme image cliquable, etc. Une image
peut être liée à une propriété d’un bean géré et provenir d’un fichier sur le système ou
d’une base de données. Le code suivant, par exemple, affiche une image en modifiant
sa taille :
<h:graphicImage value="book.gif" height="200" width="320"/>
Grilles et tableaux
Les données doivent très souvent être affichées sous forme de tableau. JSF four-
nit donc le marqueur <h:dataTable> permettant de parcourir une liste d’éléments
afin de produire un tableau (reportez-vous au code de la page listBooks.xhtml qui
apparaît dans le Listing 10.7 du chapitre précédent). Les tableaux permettent éga-
lement de créer une interface utilisateur "en grille". Dans ce cas, vous pouvez utili-
ser les marqueurs <h:panelGrid> et <h:panelGroup> pour disposer les composants
(voir Tableau 11.14).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 369
Marqueur Description
<h:dataTable> Représente un ensemble de données qui seront affichées dans un élément
<table> de HTML.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
370 Java EE 6 et GlassFish 3
Les deux grilles que nous venons de décrire auront les représentations graphiques
suivantes. La première n’aura ni en-tête ni pied ; la seconde aura les deux :
Header
One Two Three
Four Five Six
Footer
Messages d’erreur
Les applications peuvent parfois lancer des exceptions en réponse à des données mal
formatées ou pour certaines raisons techniques. Dans ce cas, il ne faut afficher dans
l’interface utilisateur que ce qui est nécessaire afin d’attirer son attention et pour qu’il
puisse corriger le problème. Le mécanisme de gestion des messages d’erreur passe
par l’utilisation des marqueurs <h:message> et <h:messages> (voir Tableau 11.15).
<h:message> est lié à un composant précis, tandis que <h:messages> permet de défi-
nir un message global pour tous les composants de la page.
Marqueur Description
<h:message> Affiche un seul message d’erreur.
<h:messages> Affiche tous les messages d’erreur en attente.
Les messages peuvent avoir des importances différentes (INFO, WARN, ERROR et FATAL)
correspondant chacune à un style CSS (respectivement infoStyle, warnStyle,
errorStyle et fatalStyle) : chaque type de message sera donc affiché dans un
style différent. Le code suivant, par exemple, affichera tous les messages en rouge :
<h:messages style="color:red"/>
<h:form>
Enter a title:
<h:inputText value="#{bookController.title}" required="true"/>
<h:commandButton action="#{bookController.save}" value="Save"/>
</h:form>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 371
Cette page affichera un champ de saisie lié à une propriété d’un bean géré. Ici, cette
propriété est obligatoire : un message d’erreur s’affichera si l’utilisateur clique sur
le bouton Save alors que ce champ est vide.
Informations complémentaires
Les marqueurs énumérés dans le Tableau 11.16 n’ont pas de représentation gra-
phique mais possèdent un équivalent HTML. Bien que les marqueurs natifs de
HTML puissent être utilisés directement sans problème, les marqueurs JSF ont
des attributs supplémentaires qui facilitent le développement. Vous pouvez, par
exemple, ajouter une bibliothèque JavaScript à l’aide du marqueur HTML standard
<script type="text/JavaScript">, mais le marqueur <h:outputScript> de JSF
permet d’utiliser la nouvelle gestion des ressources, comme nous le verrons dans la
section "Gestion des ressources".
Marqueur Description
<h:body> Produit un élément <body> HTML.
<h:head> Produit un élément <head> HTML.
<h:form> Produit un élément <form> HTML.
<h:outputScript> Produit un élément <script> HTML.
<h:outputStylesheet> Produit un élément <link> HTML.
Templates
Une application web typique contient plusieurs pages partageant toutes le même
aspect, un en-tête, un pied de page, un menu, etc. Facelets permet de définir une dis-
position de page dans un fichier template qui pourra être utilisé par toutes les pages :
ce fichier définit les zones (avec le marqueur <ui:insert>) dont le contenu sera
remplacé grâce aux marqueurs <ui:component>, <ui:composition>, <ui:fragment>
ou <ui:decorate> des pages clientes. Le Tableau 11.17 énumère les marqueurs de
templates.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
372 Java EE 6 et GlassFish 3
Marqueur Description
<ui:composition> Définit une composition utilisant éventuellement un template. Le
même template peut être utilisé par plusieurs compositions.
<ui:define> Définit un contenu qui sera inséré dans l’élément <ui:insert>
correspondant du template.
<ui:decorate> Permet de décorer le contenu d’une page.
<ui:fragment> Ajoute un fragment à une page.
<ui:insert> Définit un point d’insertion dans un template dans lequel on pourra
ensuite insérer un contenu placé dans un marqueur <ui:define>.
À titre d’exemple, réutilisons la page qui affichait un formulaire pour créer un livre
(voir Figure 11.1). Nous pourrions considérer que le titre est l’en-tête de la page
et que le texte "Apress - Beginning Java EE 6" est le pied de page. Le contenu du
template layout.xml ressemblerait donc au code du Listing 11.11.
<hr/>
<i>APress - Beginning Java EE 6</i>
</body>
</html>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 373
<ui:insert> pour insérer un attribut title dans les marqueurs HTML <title> et
<h1>. Le corps de la page sera inséré dans l’attribut content.
<ui:composition template="layout.xhtml">
<ui:define name="content">
<table border="0">
<tr>
<td>ISBN :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Title :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Price :</td>
<td><input type="text"/></td>
</tr>
<tr>
<td>Description :</td>
<td><textarea name="textarea" cols="20" rows="5">
</textarea></td>
</tr>
<tr>
<td>Number of pages :</td>
<td><input type="text"/></td>
</tr>
<tr>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
374 Java EE 6 et GlassFish 3
<td>Illustrations :</td>
<td><input type="checkbox"/></td>
</tr>
</table>
<input name="" type="submit" value="Create"/>
</ui:define>
</ui:composition>
</html>
Figure 11.8
Create a new book
La page newBook.html
avec le template
ISBN :
layout.xhtml.
Tiltle :
Price :
Description :
Number of pages :
Illustrations :
Create a book
La plupart des composants ont besoin de ressources externes pour s’afficher correc-
tement : <h:graphicImage> a besoin d’une image, <h:commandButton> peut égale-
ment afficher une image pour représenter le bouton, <h:outputScript> référence
un fichier JavaScript et les composants peuvent également appliquer des styles CSS.
Avec JSF, une ressource est un élément statique qui peut être transmis aux éléments
afin d’être affiché (images) ou traité (JavaScript, CSS) par le navigateur.
Les versions précédentes de JSF ne fournissaient pas de mécanisme particulier pour
servir les ressources : lorsque l’on voulait en fournir une, il fallait la placer dans
le répertoire WEB-INF pour que le navigateur du client puisse y accéder. Pour la
modifier, il fallait remplacer le fichier et, pour gérer les ressources localisées (une
image avec un texte anglais et une autre avec un texte français, par exemple),
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 375
ou :
META-INF/resources/<identifiant_ressource>
Tous les éléments entre crochets sont facultatifs. La locale est le code du langage,
suivi éventuellement d’un code de pays (en, en_US, pt, pt_BR). Comme l’indique
cette syntaxe, vous pouvez ajouter un numéro de version à la bibliothèque ou à la
ressource elle-même. Voici quelques exemples :
book.gif
en/book.gif
en_us/book.gif
en/myLibrary/book.gif
myLibrary/book.gif
myLibrary/1_0/book.gif
myLibrary/1_0/book.gif/2_3.gif
Vous pouvez ensuite utiliser une ressource – l’image book.gif, par exemple – direc-
tement dans un composant <h:graphicImage> ou en précisant le nom de la biblio-
thèque (library="myLibrary"). La ressource correspondant à la locale du client
sera automatiquement choisie.
<h:graphicImage value="book.gif" />
<h:graphicImage value="book.gif" library="myLibrary" />
<h:graphicImage value="#{resource[’book.gif’]}" />
<h:graphicImage value="#{resource[’myLibrary:book.gif’]}" />
Composants composites
Tous les composants que nous venons de présenter font partie de JSF et sont dis-
ponibles dans toutes les implémentations qui respectent la spécification. En outre,
comme elle repose sur des composants réutilisables, JSF fournit le moyen de créer
et d’intégrer aisément dans les applications ses propres composants ou des compo-
sants provenant de tierces parties.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
376 Java EE 6 et GlassFish 3
Nous avons déjà mentionné le fait que tous les composants héritaient, directement
ou indirectement, de la classe javax.faces.component.UIComponent. Avant JSF 2.0,
pour créer son propre composant il fallait étendre la classe component la plus proche
du nouveau composant (UICommand, UIGraphic, UIOutput, etc.), la déclarer dans le
fichier faces-config.xml et fournir un descripteur de marqueur et une représenta-
tion. Ces étapes étaient complexes : d’autres frameworks comme Facelets ont alors
montré qu’il était possible de créer plus simplement des composants puissants. Le
but des composants composites est de permettre aux développeurs de créer de vrais
composants graphiques réutilisables sans avoir besoin d’écrire du code Java ou de
mettre en place une configuration XML.
Cette nouvelle approche consiste à créer une page XHTML contenant les compo-
sants, puis de l’utiliser comme composant dans d’autres pages. Cette page XHTML
est alors vue comme un véritable composant supportant des validateurs, des conver-
tisseurs et des écouteurs. Les composants composites peuvent contenir n’importe
quel marqueur valide et utiliser des templates. Ils sont traités comme des ressources
et doivent donc se trouver dans les nouveaux répertoires standard des ressources.
Le Tableau 11.18 énumère les marqueurs permettant de les créer et de les définir.
Marqueur Description
<composite:interface> Déclare le contrat d’un composant.
<composite:implementation> Définit l’implémentation d’un composant.
<composite:attribute> Déclare un attribut pouvant être fourni à
une instance du composant. Un marqueur
<composite:interface> peut en contenir
plusieurs.
<composite:facet> Déclare que ce composant supporte une facet.
<composite:insertFacet> Utilisé dans un marqueur
<composite:implementation>. La facet
insérée sera représentée dans le composant.
<composite:insertChildren> Utilisé dans un marqueur
<composite:implementation>. Tous les
composants fils ou les templates seront insérés
dans la représentation de ce composant.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 377
Marqueur Description
<composite:valueHolder> Le composant dont le contrat est déclaré par
le marqueur <composite:interface> dans
lequel est imbriqué cet élément devra exposer
une implémentation de ValueHolder.
<composite:editableValueHolder> Le composant dont le contrat est déclaré par
le marqueur <composite:interface> dans
lequel est imbriqué cet élément devra exposer
une implémentation d’editableValueHolder.
<composite:actionSource> Le composant dont le contrat est déclaré
par le marqueur <composite:interface>
dans lequel est imbriqué cet élément devra
exposer une implémentation de l’interface
actionSource.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
378 Java EE 6 et GlassFish 3
Price : Price :
Description : Description :
Create a cd
APress Beginning Java EE 6
Figure 11.9
Deux formulaires : l’un pour créer un CD, l’autre pour créer un livre.
<composite:interface>
<composite:attribute name="item" required="true"/>
<composite:attribute name="style" required="false"/>
</composite:interface>
<composite:implementation>
<tr style="#{compositeComponent.attrs.style}">
<td>Title :</td>
<td>
<h:inputText value="#{compositeComponent.attrs.item.title}"/>
</td>
</tr>
<tr style="#{compositeComponent.attrs.style}">
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 379
<td>Price :</td>
<td>
<h:inputText value="#{compositeComponent.attrs.item.price}"/>
</td>
</tr>
<tr style="#{compositeComponent.attrs.style}">
<td>Description :</td>
<td>
<h:inputTextarea
value="#{compositeComponent.attrs.item.description}"
cols="20" rows="5"/>
</td>
</tr>
</composite:implementation>
</html>
Ce composant déclare une interface avec deux attributs : item représente l’entité
Item (et les sous-classes Book et CD) et style est une feuille de style CSS utili-
sée pour la présentation. Ces attributs sont ensuite utilisés par l’implémentation du
composant à l’aide de la syntaxe suivante :
#{compositeComponent.attrs.style}
Puis on appelle le composant newItem (le nom de la page) en lui passant les para-
mètres qu’il attend : item désigne l’entité Item et style est le paramètre facultatif
désignant une feuille de style CSS :
<ago:newItem item="#{itemController.book}" style="myCssStyle"/>
<ago:newItem item="#{itemController.cd}"/>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
380 Java EE 6 et GlassFish 3
champs de saisie pour l’ISBN et le nombre de pages, ainsi qu’une case à cocher pour
indiquer si le livre contient, ou non, des illustrations.
<h:head>
<title>Creates a new book</title>
</h:head>
<h:body>
<h1>Create a new book</h1>
<hr/>
<h:form>
<table border="0">
<ago:newItem item="#{itemController.book}"/>
<tr>
<td><h:outputLabel value="ISBN : "/></td>
<td>
<h:inputText value="#{itemController.book.isbn}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Number of pages : "/></td>
<td>
<h:inputText value="#{itemController.book.nbOfPage}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Illustrations : "/></td>
<td>
<h:selectBooleanCheckbox
value="#{itemController.book.illustrations}"/>
</td>
</tr>
</table>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 381
</h:form>
<hr/>
<i>APress - Beginning Java EE 6</i>
</h:body>
</html>
Objets implicites
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
382 Java EE 6 et GlassFish 3
Tous ces objets implicites sont de vrais objets avec des interfaces : vous pouvez
accéder à leurs attributs avec EL (consultez la spécification). #{view. Locale}, par
exemple, permet d’obtenir la locale de la vue courante (en_US, pt_PT, etc.). Si vous
stockez un livre dans la portée de la session, par exemple, vous pouvez y accéder par
#{sessionScope.book}. Vous pouvez même utiliser un algorithme plus élaboré pour
afficher tous les en-têtes HTTP et leurs valeurs :
<h3>headerValues</h3>
<c:forEach var="parameter" items="#{headerValues}">
<h:outputText value="#{parameter.key}"/> =
<c:forEach var="value" items="#{parameter.value}">
<h:outputText value="#{value}" escape="false"/>
<br/>
</c:forEach>
</c:forEach>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 11 Pages et composants 383
Résumé
Ce chapitre a présenté les différents moyens de créer des pages web statiques à
l’aide de langages comme HTML, XHTML ou CSS et dynamiques avec JavaScript
ou les technologies côté serveur. Pour créer des interfaces web dynamiques avec
Java EE 6, vous avez le choix entre plusieurs spécifications. JSP 2.2, EL 2.2 et
JSTL 1.2 ont été créées en pensant aux servlets, leurs pages sont formées d’infor-
mations HTML et de code Java compilés dans une servlet qui renvoie une réponse
à une requête donnée.
Bien que l’on puisse se servir de JSP comme PDL (Presentation Description Lan-
guage) pour JSF, il est préférable d’utiliser Facelets afin de disposer de la puissance
de l’architecture des composants JSF et de son cycle de vie élaboré. JSF fournit un
ensemble de widgets standard (boutons, liens, cases à cocher, etc.) et un nouveau
modèle pour créer ses propres composants (composants composites). JSF 2.0 dis-
pose également d’un nouveau mécanisme de gestion des ressources permettant de
gérer de façon simple les locales et les versions des ressources externes.
JSF 2.0 utilise une architecture de composants UI sophistiquée ; ses composants
peuvent être convertis et validés, et interagir avec les beans gérés, qui sont présentés
dans le prochain chapitre.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
12
Traitement et navigation
Au chapitre précédent, nous avons vu comment créer des pages web avec différentes
technologies (HTML, JSP, JSTL, etc.) en insistant sur le fait que JSF est la spécifi-
cation conseillée pour écrire des applications web modernes avec Java EE. Cepen-
dant, créer des pages contenant des composants graphiques ne suffit pas : ces pages
doivent interagir avec un backend (un processus en arrière-plan), il faut pouvoir
naviguer entre les pages et valider et convertir les données. JSF est une spécifica-
tion très riche : les beans gérés permettent d’invoquer la couche métier, de naviguer
dans votre application, et, grâce à un ensemble de classes, vous pouvez convertir les
valeurs des composants ou les valider pour qu’ils correspondent aux règles métiers.
Grâce aux annotations, le développement de convertisseurs et de validateurs person-
nalisés est désormais chose facile.
JSF 2.0 apporte la simplicité et la richesse aux interfaces utilisateurs dynamiques.
Il reconnaît nativement Ajax en fournissant une bibliothèque JavaScript permettant
d’effectuer des appels asynchrones vers le serveur et de rafraîchir une page par parties.
La création d’interfaces utilisateurs, le contrôle de la navigation dans l’application
et les appels synchrones ou asynchrones de la logique métier sont possibles parce
que JSF utilise le modèle de conception MVC (Modèle-Vue-Contrôleur). Chaque
partie est donc isolée des autres, ce qui permet de modifier l’interface utilisateur
sans conséquence sur la logique métier et vice versa.
Le modèle MVC
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
386 Java EE 6 et GlassFish 3
mélange pas bien avec la seconde : leur mélange produit des applications plus diffi-
ciles à maintenir et qui supportent moins bien la montée en charge. Dans la section
"JavaServer Pages" du chapitre précédent, nous avons vu une page JSP qui contenait
à la fois du code Java et des instructions SQL : bien que ce soit techniquement cor-
rect, imaginez la difficulté de maintenir une telle page... Elle mélange deux types de
développement différents (celui de concepteur graphique et celui de programmeur
métier) et pourrait finir par utiliser bien plus d’API encore (accès aux bases de don-
nées, appels d’EJB, etc.), par gérer les exceptions ou par effectuer des traitements
métiers complexes. Avec MVC, l’application utilise un couplage faible, ce qui faci-
lite la modification de son aspect visuel ou des règles métiers sous-jacentes sans
pour autant affecter l’autre composante.
Comme le montre la Figure 12.1, la partie "modèle" de MVC représente les données
de l’application ; la "vue" correspond à l’interface utilisateur et le "contrôleur" gère
la communication entre les deux.
Figure 12.1 Client
Server
Requête HTTP
Le modèle Contrôleur
de conception MVC. (FacesServlet)
Navigateur Réponse HTTP
crée et gère
accède
Vue
(pages XHTML)
Le modèle est représenté par le contenu, qui est souvent stocké dans une base de
données et affiché dans la vue ; il ne se soucie pas de l’aspect que verra l’utilisateur.
Avec JSF, il peut être formé de backing beans, d’appels EJB, d’entités JPA, etc.
La vue JSF est la véritable page XHTML (XHTML est réservé aux interfaces web,
mais il pourrait s’agir d’un autre type de vue, comme WML pour les dispositifs
mobiles). Comme au chapitre précédent, une vue fournit une représentation gra-
phique d’un modèle et un modèle peut avoir plusieurs vues pour afficher un livre
sous forme de formulaire ou de liste, par exemple.
Lorsqu’un utilisateur manipule une vue, celle-ci informe un contrôleur des modifi-
cations souhaitées. Ce contrôleur se charge alors de rassembler, convertir et valider
les données, appelle la logique métier puis produit le contenu en XHTML. Avec JSF,
le contrôleur est un objet FacesServlet.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 387
FacesServlet
Figure 12.2
Cycle de vie
Interactions de 3. Traitement en 6 étapes
FacesServlet.
2. Passe le contrôle au cycle de vie FacesContext
1. Crée un FaceContext
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
388 Java EE 6 et GlassFish 3
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
</web-app>
Paramètre Description
javax.faces.CONFIG_FILES Définit une liste de chemins de ressources liées au
contexte dans laquelle JSF recherchera les ressources.
javax.faces.DEFAULT_SUFFIX Permet de définir une liste de suffixes possibles pour les
pages ayant du contenu JSF (.xhtml, par exemple).
javax.faces.LIFECYCLE_ID Identifie l’instance LifeCycle utilisée pour traiter les
requêtes JSF.
javax.faces.STATE_SAVING_ Définit l’emplacement de sauvegarde de l’état. Les
METHOD valeurs possibles sont server (valeur par défaut qui
indique que l’état sera généralement sauvegardé dans un
objet HttpSession) et client (l’état sera sauvegardé
dans un champ caché lors du prochain envoi de
formulaire).
javax.faces.PROJECT_STAGE Décrit l’étape dans laquelle se trouve cette application
JSF dans le cycle de vie (Development, UnitTest,
SystemTest ou Production). Cette information peut être
utilisée par une implémentation de JSF pour améliorer les
performances lors de la phase de production en utilisant
un cache pour les ressources, par exemple.
javax.faces.DISABLE_FACELET_ Désactive Facelets comme langage de déclaration de
JSF_VIEWHANDLER page (PDL).
javax.faces.LIBRARIES Liste des chemins qui seront considérés comme une
bibliothèque de marqueurs Facelets.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 389
FacesContext
Méthode Description
addMessage Ajoute un message d’erreur.
getApplication Renvoie l’instance Application associée à cette application
web.
getAttributes Renvoie un objet Map représentant les attributs associés à
l’instance FacesContext.
getCurrentInstance Renvoie l’instance FacesContext pour la requête traitée par le
thread courant.
getMaximumSeverity Renvoie le niveau d’importance maximal pour tout
FacesMessage mis en file d’attente.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
390 Java EE 6 et GlassFish 3
Configuration de Faces
La FacesServlet est interne aux implémentations de JSF ; bien que vous n’ayez
pas accès à son code, vous pouvez la configurer avec des métadonnées. Vous savez
désormais qu’il existe deux moyens d’indiquer des métadonnées avec Java EE 6 :
les annotations et les descripteurs de déploiement XML (/WEB-INF/faces-config.
xml). Avant JSF 2.0, le seul choix possible était XML mais, désormais, les beans
gérés, les convertisseurs, les moteurs de rendu et les validateurs pouvant utiliser les
annotations, les fichiers de configuration XML sont devenus facultatifs.
Nous conseillons l’emploi des annotations mais, pour montrer à quoi ressemble
un fichier faces-config.xml, le Listing 12.2 définit une locale et un ensemble de
messages pour l’internationalisation et certaines règles de navigation. Nous verrons
ensuite comment naviguer avec et sans faces-config.xml.
<application>
<locale-config>
<default-locale>fr</default-locale>
</locale-config>
<resource-bundle>
<base-name>messages</base-name>
<var>msg</var>
</resource-bundle>
</application>
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>doCreateBook-success</from-outcome>
<to-view-id>/listBooks.htm</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
Beans gérés
Comme on l’a indiqué plus haut, le modèle MVC encourage la séparation entre le
modèle, la vue et le contrôleur. Avec Java EE, les pages JSF forment la vue et la
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 391
FacesServlet est le contrôleur. Les beans gérés, quant à eux, sont une passerelle
vers le modèle.
Les beans gérés sont des classes Java annotées. Ils constituent le cœur des applications
web car ils exécutent la logique métier (ou la délèguent aux EJB, par exemple), gèrent
la navigation entre les pages et stockent les données. Une application JSF typique
contient un ou plusieurs beans gérés qui peuvent être partagés par plusieurs pages.
Les données sont stockées dans les attributs du bean géré, qui, en ce cas, est égale-
ment appelé "backing bean". Un backing bean définit les données auxquelles est lié
un composant de l’interface utilisateur (la cible d’un formulaire, par exemple). Pour
établir cette liaison, on utilise EL, le langage d’expressions.
Écrire un bean géré est aussi simple qu’écrire un EJB ou une entité JPA puisqu’il
s’agit simplement de créer une classe Java annotée par @ManagedBean (voir Lis-
ting 12.3) – il n’y a nul besoin de créer des entrées dans faces-config.xml, de créer
des classes utilitaires ou d’hériter d’une classe quelconque : JSF 2.0 utilisant égale-
ment le mécanisme de configuration par exception, une seule annotation suffit pour
utiliser tous les comportements par défaut et pour déployer une application web
utilisant un bean géré.
Les beans gérés sont des classes Java prises en charge par la FacesServlet. Les com-
posants de l’interface utilisateur sont liés aux propriétés du bean (backing bean) et
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
392 Java EE 6 et GlassFish 3
peuvent invoquer des méthodes d’action. Un bean géré doit respecter les contraintes
suivantes :
■■ La classe doit être annotée par @javax.faces.model.ManagedBean ou son équi-
valent dans le descripteur de déploiement XML faces-config.xml.
■■ La classe doit avoir une portée (qui vaut par défaut @RequestScoped).
■■ La classe doit être publique et non finale ni abstraite.
■■ La classe doit fournir un constructeur public sans paramètre qui sera utilisé par
le conteneur pour créer les instances.
■■ La classe ne doit pas définir de méthode finalize().
■■ Pour être liés à un composant, les attributs doivent avoir des getters et des setters
publics.
Bien qu’un bean géré puisse être un simple POJO annoté, sa configuration peut être
personnalisée grâce aux éléments de @ManagedBean et @ManagedProperty (ou leurs
équivalents XML).
@ManagedBean
La présence de l’annotation @javax.faces.model.ManagedBean sur une classe l’en-
registre automatiquement comme un bean géré. La Figure 12.4 présente l’API de
cette annotation, dont tous les éléments sont facultatifs.
L’élément name indique le nom du bean géré (par défaut, ce nom est celui de la
classe commençant par une minuscule). Si l’élément eager vaut true, le bean géré
est instancié dès le démarrage de l’application.
Les composants de l’interface utilisateur étant liés aux propriétés d’un bean géré,
changer son nom par défaut a des répercussions sur la façon d’appeler une propriété
ou une méthode. Le code du Listing 12.5, par exemple, renomme le bean géré Book–
Controller en myManagedBean.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 393
Pour invoquer les attributs ou les méthodes de ce bean, vous devez donc utiliser son
nouveau nom :
<h:outputText value="#{myManagedBean.book.isbn}"/>
<h:form>
<h:commandLink action="#{myManagedBean.doCreateBook}">
Create a new book
</h:commandLink>
</h:form>
Portées
Les objets créés dans le cadre d’un bean géré ont une certaine durée de vie et peu-
vent ou non être accessibles aux composants de l’interface utilisateur ou aux objets
de l’application. Cette durée de vie et cette accessibilité sont regroupées dans la
notion de portée. Cinq annotations permettent de définir la portée d’un bean géré :
■■ @ApplicationScoped. Il s’agit de l’annotation la moins restrictive, avec la plus
longue durée de vie. Les objets créés sont disponibles dans tous les cycles requête/
réponse de tous les clients utilisant l’application tant que celle-ci est active. Ces
objets peuvent être appelés de façon concurrente et doivent donc être thread-
safe (c’est-à-dire utiliser le mot-clé synchronized). Les objets ayant cette portée
peuvent utiliser d’autres objets sans portée ou avec une portée d’application.
■■ @SessionScoped. Ces objets sont disponibles pour tous les cycles requête/réponse
de la session du client. Leur état persiste entre les requêtes et dure jusqu’à la fin
de la session. Ils peuvent utiliser d’autres objets sans portée, avec une portée de
session ou d’application.
■■ @ViewScoped. Ces objets sont disponibles dans une vue donnée jusqu’à sa modi-
fication. Leur état persiste jusqu’à ce que l’utilisateur navigue vers une autre vue,
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
394 Java EE 6 et GlassFish 3
auquel cas il est supprimé. Ils peuvent utiliser d’autres objets sans portée, avec
une portée de vue, de session ou d’application.
■■ @RequestScoped. Il s’agit de la portée par défaut. Ces objets sont disponibles
du début d’une requête jusqu’au moment où la réponse est envoyée au client.
Un client pouvant exécuter plusieurs requêtes tout en restant sur la même vue,
la durée de @ViewScoped est supérieure à celle de @RequestScoped. Les objets
ayant cette portée peuvent utiliser d’autres objets sans portée, avec une portée de
requête, de vue, de session ou d’application.
■■ @NoneScoped. Les beans gérés ayant cette portée ne sont visibles dans aucune
page JSF ; ils définissent des objets utilisés par d’autres beans gérés de l’appli-
cation. Ils peuvent utiliser d’autres objets avec la même portée.
La portée des beans gérés doit être judicieusement choisie : vous ne devez leur
donner que la portée dont ils ont besoin. Des beans ayant une portée trop grande (@
ApplicationScoped, par exemple) augmentent l’utilisation mémoire et l’utilisation
du disque pour leur persistance éventuelle. Il n’y a aucune raison de donner une
portée d’application à un objet qui n’est utilisé que dans un seul composant. Inverse-
ment, un objet ayant une portée trop restreinte ne sera pas disponible dans certaines
parties de l’application.
Le code du Listing 12.6 définit un bean géré avec une portée d’application. Il sera
instancié dès le lancement de l’application (eager = true) et initialise l’attribut
defaultBook dès qu’il est construit (@PostConstruct). Il pourrait donc être le bean
idéal pour initialiser des parties de l’application web ou pour être référencé par des
propriétés d’autres beans gérés.
Listing 12.6 : Bean géré avec une portée d’application et une instanciation eager
@ManagedBean(eager = true)
@ApplicationScoped
public class InitController {
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 395
@ManagedProperty
Dans un bean géré, vous pouvez demander au système d’injecter une valeur dans
une propriété (un attribut avec des getters et/ou des setters) en utilisant le fichier
faces-config.xml ou l’annotation @javax.faces.model.ManagedProperty, dont
l’attribut value peut recevoir une chaîne ou une expression EL. Le Listing 12.7
montre quelques exemples d’initialisations.
@ManagedProperty(value = "#{initController.defaultBook}")
private Book book;
@ManagedProperty(value = "999")
private Integer aPrice;
Dans le Listing 12.7, les propriétés aTitle et aPrice sont initialisées avec une valeur
de type String. L’attribut aTitle, de type String, sera initialisé avec "this is a title"
et l’attribut aPrice, qui est un Integer, sera initialisé avec le nombre 999 (bien
que "999" soit une chaîne, celle-ci sera convertie en Integer). Les propriétés étant
évaluées lors de l’exécution (généralement lorsqu’une vue est affichée), celles qui
font référence à d’autres beans gérés peuvent aussi être initialisées. Ici, par exemple,
book est initialisé par une expression utilisant la propriété defaultBook du bean géré
initController (#{initController.defaultBook}) que nous avons présenté plus
haut. Le Listing 12.6 montre que defaultBook est un attribut de type Book initialisé
par le bean InitController : lorsque BookController est initialisé, l’implémen-
tation JSF injectera donc cet attribut. Il est généralement conseillé d’initialiser les
littéraux dans le fichier faces-config.xml et d’utiliser les annotations pour les réfé-
rences croisées entre les beans gérés (en utilisant EL).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
396 Java EE 6 et GlassFish 3
beans gérés (voir Figure 12.3) est totalement différent : en fait, il est identique à
celui des beans de session sans état.
Appel de méthode
Les beans gérés qui s’exécutent dans un conteneur de servlet peuvent utiliser les
annotations @PostConstruct et @PreDestroy. Après avoir créé une instance de bean
géré, le conteneur appelle la méthode de rappel @PostConstruct s’il y en a une.
Puis le bean est lié à une portée et répond à toutes les requêtes de tous les utilisa-
teurs. Avant de supprimer le bean, le conteneur appelle la méthode @PreDestroy.
Ces méthodes permettent donc d’initialiser les attributs ou de créer et libérer les
ressources externes.
Navigation
Les applications web sont formées de plusieurs pages entre lesquelles vous devez
naviguer. Selon les cas, il peut exister différents niveaux de navigation avec des flux
de pages plus ou moins élaborés. JSF dispose de plusieurs options de navigation et
vous permet de contrôler le flux page par page ou pour toute l’application.
Les composants <h:commandButton> et <h:commandLink> permettent de passer sim-
plement d’une page à une autre en cliquant sur un bouton ou sur un lien sans effec-
tuer aucun traitement. Il suffit d’initialiser leur attribut action avec le nom de la
page vers laquelle vous voulez vous rendre :
<h:commandButton value="Create" action="listBooks.xhtml"/>
Cependant, la plupart du temps, ceci ne suffira pas car vous aurez besoin d’accéder
à une couche métier ou à une base de données pour récupérer ou traiter des données.
En ce cas, vous aurez besoin d’un bean géré. Dans la section "Récapitulatif" du
Chapitre 10, une première page (newBook.xhtml) affichait un formulaire permettant
de créer un livre. Lorsque l’on cliquait sur le bouton Create, le livre était créé puis
le bean géré passait à la page listBooks.xhtml, qui affichait tous les livres. Cette
page contenait un lien Create a new book permettant de revenir à la page précédente
(voir Figure 12.4).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 397
newBook.xhtml listBooks.xhtml
Number of pages
Illustrations
Create
Figure 12.4
Navigation entre newBook.xhtml et listBooks.xhtml.
Le flux des pages est simple, mais ces deux pages ont pourtant besoin d’un bean
géré (BookController) pour traiter la logique métier et la navigation. Elles utilisent
les composants bouton et lien pour naviguer et interagir avec ce bean.
La page newBook.xhtml utilise un bouton pour appeler la méthode doCreateBook()
du bean géré :
<h:commandButton value="Create"
action="#{bookController.doCreateBook}"/>
Les composants bouton et lien n’appellent pas directement la page vers laquelle ils
doivent se rendre : ils appellent des méthodes du bean géré qui prennent en charge
cette navigation et laissent le code décider de la page qui sera chargée ensuite. La
navigation utilise un ensemble de règles qui définissent tous les chemins de naviga-
tion possibles de l’application. Dans le Listing 12.8, le code du bean géré utilise la
forme la plus simple de ces règles de navigation : chaque méthode définit la page
vers laquelle elle doit aller.
@EJB
private BookEJB bookEJB;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
398 Java EE 6 et GlassFish 3
Avec JSF, le flux de navigation peut être défini en externe via faces-config.xml, à
l’aide d’éléments <navigation-rule> qui identifient la page de départ, une condi-
tion et la page vers laquelle naviguer lorsque la condition sera vérifiée. Celle-ci
utilise un nom logique au lieu du nom physique de la page. Comme le montre le
Listing 12.9, le code précédent aurait pu utiliser, par exemple, le nom success.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 399
return "success";
}
<navigation-rule>
<from-view-id>newBook.xhtml</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>listBooks.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-view-id>listBooks.xhtml</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>newBook.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
La navigation pouvant avoir lieu directement dans les beans gérés ou au moyen de
faces-config.xml, quelle solution utiliser plutôt qu’une autre ? La première moti-
vation pour renvoyer directement le nom de la page dans les beans gérés est la
simplicité : le code Java est explicite et il n’y a pas besoin de passer par un fichier
XML supplémentaire. Si, en revanche, le flux des pages d’une application web est
complexe, il peut être judicieux de le décrire à un seul endroit afin que les modifica-
tions soient centralisées au lieu d’être dispersées dans plusieurs pages. Vous pouvez
également mélanger ces deux approches et effectuer une partie de la navigation dans
vos beans et une autre dans le fichier faces-config.xml.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
400 Java EE 6 et GlassFish 3
Il existe un cas où une configuration XML est très utile : c’est lorsque plusieurs pages
contiennent des liens globaux (lorsque, par exemple, la connexion ou la déconnexion
peuvent être appelées à partir de toutes les pages de l’application) car il serait assez
lourd de devoir les définir dans chaque page. Avec XML, vous pouvez définir des
règles de navigation globales (ce qui n’est pas possible avec les beans gérés) :
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>logout</from-outcome>
<to-view-id>logout.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
Si une action s’applique à toutes les pages d’une application, vous pouvez utiliser un
élément <navigation- rule> sans <from-view-id> ou utiliser un joker (*). Le code
précédent indique que, quelle que soit la page où il se trouve, l’utilisateur sera dirigé
vers la page logout.xhtml si la méthode du bean géré renvoie le nom logique logout.
Les exemples précédents ont montré une navigation simple où une page n’avait
qu’une seule règle de navigation et une seule page destination. Ce n’est pas un cas
si fréquent : les utilisateurs peuvent généralement être redirigés vers des pages dif-
férentes, en fonction de certaines conditions. Cette navigation, là encore, peut être
mise en place dans les beans gérés et dans le fichier faces-config.xml. Le code sui-
vant utilise une instruction switch pour rediriger l’utilisateur vers trois pages pos-
sibles. Si l’on renvoie la valeur null, l’utilisateur reviendra sur la page sur laquelle
il se trouve déjà.
public String doNewBookForm() {
switch (value) {
case 1: return "page1.xhtml"; break;
case 2: return "page2.xhtml"; break;
case 3: return "page3.xhtml"; break;
default: return null; break;
}
}
Les beans gérés traitent la logique métier, appellent les EJB, utilisent les bases de
données, etc. Parfois, cependant, un problème peut survenir et, en ce cas, l’uti-
lisateur doit en être informé par un message qui peut être un message d’erreur
de l’application (concernant la logique métier ou la connexion à la base ou au
réseau) ou un message d’erreur de saisie (un ISBN incorrect ou un champ vide, par
exemple). Les erreurs d’application peuvent produire une page particulière demandant
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 401
à l’utilisateur de réessayer dans un moment, par exemple, alors que les erreurs de
saisie peuvent s’afficher dans la même page avec un texte décrivant l’erreur. On peut
également utiliser des messages pour informer l’utilisateur qu’un livre a été correc-
tement ajouté à la base de données.
Au chapitre précédent, nous avons utilisé des marqueurs pour afficher des messages
sur les pages (<h:message> et <h:messages>). Pour produire ces messages, JSF vous
permet de les placer dans une file d’attente en appelant la méthode FacesContext.
addMessage() dans les beans gérés. Sa signature est la suivante :
Le code suivant est un extrait d’un bean géré qui crée un livre. Selon que cette créa-
tion réussit ou qu’une exception survient, un message d’information ou d’erreur est
ajouté à la file d’attente des messages à afficher. Notez que ces deux messages sont
globaux car l’identifiant du client vaut null :
FacesContext ctx = FacesContext.getCurrentInstance();
try {
book = bookEJB.createBook(book);
ctx.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO,
"Book has been created",
"The book" + book.getTitle() +
" has been created with id=" +
book.getId())
);
} catch (Exception e) {
ctx.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Book hasn’t been created",
e.getMessage())
);
}
}
Ces messages étant globaux, on peut les afficher dans une page à l’aide d’un simple
marqueur <h:messages>. On peut également préférer afficher un message à un
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
402 Java EE 6 et GlassFish 3
endroit précis pour un composant spécifique (ce qui est généralement le cas avec les
erreurs de validation ou de conversion). La Figure 12.5, par exemple, montre une
page avec un message spécifiquement destiné au champ de saisie du prix.
Figure 12.5
Create a new book
Page affichant un message
pour un composant
ISBN :
d’interface précis.
Tiltle :
Description :
Number of pages :
Illustrations :
Create
Si le champ du prix n’a pas été rempli, un message s’affiche à côté du champ de sai-
sie. Le code qui suit vérifie le prix saisi et crée un message d’avertissement associé
à l’identifiant du composant si la valeur n’est pas correcte :
if (book.getPrice() == null || "".equals(book.getPrice())) {
ctx.addMessage("priceId", new FacesMessage(SEVERITY_WARN,
"Please, fill the price !", "Enter a number value"));
Conversion et validation
Nous venons de voir comment gérer les messages pour informer l’utilisateur sur les
actions à entreprendre. L’une d’elles consiste à corriger une saisie incorrecte (un
ISBN non valide, par exemple). JSF fournit un mécanisme standard de conversion
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 403
et de validation permettant de traiter les saisies des utilisateurs afin d’assurer l’inté-
grité des données. Lorsque vous invoquez des méthodes métiers, vous pouvez donc
vous fier à des données valides : la conversion et la validation permettent aux déve-
loppeurs de se concentrer sur la logique métier au lieu de passer du temps à vérifier
que les données saisies ne sont pas null, qu’elles appartiennent bien à un intervalle
précis, etc.
La conversion a lieu lorsque les données saisies par l’utilisateur doivent être trans-
formées de String en un objet et vice versa. Elle garantit que les informations sont
du bon type – en convertissant, par exemple, un String en java.util.Date, un
String en Integer ou des dollars en euros. Comme pour la validation, elle garantit
que les données contiennent ce qui est attendu (une date au format jj/mm/aaaa, un
réel compris entre 3,14 et 3,15, etc.).
Comme le montre la Figure 12.6, la conversion et la validation interviennent au
cours des différentes phases du cycle de vie de la page (que nous avons présenté au
chapitre précédent).
Figure 12.6
Conversion et validation au cours du cycle de vie d’une page.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
404 Java EE 6 et GlassFish 3
au backing bean (ce qui a lieu au cours de la phase Mise à jour des valeurs du
modèle). En cas d’erreur, des messages d’erreur seront ajoutés et le cycle de vie
sera écourté afin de passer directement à l’Affichage de la réponse (les messages
seront alors affichés sur l’interface utilisateur avec <h:messages>). Au cours
de⁄cette phase, les propriétés du backing bean sont reconverties en chaînes pour
pouvoir être affichées.
JSF fournit un ensemble de convertisseurs et de validateurs standard et vous permet
de créer les vôtres très facilement.
Convertisseurs
Lorsqu’un formulaire est affiché par un navigateur, l’utilisateur remplit les champs
et appuie sur un bouton ayant pour effet de transporter les données vers le serveur
dans une requête HTTP formée de chaînes. Avant de mettre à jour le modèle du bean
géré, ces données textuelles doivent être converties dans les objets cibles (Float,
Integer, BigDecimal, etc.). L’opération inverse aura lieu lorsque les données seront
renvoyées au client dans la réponse pour être affichées par le navigateur.
JSF fournit des convertisseurs pour les types classiques comme les dates et les
nombres. Si une propriété du bean géré est d’un type primitif (Integer, int, Float,
float, etc.), JSF convertira automatiquement la valeur du composant d’interface dans
le type adéquat et inversement. Si elle est d’un autre type, vous devrez fournir votre
propre convertisseur. Le Tableau 12.3 énumère tous les convertisseurs standard du
paquetage javax.faces.convert.
Convertisseur Description
BigDecimalConverter Convertit un String en java.math.BigDecimal et vice versa.
BigIntegerConverter Convertit un String en java.math.BigInteger et vice versa.
BooleanConverter Convertit un String en Boolean (et boolean) et vice versa.
ByteConverter Convertit un String en Byte (et byte) et vice versa.
CharacterConverter Convertit un String en Character (et char) et vice versa.
DateTimeConverter Convertit un String en java.util.Date et vice versa.
DoubleConverter Convertit un String en Double (et double) et vice versa.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 405
Convertisseur Description
EnumConverter Convertit un String en Enum (et enum) et vice versa.
FloatConverter Convertit un String en Float (et float) et vice versa.
IntegerConverter Convertit un String en Integer (et int) et vice versa.
LongConverter Convertit un String en Long (et long) et vice versa.
NumberConverter Convertit un String en classe abstraite java.lang.Number et
vice versa.
ShortConverter Convertit un String en Short (et short) et vice versa.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
406 Java EE 6 et GlassFish 3
Convertisseurs personnalisés
@Override
public Object getAsObject(FacesContext ctx,
UIComponent component,
String value) {
return value;
}
@Override
public String getAsString(FacesContext ctx,
UIComponent component,
Object value) {
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 407
Pour utiliser ce convertisseur, on utilise soit l’attribut converter d’un marqueur, soit
le marqueur <f:converter> : dans les deux cas, il faut fournir le nom du convertisseur
défini par l’annotation @FacesConverter (euroConverter, ici). Le code suivant affiche
deux textes, l’un représentant le prix en dollars, l’autre ce prix converti en euros :
<h:outputText value="#{book.price}"/>
<h:outputText value="#{book.price}">
<f:converter converterId="euroConverter"/>
</h:outputText>
Validateurs
Les applications web doivent garantir que les données saisies par les utilisateurs
sont appropriées. Cette vérification peut avoir lieu côté client avec JavaScript ou
côté serveur avec des validateurs. JSF simplifie la validation des données en four-
nissant des validateurs standard et en permettant d’en créer de nouveaux, adaptés
à vos besoins. Les validateurs agissent comme des contrôles de premier niveau en
validant les valeurs des composants de l’interface utilisateur avant qu’elles ne soient
traitées par le bean géré.
Généralement, les composants d’interface mettent en œuvre une validation simple,
comme vérifier qu’une valeur est obligatoire. Le marqueur suivant, par exemple,
exige qu’une valeur soit entrée dans le champ de saisie :
<h:inputText value="#{bookController.book.title}"
required="true"/>
Si aucune valeur n’est saisie, JSF renvoie la page avec un message indiquant qu’il
faut en fournir une (la page doit avoir un marqueur <h:messages>) en utilisant le
même mécanisme de messages que nous avons déjà décrit. Mais JSF fournit égale-
ment un ensemble de validateurs plus élaborés (voir Tableau 12.4) définis dans le
paquetage javax.faces.validator.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
408 Java EE 6 et GlassFish 3
Convertisseur Description
DoubleRangeValidator Compare la valeur du composant aux valeurs minimales et
maximales indiquées (de type double).
LengthValidator Teste le nombre de caractères de la valeur textuelle du
composant.
LongRangeValidator Compare la valeur du composant aux valeurs minimales et
maximales indiquées (de type long).
RegexValidator Compare la valeur du composant à une expression régulière.
Ces validateurs permettent de traiter les cas génériques comme la longueur d’une
chaîne ou un intervalle de valeurs ; ils peuvent être associés facilement à un com-
posant, de la même façon que les convertisseurs (un même composant peut contenir
les deux). Le code suivant, par exemple, garantit que le titre d’un livre fait entre 2 et
20 caractères et que son prix varie de 1 à 500 dollars :
<h:inputText value="#{bookController.book.title}" required="true">
<f:validateLength minimum="2" maximum="20"/>
</h:inputText>
<h:inputText value="#{bookController.book.price}">
<f:validateLongRange minimum="1" maximum="500"/>
</h:inputText>
Validateurs personnalisés
Les validateurs standard de JSF peuvent ne pas convenir à vos besoins : vous avez
peut-être des données qui respectent certains formats métier, comme un code postal
ou une adresse de courrier électronique. En ce cas, vous devrez créer votre propre
validateur. Comme les convertisseurs, un validateur est une classe qui doit implé-
menter une interface et redéfinir une méthode. Dans le cas des validateurs, cette
interface est javax.faces.validator.Validator, qui n’expose que la méthode
validate() :
Le paramètre value est celui qui doit être vérifié en fonction d’une certaine logique
métier. S’il passe le test de validation, vous pouvez simplement sortir de cette
méthode et le cycle de la page se poursuivra. Dans le cas contraire, vous pouvez lancer
une exception ValidatorException et inclure un FacesMessage avec des messages
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 409
r ésumés et détaillés pour décrire l’erreur de validation. Ce validateur doit être enre-
gistré dans le fichier faces-config.xml ou à l’aide de l’annotation @FacesValidator.
Le Listing 12.12, par exemple, contient le code d’un validateur qui garantit que
l’ISBN saisi par l’utilisateur est au bon format.
@Override
public void validate(FacesContext context,
UIComponent component,
Object value) throws ValidatorException {
String componentValue = value.toString();
pattern = Pattern.compile("(?=[-0-9xX]{13}$)");
matcher = pattern.matcher(componentValue);
if (!matcher.find()) {
String message =
MessageFormat.format("{0} is not a valid isbn format",
componentValue);
FacesMessage facesMessage =
new FacesMessage(SEVERITY_ERROR, message, message);
throw new ValidatorException(facesMessage);
}
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
410 Java EE 6 et GlassFish 3
Ajax
Le protocole HTTP repose sur un mécanisme requête/réponse : un client a besoin
d’une information, il envoie une requête et reçoit une réponse du serveur – généra-
lement une page web complète. La communication va toujours dans ce sens : c’est
le client qui est à l’initiative de la requête. Cependant, les applications web doivent
produire des interfaces riches et réactives et répondre aux événements du serveur,
mettre à jour des parties d’une page, agréger des widgets, etc. Dans un cycle requête/
réponse classique, le serveur devrait envoyer toute la page web, même s’il ne faut
en modifier qu’une petite partie : si la taille de cette page n’est pas négligeable, ceci
consomme de la bande passante et le temps de réponse sera médiocre car le navi-
gateur devra recharger toute la page. Pour améliorer la réactivité du navigateur et
fournir plus de fluidité à l’utilisateur, il ne faut modifier que de petites parties de la
page, et c’est là qu’Ajax entre en jeu.
Ajax (acronyme d’Asynchronous JavaScript and XML) est un ensemble de techniques
de développement web permettant de créer des applications web interactives. Grâce
à lui, les applications récupèrent de façon asynchrone des portions de données à
partir du serveur sans interférer avec l’affichage et le comportement de la page en
cours de consultation. Lorsque les données sont reçues par le client, seules les par-
ties qui ont besoin d’être modifiées le seront : pour cela, on utilise le DOM de la
page et du code JavaScript. Il existe également un mécanisme appelé Reverse Ajax
(ou programmation Comet) pour pousser les données du serveur vers le navigateur.
Ces mécanismes sont utilisés dans la plupart de nos applications web quotidiennes
et sont désormais intégrés à JSF 2.0.
Concepts généraux
Le terme Ajax a été inventé en 2005 pour désigner un ensemble d’alternatives per-
mettant de charger des données de façon asynchrone dans les pages web. En 1999,
Microsoft avait créé l’objet XMLHttpRequest comme un contrôle ActiveX dans Inter-
net Explorer 5. En 2006, le W3C produisit le premier draft de la spécification de
l’objet XMLHttpRequest, qui est désormais reconnu par la plupart des navigateurs.
Au même moment, plusieurs sociétés réfléchirent au moyen de garantir qu’Ajax
devienne un standard reposant sur des technologies ouvertes. Le résultat de ce tra-
vail fut la création de l’OpenAjax Alliance, composée d’éditeurs de logiciels, de
projets open-source et de sociétés utilisant les technologies Ajax.
Comme le montre la Figure 12.7, dans les applications web traditionnelles le naviga-
teur doit demander des documents HTML complets au serveur. L’utilisateur clique
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 411
Bibliothèque Ajax
Serveur Serveur
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
412 Java EE 6 et GlassFish 3
Ajax étant disponible nativement dans JSF 2.0, vous n’avez plus besoin d’écrire
de code JavaScript pour gérer l’objet XMLHttpRequest : il suffit d’utiliser la biblio-
thèque JavaScript qui a été spécifiée et qui est disponible dans les implémentations
de JSF 2.0.
Ajax et JSF
Les versions précédentes de JSF n’offrant pas de solution native pour Ajax, des
bibliothèques tierces sont venues combler ce manque, ce qui augmentait la com-
plexité du code au détriment des performances. À partir de JSF 2.0, tout est beau-
coup plus simple puisque Ajax a été ajouté à la spécification et est désormais intégré
dans toutes les implémentations.
La bibliothèque JavaScript jsf.js permet de réaliser les interactions Ajax, ce qui
signifie qu’il n’est plus nécessaire d’écrire ses propres scripts pour manipuler direc-
tement les objets XMLHttpRequest : vous pouvez vous servir d’un ensemble de fonc-
tions standard pour envoyer des requêtes asynchrones et recevoir les données. Pour
utiliser cette bibliothèque dans vos pages, il suffit d’ajouter la ressource jsf.js avec
la ligne suivante :
<h:outputScript name="jsf.js" library="javax.faces"
target="head"/>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 413
Lorsque le client fait une requête Ajax, le cycle de vie de la page sur le serveur
reste le même (il passe par les mêmes six phases). L’avantage principal est que
la réponse ne renvoie au navigateur qu’un petit extrait de données au lieu d’une
page HTML complète. La phase Application de la requête détermine si la requête
adressée est "partielle" ou non : l’objet PartialViewContext est utilisé tout au long
du cycle de vie de la page et contient les méthodes et les propriétés pertinentes per-
mettant de traiter une requête partielle et de produire une réponse partielle. À la
fin du cycle de vie, la réponse Ajax (ou, à proprement parler, la réponse partielle)
est envoyée au client au cours de la phase Rendu de la réponse – elle est générale-
ment formée de XHTML, XML ou JSON, qui sera analysé par le code JavaScript
s’exécutant côté client.
Récapitulatif
L’exemple du Listing 12.13 montre comment utiliser Ajax et son support par JSF.
La section "Récapitulatif" du Chapitre 10 a montré comment insérer de nouveaux
livres dans la base de données à l’aide d’un bean géré nommé BookController. La
navigation était simple : dès que le livre était créé, l’utilisateur était redirigé vers la
page affichant la liste de tous les livres ; en cliquant sur un lien, il pouvait revenir
sur le formulaire de création. Nous allons reprendre cet exemple pour lui ajouter
quelques fonctionnalités Ajax.
Nous voulons maintenant afficher sur la même page le formulaire et la liste des
livres (voir Figure 12.8). À chaque fois qu’un livre est créé en cliquant sur le bouton
du formulaire, la liste sera rafraîchie afin de faire apparaître ce nouveau livre.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
414 Java EE 6 et GlassFish 3
Figure 12.8
Create a new book
Une seule page
pour créer et afficher
ISBN : 256 6 56
tous les livres.
Tiltle : Dune
Price : 23.25
The trilogy
Description :
Illustrations :
Create a book
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 415
<h:head>
<title>Create a new book</title>
</h:head>
<h:body>
<h:outputScript name="jsf.js"
library="javax.faces" target="head"/>
<tr>
<td><h:outputLabel value="ISBN : "/></td>
<td>
<h:inputText id="isbn" value="#{bookCtrl.book.isbn}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Title :"/></td>
<td>
<h:inputText id="title"
value="#{bookCtrl.book.title}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Price : "/></td>
<td><h:inputText id="price"
value="#{bookCtrl.book.price}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Description : "/></td>
<td>
<h:inputTextarea id="description"
value="#{bookCtrl.book.description}" cols="20"
rows="5"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Number of pages : "/></td>
<td>
<h:inputText id="nbOfPage"
value="#{bookCtrl.book.nbOfPage}"/>
</td>
</tr>
<tr>
<td><h:outputLabel value="Illustrations : "/></td>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
416 Java EE 6 et GlassFish 3
<td>
<h:selectBooleanCheckbox id="illustrations"
value="#{bookCtrl.book.illustrations}"/>
</td>
</tr>
</table>
<h:column>
<f:facet name="header">
<h:outputText value="Title"/>
</f:facet>
<h:outputText value="#{bk.title}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Price dollar"/>
</f:facet>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 12 Traitement et navigation 417
<h:outputText value="#{bk.price}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Description"/>
</f:facet>
<h:outputText value="#{bk.description}" />
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Number Of Pages"/>
</f:facet>
<h:outputText value="#{bk.nbOfPage}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Illustrations"/>
</f:facet>
<h:outputText value="#{bk.illustrations}"/>
</h:column>
</h:dataTable>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
418 Java EE 6 et GlassFish 3
<td>1234-234</td>
<td>H2G2</td>
<td>12.0</td>
<td>Scifi IT book</td>
<td>241</td>
<td>false</td>
</tr>
<tr>
<td>564-694</td>
<td>Robots</td>
<td>18.5</td>
<td>Best seller</td>
<td>317</td>
<td>true</td>
</tr>
</table>
</update>
</changes>
</partial-response>
Résumé
Le chapitre précédent avait étudié l’aspect graphique de JSF alors que ce chapitre
s’est intéressé à sa partie dynamique. JSF met en œuvre le modèle de conception
MVC : sa spécification s’étend de la création des interfaces utilisateurs à l’aide de
composants au traitement des données avec les beans gérés.
Les beans gérés sont au cœur de JSF car ce sont eux qui traitent la logique métier,
appellent les EJB, les bases de données, etc. et qui permettent de naviguer entre
les pages. Ils ont une portée et un cycle de vie (qui ressemble à celui des beans de
session sans état), et ils déclarent des méthodes et des propriétés qui sont liées aux
composants d’interface grâce au langage d’expression. Les annotations et la confi-
guration par exception ont permis de beaucoup simplifier JSF 2.0 car la plupart des
configurations XML sont désormais facultatives.
Nous avons montré comment chaque composant d’entrée peut gérer les conversions
et les validations. JSF définit un ensemble de convertisseurs et de validateurs pour
les situations les plus courantes, mais vous pouvez également créer et enregistrer les
vôtres de façon très simple.
Ajax existe depuis quelques années et JSF 2.0 dispose maintenant pour cette techno-
logie d’un support natif qui permet aux pages web d’invoquer de façon asynchrone
des beans gérés. JSF 2.0 définit une bibliothèque JavaScript standard afin que les
développeurs n’aient plus besoin d’écrire de scripts et puissent simplement utiliser
des fonctions pour rafraîchir des portions de pages.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
13
Envoi de messages
La plupart des communications entre les composants que nous avons vus jusqu’à
présent sont synchrones : une classe en appelle une autre, un bean géré invoque un
EJB, qui appelle une entité, etc. Dans ces cas-là, l’appelant et la cible doivent être
en cours d’exécution pour que la communication réussisse et l’appelant doit attendre
que la cible termine son exécution avant de pouvoir continuer. À l’exception des
appels asynchrones dans EJB, la plupart des composants de Java EE utilisent des
appels synchrones (locaux ou distants). Lorsque nous parlons de messages, nous pen-
sons à une communication asynchrone, faiblement couplée, entre les composants.
MOM (Middleware Orienté Messages) est un logiciel (un fournisseur) qui autorise les
messages asynchrones entre des systèmes hétérogènes. On peut le considérer comme
un tampon placé entre les systèmes, qui produisent et consomment les messages à leurs
propres rythmes (un système peut, par exemple, tourner 24 heures sur 24, 7 jours sur 7
alors qu’un autre ne tourne que la nuit). Il est faiblement couplé car ceux qui envoient
les messages ne savent pas qui les recevra à l’autre extrémité du canal de communi-
cation. Pour communiquer, l’expéditeur et le récepteur ne doivent pas nécessairement
fonctionner en même temps – en fait, ils ne se connaissent même pas puisqu’ils uti-
lisent un tampon intermédiaire. De ce point de vue, le MOM est totalement différent
des technologies comme RMI (Remote Method Invocation), qui exigent qu’une appli-
cation connaisse les signatures des méthodes d’une application distante.
Aujourd’hui, une société typique utilise plusieurs applications, souvent écrites dans
des langages différents, qui réalisent des tâches bien définies. Le MOM leur permet
de fonctionner de façon indépendante tout en faisant partie d’un workflow. Les mes-
sages sont une bonne solution pour intégrer les applications existantes et nouvelles
en les couplant faiblement, de façon asynchrone, pourvu qu’émetteur et destinataire
se mettent d’accord sur le format du message et sur le tampon intermédiaire.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
420 Java EE 6 et GlassFish 3
Cette communication peut être locale à une organisation ou distribuée entre plusieurs
services externes.
Le MOM, qui existe déjà depuis un certain temps, utilise un vocabulaire spécial.
Lorsqu’un message est envoyé, le logiciel qui le stocke et l’expédie est appelé un
fournisseur (ou, parfois, un broker). L’émetteur du message est appelé producteur
et l’emplacement de stockage du message, une destination. Le composant qui reçoit
le message est appelé consommateur – tout composant intéressé par un message à
cette destination précise peut le consommer. La Figure 13.1 illustre ces concepts.
Figure 13.1 Fournisseur
de messages
Architecture du MOM. Mess
Avec Java EE, tous ces concepts sont pris en charge par l’API JMS (Java Mes-
sage Service), qui propose un ensemble d’interfaces et de classes permettant de se
connecter à un fournisseur, de créer un message, de l’envoyer et de le recevoir. JMS
ne transporte pas les messages : il a besoin d’un fournisseur qui s’occupe de le faire.
Lorsqu’ils s’exécutent dans un conteneur, les MDB (Message-Driven Beans) peuvent
servir à recevoir les messages de façon gérée par le conteneur.
JMS
JMS est une API standard de Java permettant aux applications de créer, d’envoyer,
de recevoir et de lire les messages de façon asynchrone. Elle définit un ensemble
d’interfaces et de classes que les programmes peuvent utiliser pour communiquer
avec d’autres fournisseurs de messages. JMS ressemble à JDBC : cette dernière
permet de se connecter à plusieurs bases de données (Derby, MySQL, Oracle, DB2,
etc.), alors que JMS permet de se connecter à plusieurs fournisseurs (OpenMQ,
MQSeries, SonicMQ, etc.).
L’API JMS couvre toutes les fonctionnalités nécessaires aux messages, c’est-à-dire
leur envoi et leur réception via des destinations :
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 421
MDB
Les MDB sont des consommateurs de messages asynchrones qui s’exécutent dans
un conteneur EJB. Comme on l’a vu aux Chapitres 6 à 9, le conteneur prend en
charge plusieurs services (transaction, sécurité, concurrence, acquittement des
messages, etc.) ; ce qui permet au MDB de se consacrer à la consommation des
messages JMS. Les MDB étant sans état, le conteneur EJB peut avoir de nom-
breuses instances s’exécutant de façon concurrente pour traiter les messages pro-
venant de différents producteurs JMS. Bien que les MDB ressemblent aux beans
sans état, on ne peut pas y accéder directement par les applications : la seule façon
de communiquer avec eux consiste à envoyer un message à la destination que le
MDB surveille.
En général, les MDB surveillent une destination (file d’attente ou sujet) et consom-
ment et traitent les messages qui y arrivent. Ils peuvent également déléguer la
logique métier à d’autres beans de session sans état de façon transactionnelle. Étant
sans état, les MDB ne mémorisent pas l’état d’un message à l’autre. Ils répondent
aux messages JMS reçus du conteneur, alors que les beans de session sans état
répondent aux requêtes clientes via une interface appropriée (locale, distante ou sans
interface).
En Java, les messages sont essentiellement représentés par JMS, qui peut être utili-
sée par les applications qui s’exécutent dans un environnement Java SE ou Java EE.
MDB est l’extension entreprise d’un consommateur JMS et est lié à la spécification
EJB.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
422 Java EE 6 et GlassFish 3
Jusqu’à la fin des années 1980, les sociétés ne disposaient pas de moyens simples
pour lier leurs différentes applications : les développeurs devaient écrire des adapta-
teurs logiciels pour que les systèmes traduisent les données des applications sources
dans un format reconnu par les applications destinataires (et réciproquement). Pour
gérer les différences de puissance et de disponibilité de serveurs, il fallait créer des
tampons pour faire attendre les traitements. En outre, l’absence de protocoles de
transport homogènes exigeait de créer des adaptateurs de protocoles de bas niveau.
Le middleware a commencé à émerger à la fin des années 1980 afin de résoudre
ces problèmes d’intégration. Les premiers MOM furent créés comme des compo-
sants logiciels séparés que l’on plaçait au milieu des applications pour "faire de la
plomberie" entre les systèmes. Ils étaient capables de gérer des plates-formes, des
langages de programmation, des matériels et des protocoles réseaux différents.
JMS 1.1
La spécification JMS fut publiée en août 1998. Elle a été mise au point par les princi-
paux éditeurs de middleware afin d’ajouter à Java les fonctionnalités des messages.
La JSR 914 apporta quelques modifications mineures (JMS 1.0.1, 1.0.2 et 1.0.2b)
pour finalement atteindre la version 1.1 en avril 2002. Depuis, cette spécification n’a
pas changé. JMS 1.1 a été intégrée dans J2EE 1.2 et fait depuis partie de Java EE.
Cependant, JMS et les MDB ne font pas partie de la spécification Web Profile que
nous avons décrite au premier chapitre, ce qui signifie qu’ils ne seront disponibles
que sur les serveurs d’applications qui implémentent l’intégralité de la plate-forme
Java EE 6.
Bien que JMS soit une API touffue et de bas niveau, elle fonctionne très bien.
Malheureusement, elle n’a pas été modifiée ou améliorée dans Java EE 5 ni dans
Java EE 6.
EJB 3.1
Les MDB ont été introduits avec EJB 2.0 et améliorés avec EJB 3.0 et par le para-
digme général de simplification de Java EE 5. Ils n’ont pas été modifiés en interne
car ils continuent d’être des consommateurs de messages, mais l’introduction des
annotations et de la configuration par exception facilite beaucoup leur écriture. La
nouvelle spécification EJB 3.1 (JSR 318) n’apporte pas de modifications notables
aux MDB.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 423
Implémentation de référence
L’implémentation de référence de JMS est OpenMQ (Open Message Queue), qui est
open-source depuis 2006 et peut être utilisée dans des applications JMS autonomes
ou intégrées dans un serveur d’applications. OpenMQ est le fournisseur de mes-
sages par défaut de GlassFish et, à la date où ce livre est écrit, en est à sa version 4.4.
Elle respecte évidemment la spécification JMS et lui ajoute de nombreuses fonction-
nalités supplémentaires comme UMS (Universal Message Service), les clusters et
bien d’autres choses encore.
Étudions un exemple simple pour nous faire une idée de JMS. Comme on l’a déjà
évoqué, JMS utilise des producteurs, des consommateurs et des destinations : le
producteur envoie un message à la destination, qui est surveillée par le consomma-
teur qui attend qu’un message y arrive. Les destinations sont de deux types : les files
d’attente (pour les communications point à point) et les sujets (pour les communica-
tions de type Publication-Abonnement). Dans le Listing 13.1, un producteur envoie
un message de texte à une file surveillée par le consommateur.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
424 Java EE 6 et GlassFish 3
connection.close();
}
}
Le code du Listing 13.1 représente une classe Sender qui ne dispose que d’une
méthode main(). Celle-ci commence par instancier un contexte JNDI pour obtenir
des objets ConnectionFactory et Queue. Les fabriques de connexions et les destina-
tions (files d’attente et sujets) sont appelées objets administrés : ils doivent être créés
et déclarés dans le fournisseur de messages (OpenMQ, ici). Tous les deux ont un
nom JNDI (la file d’attente, par exemple, s’appelle jms/javaee6/Queue) et doivent
être recherchés dans l’arborescence JNDI.
Lorsque les deux objets administrés ont été obtenus, la classe Sender utilise l’ob-
jet ConnectionFactory pour créer une connexion (objet Connection) afin d’obtenir
une session. Cette dernière est utilisée pour créer un producteur (objet Message-
Producer) et un message (objet TextMessage) sur la file de destination (session.
createProducer(queue)). Le producteur envoie ensuite ce message de type texte.
Bien que ce code soit largement commenté, nous avons délibérément omis le
traitement des exceptions JNDI et JMS.
Le code pour recevoir le message est quasiment identique. En fait, les premières
lignes de la classe Receiver du Listing 12.2 sont exactement les mêmes : on crée un
contexte JNDI, on recherche la fabrique de connexion et la file d’attente, puis on se
connecte. Les seules différences tiennent au fait que l’on utilise un MessageConsumer
au lieu d’un MessageProducer et que le récepteur entre dans une boucle sans fin pour
surveiller la file d’attente (nous verrons plus tard que l’on peut éviter cette boucle en
utilisant un écouteur de messages, ce qui est une technique plus classique). Lorsque
le message arrive, on le consomme et on affiche son contenu.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 425
}
}
Nous pouvons maintenant nous intéresser à l’API de JMS, définir les objets admi-
nistrés et les classes nécessaires et voir comment traduire tout ceci dans un MDB.
Au niveau le plus haut, l’architecture de JMS est formée des composants suivants
(voir Figure 13.2) :
■■ Un fournisseur. JMS n’est qu’une spécification et a donc besoin d’une implé-
mentation sous-jacente pour router les messages, c’est-à-dire un fournisseur.
Celui-ci gère le tampon et la livraison des messages en fournissant une implé-
mentation de l’API JMS.
■■ Clients. Un client est une application ou un composant Java qui utilise l’API
JMS pour consommer ou produire un message JMS. On parle de client JMS car
c’est un client du fournisseur sous-jacent. "Client" est le terme générique pour
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
426 Java EE 6 et GlassFish 3
Producteur Consommateur
Annuaire JNDI
recherche les objets administrés recherche les objets administrés
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 427
Point à point
Dans le modèle P2P, un unique message va d’un unique producteur (le point A) à
un unique consommateur (le point B). Ce modèle repose sur le concept de files de
messages, d’émetteurs et de récepteurs (voir Figure 13.3). Une file stocke les mes-
sages envoyés par l’émetteur jusqu’à ce qu’ils soient consommés – les timings de
l’émetteur et du récepteur sont indépendants, ce qui signifie que l’émetteur peut pro-
duire des messages et les envoyer dans la file lorsqu’il le désire et qu’un récepteur
peut les consommer quand il le souhaite. Lorsqu’un récepteur est créé, il reçoit tous
les messages envoyés dans la file, même ceux qui ont été envoyés avant sa création.
Figure 13.3
Le modèle P2P. Mess Mess
Émetteur Récepteur
envoie reçoit
Chaque message est envoyé dans une file spécifique d’où le récepteur extrait les
messages. Les files conservent tous les messages tant qu’ils n’ont pas été consommés
ou jusqu’à ce qu’ils expirent.
Le modèle P2P est utilisé lorsqu’il n’y a qu’un seul récepteur pour chaque mes-
sage – une file peut avoir plusieurs consommateurs mais, une fois qu’un récepteur a
consommé un message, ce dernier est supprimé de la file et aucun autre consomma-
teur ne peut donc le recevoir. À la Figure 13.4, un seul émetteur produit trois mes-
sages et deux récepteurs consomment chacun un message qui ne sera pas disponible
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
428 Java EE 6 et GlassFish 3
pour l’autre. JMS garantit également qu’un message ne sera délivré qu’une seule
fois.
Figure 13.4
Plusieurs
récepteurs. Mess Mess Mess Récepteur 1 Mess Mess
1 2 3 3 2
Émetteur
Récepteur 2 Mess
1
Notez que P2P ne garantit pas que les messages seront délivrés dans un ordre parti-
culier. En outre, s’il y a plusieurs récepteurs possibles pour un message, le choix du
récepteur est aléatoire.
Publication-abonnement
Dans ce modèle, un unique message envoyé par un seul producteur peut être reçu
par plusieurs consommateurs. Ce modèle repose sur le concept de sujets, d’éditeurs
et d’abonnés (voir Figure 13.5). Les consommateurs sont appelés abonnés car ils
doivent d’abord s’abonner à un sujet. C’est le fournisseur qui gère le mécanisme
d’abonnement/désabonnement, qui a lieu de façon dynamique.
Figure 13.5
Modèle
Publication- Mess s'abonne
abonnement. Éditeur Abonné
publie reçoit
Mess
Le sujet conserve les messages jusqu’à ce qu’ils aient été distribués à tous les abon-
nés. À la différence du modèle P2P, les timings entre éditeurs et abonnés sont liés :
ces derniers ne reçoivent pas les messages envoyés avant leur abonnement et un
abonné resté inactif pendant une période donnée ne recevra pas les messages publiés
entre-temps lorsqu’il redeviendra actif. Comme nous le verrons plus tard, ceci peut
être évité car l’API JMS dispose du concept d’abonné durable.
Plusieurs abonnés peuvent consommer le même message. Ce modèle peut donc être
utilisé par les applications de type "diffusion", dans lesquelles un même message est
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 429
Figure 13.6
Plusieurs
Abonné 1
abonnés. Mess Mess Mess Mess Mess Mess
3 2 1 3 1 2
Éditeur
Abonné 2 Mess Mess Mess
1 3 2
API JMS
L’API JMS se trouve dans le paquetage javax.jms et fournit des classes et des inter-
faces aux applications qui ont besoin d’un système de messages (voir Figure 13.7).
Elle autorise les communications asynchrones entre les clients en fournissant une
connexion à un fournisseur et une session au cours de laquelle les messages peuvent
être créés, envoyés et reçus. Ces messages peuvent contenir du texte ou différents
types d’objets.
Figure 13.7 <<Interface>>
ConnectionFactory
API JMS (inspirée
de la Figure 2.1
crée
de la spécification
JMS 1.1). <<Interface>>
Connection
crée
<<Interface>>
Destination
envoie à reçoit de
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
430 Java EE 6 et GlassFish 3
Objets administrés
Les objets administrés sont des objets qui ne sont pas configurés par programma-
tion : le fournisseur de messages les rend disponibles dans l’espace de noms JNDI.
Comme les sources de données JDBC, les objets administrés ne sont créés qu’une
seule fois. Il en existe deux types pour JMS :
■■ Les fabriques de connexion. Ces objets sont utilisés par les clients pour créer
une connexion vers une destination.
■■ Les destinations. Ces objets sont des points de distribution qui reçoivent,
stockent et distribuent les messages. Les destinations peuvent être des files
d’attente (P2P) ou des sujets (Publication-abonnement).
Les clients JMS accèdent à ces objets via des interfaces portables en les recherchant
dans l’espace de noms JNDI. GlassFish fournit plusieurs moyens d’en créer : en
utilisant la console d’administration, avec l’outil en ligne de commande asadmin ou
avec l’interface REST.
Fabriques de connexion
Ce sont les objets administrés qui permettent à une application de se connecter
à un fournisseur en créant par programme un objet Connection. Une fabrique de
connexion encapsule les paramètres de configuration définis par un administrateur.
Il en existe trois types :
■■ javax.jms.ConnectionFactory est une interface pouvant être utilisée à la fois
par les communications P2P et Publication-abonnement.
■■ javax.jms.QueueConnectionFactory est une interface qui hérite de Connection
Factory et qui ne sert qu’aux communications P2P.
■■ javax.jms.TopicConnectionFactory est une interface qui hérite de Connection
Factory et qui ne sert qu’aux communications Publication-abonnement.
Le programme doit d’abord obtenir une fabrique de connexion en effectuant une
recherche JNDI. Le fragment de code suivant, par exemple, obtient un objet Ini-
tialContext et s’en sert pour rechercher un QueueConnectionFactory et un Topic-
ConnectionFactory par leurs noms JNDI :
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 431
Les fabriques de files d’attente/sujets sont nécessaires lorsque l’on a besoin d’ac-
céder à des détails spécifiques de ces modèles. Dans le cas contraire, vous pouvez
directement utiliser une ConnectionFactory générique pour les deux situations :
Context ctx = new InitialContext();
ConnectionFactory ConnectionFactory =
(QueueConnectionFactory) ctx.lookup("GenericConnFactory");
La seule méthode disponible dans ces trois interfaces est la méthode createConnec-
tion(), qui renvoie un objet Connection. Cette méthode est surchargée pour pouvoir
créer une connexion en utilisant l’identité de l’utilisateur par défaut ou en précisant
un nom d’utilisateur et un mot de passe (voir Listing 13.3).
Destinations
Une destination est un objet administré contenant des informations spécifiques à
un fournisseur, comme l’adresse de destination. Cependant, ces informations sont
cachées au client JMS par l’interface standard javax.jms.Destination. Il existe
deux types de destinations, représentées par deux interfaces héritant de Destination :
■■ javax.jms.Queue, utilisée pour les communications P2P ;
■■ javax.jms.Topic, utilisée pour les communications Publication-abonnement.
Ces interfaces ne contiennent qu’une seule méthode, qui renvoie le nom de la des-
tination. Comme pour les fabriques de connexion, ces objets s’obtiennent par une
recherche JNDI.
Injection
Les fabriques de connexion et les destinations sont des objets administrés qui rési-
dent dans un fournisseur de messages et qui doivent être déclarés dans l’espace
de noms JNDI. Lorsque le code client s’exécute dans un conteneur (EJB, servlet,
client d’application), vous pouvez utiliser l’injection des dépendances à la place
des recherches JNDI grâce à l’annotation @Resource (voir la section "Injection de
dépendances" du Chapitre 7), ce qui est bien plus simple.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
432 Java EE 6 et GlassFish 3
Élément Description
name Nom JNDI de la ressource.
type Type Java de la ressource (javax.sql.DataSource ou javax.
jms.Topic, par exemple).
Pour tester cette annotation, nous utiliserons une classe Receiver avec une méthode
main() qui reçoit des messages textuels. Dans le Listing 13.2, la fabrique de
connexions et la file d’attente étaient obtenues par des recherches JNDI ; dans le
Listing 13.4, les noms JNDI sont indiqués dans les annotations @Resource : si cette
classe Receiver s’exécute dans un conteneur, elle recevra donc par injection des
références ConnectionFactory et Queue lors de son initialisation.
Listing 13.4 : La classe Receiver obtient par injection des références à des ressources JMS
public class Receiver {
@Resource(name = "jms/javaee6/ConnectionFactory")
private static ConnectionFactory connectionFactory;
@Resource(name = "jms/javaee6/Queue")
private static Queue queue;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 433
Pour simplifier, nous avons omis le traitement des exceptions. Notez que l’annota-
tion @Resource porte sur des attributs privés : l’injection de ressources peut s’ap-
pliquer à différents niveaux de visibilité (privée, protégée, paquetage ou publique).
Connexion
L’objet javax.jms.Connection que l’on crée avec la méthode createConnection()
de la fabrique de connexion encapsule une connexion au fournisseur JMS. Les
connexions sont thread-safe et conçues pour être partagées car l’ouverture d’une
connexion est une opération coûteuse en terme de ressources. Une session (javax.
jms.Session), par contre, fournit un contexte mono-thread pour envoyer et recevoir
les messages ; une connexion permet de créer une ou plusieurs sessions.
Comme les fabriques de connexion, les connexions peuvent être de trois types en uti-
lisant l’interface générique Connection ou les interfaces QueueConnection et Topic-
Connection qui dérivent de celle-ci. La création de l’objet "connexion" dépend du
type de la fabrique dont vous disposez :
Connection connection = connFactory.createConnection(); QueueConnection
connection =
queueConnFactory.createQueueConnection();
TopicConnection connection =
topicConnFactory.createTopicConnection();
Session
On crée une session en appelant la méthode createSession() de la connexion.
Une session fournit un contexte transactionnel qui permet de regrouper plusieurs
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
434 Java EE 6 et GlassFish 3
essages dans une unité de traitement atomique : si vous envoyez plusieurs mes-
m
sages au cours de la même session, JMS garantira qu’ils arrivent tous à destination
dans leur ordre d’émission, ou qu’aucun n’arrivera. Ce comportement est mis en
place lors de la création de la session :
Session session = connection.createSession(true,
Session.AUTO_ACKNOWLEDGE);
Messages
Figure 13.8
Structure En-tête Propriétés Corps
JMSMessageID <name><value> BytesMessage
d’un message JMS. JMSCorrelationID <name><value> TextMessage
JMSDeliveryMode <name><value> ObjectMessage
JMSDestination MapMessage
JMSExpiration StreamMessage
JMSPriority
JMSRedelivered
JMSReplyTo
JMSTimestamp
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 435
En-tête
L’en-tête contient des paires nom/valeur prédéfinies, communes à tous les mes-
sages et que les clients et les fournisseurs utilisent pour identifier et acheminer
les messages. Elles peuvent être considérées comme les métadonnées du message
car elles fournissent des informations sur le message lui-même. Chaque champ a
des méthodes getter et setter qui lui sont associées dans l’interface javax.jms.
Message. Bien que certains champs de l’en-tête soient prévus pour être initialisés
par un client, la plupart sont automatiquement fixés par la méthode send() ou
publish().
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
436 Java EE 6 et GlassFish 3
Propriétés
Outre les champs d’en-tête, l’interface javax.jms.Message permet de gérer les
valeurs des propriétés, qui sont exactement comme les en-têtes, mais créées expli-
citement par l’application au lieu d’être standard pour tous les messages. Elles per-
mettent d’ajouter des champs d’en-têtes optionnels à un message, que le client peut
choisir de recevoir ou non via des sélecteurs. Leurs valeurs peuvent être de type
boolean, byte, short, int, long, float, double et String. Le code permettant de les
initialiser et de les lire est de la forme :
message.setFloatProperty("orderAmount", 1245.5f); message.getFloatProperty("or
derAmount");
Corps
Le corps d’un message est facultatif et contient les données à envoyer ou à rece-
voir. Selon l’interface utilisée, il peut contenir des données dans différents formats,
énumérés dans le Tableau 13.4.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 437
Interface Description
StreamMessage Message contenant un flux de valeurs primitives Java. Il est rempli et lu
séquentiellement.
MapMessage Message contenant un ensemble de paires nom/valeur où les noms sont
des chaînes et les valeurs sont d’un type primitif.
TextMessage Message contenant une chaîne (du XML, par exemple).
ObjectMessage Message contenant un objet sérialisable ou une collection d’objets
sérialisables.
BytesMessage Message contenant un flux d’octets.
MessageProducer
Un producteur de messages est un objet créé par une session pour envoyer des mes-
sages à une destination. L’interface générique javax.jms.MessageProducer peut
être utilisée pour obtenir un producteur spécifique disposant d’une interface unique.
Dans le cas du modèle P2P, un producteur de messages est appelé émetteur et implé-
mente l’interface QueueSender. Avec le modèle Publication-abonnement, il s’ap-
pelle éditeur et implémente TopicPublisher. Selon l’interface utilisée, un message
créé est envoyé (P2P) ou publié (Publication-abonnement) :
messageProducer.send(message);
queueSender.send(message);
topicPublisher.publish(message);
Un producteur peut préciser un mode de livraison par défaut, une priorité et un délai
d’expiration des messages qu’il envoie. Les étapes suivantes expliquent comment
créer un éditeur qui publie un message dans un sujet (voir Listing 13.5) :
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
438 Java EE 6 et GlassFish 3
1. Obtenir une fabrique de connexions et un sujet par injection (ou par une
recherche JNDI).
2. Créer un objet Connection avec la fabrique de connexions.
3. Créer un objet Session avec la connexion.
4. Créer un MessageProducer (ici, nous aurions pu choisir un TopicPublisher) en
utilisant l’objet Session.
5. Créer un ou plusieurs messages d’un type quelconque (ici, nous avons utilisé
un TextMessage) en utilisant l’objet Session. Après sa création, remplir le mes-
sage avec les données (nous nous sommes servis ici de la méthode setText()).
6. Envoyer un ou plusieurs messages dans le sujet à l’aide de la méthode Message-
Producer.send() (ou TopicPublisher.publish()).
@Resource(mappedName = "jms/javaee6/ConnectionFactory")
private static ConnectionFactory connectionFactory;
@Resource(mappedName = "jms/javaee6/Topic")
private static Topic topic;
connection.close();
}
}
MessageConsumer
Un client utilise un MessageConsumer pour recevoir les messages provenant d’une
destination. Cet objet est créé en passant une Queue ou un Topic à la méthode
createConsumer() de la session. Dans le cas du modèle P2P, un consommateur
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 439
Figure 13.9
Consommateurs
synchrones et Mess Mess
asynchrones. Émetteur Récepteur
envoie collecte synchrone
Mess enregistre
Émetteur Récepteur
prévient asynchrone
envoie
collecte
Mess
Livraison synchrone
Un récepteur synchrone doit lancer une connexion, boucler en attendant qu’un
message arrive et demander le message arrivé à l’aide de l’une de ses méthodes
receive() : leurs différentes variantes permettent au client de demander ou d’at-
tendre le prochain message. Les étapes suivantes expliquent comment créer un
récepteur synchrone qui consomme un message d’un sujet (voir Listing 13.6) :
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
440 Java EE 6 et GlassFish 3
5. Lancer la connexion.
6. Boucler et appeler la méthode receive() sur l’objet consommateur. Cette
méthode se bloque si la file est vide et attend qu’un message arrive. Ici, la
boucle sans fin attend que d’autres messages arrivent.
7. Traiter le message renvoyé par receive() en utilisant la méthode TextMessage.
getText() (si c’est un message texte).
@Resource(mappedName = "jms/javaee6/ConnectionFactory")
private static ConnectionFactory connectionFactory;
@Resource(mappedName = "jms/javaee6/Topic")
private static Topic topic;
Livraison asynchrone
La consommation asynchrone repose sur la gestion des événements. Un client
peut enregistrer un objet (y compris lui-même) qui implémente l’interface
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 441
@Resource(mappedName = "jms/javaee6/ConnectionFactory")
private static ConnectionFactory connectionFactory;
@Resource(mappedName = "jms/javaee6/Topic")
private static Topic topic;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
442 Java EE 6 et GlassFish 3
Sélecteurs
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 443
Les expressions de sélections peuvent utiliser des opérateurs logiques (NOT, AND,
OR), de comparaison (=, >, >=, <, <=, <>), des opérateurs arithmétiques (+, -, *, /), des
expressions ([NOT] BETWEEN, [NOT] IN, [NOT] LIKE, IS [NOT] NULL), etc.
Mécanismes de fiabilité
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
444 Java EE 6 et GlassFish 3
Le premier appel fixe le mode global de livraison pour le producteur et tous les mes-
sages qu’il délivre, tandis que le second ne l’indique que pour le message concerné.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 445
Figure 13.10
Un récepteur Mess
accuse réception Mess
d’un message. Émetteur Abonné 1
reçoit
envoie
accuse réception
// Producteur
connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
producer = session.createProducer(topic);
message = session.createTextMessage();
producer.send(message);
// Consommateur
message.acknowledge();
Abonnements durables
L’inconvénient du modèle Publication-abonnement est qu’il faut qu’un consomma-
teur de messages s’exécute lorsque les messages sont envoyés au sujet ; sinon il ne
les recevra pas. En utilisant des abonnés durables, l’API JMS fournit un moyen de
conserver les messages dans le sujet jusqu’à ce que tous les consommateurs abonnés
les aient reçus. Grâce à l’abonnement durable, le récepteur peut donc être déconnecté :
lorsqu’il se reconnectera, il recevra les messages arrivés entre-temps. Pour cela, le
client crée un abonné durable en utilisant la session :
session.createDurableSubscriber(topic,
"javaee6DurableSubscription");
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
446 Java EE 6 et GlassFish 3
Priorités
Les priorités permettent de faire en sorte que le fournisseur JMS délivre en pre-
mier les messages urgents. JMS définit dix priorités, de 0 (la plus faible) à 9 (la
plus forte). Vous pouvez indiquer la valeur de la priorité par un appel à la méthode
etPriority() ou en la passant en paramètre à la méthode send() :
s
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 447
multithread. Dès qu’un nouveau message atteint la destination, une instance MDB
est retirée du pool pour gérer le message.
Les MDB peuvent être transactionnels, multithreads et, naturellement, ils peuvent
consommer des messages JMS. Avec l’API JMS, vous pourriez vous attendre à
devoir utiliser des fabriques, des connexions, des consommateurs, des transactions,
etc., comme précédemment. Pourtant, l’aspect d’un MDB simple comme celui du
Listing 13.8 risque de vous surprendre.
Le code du Listing 13.8 (qui omet le traitement des exceptions pour plus de clarté)
montre que les MDB libèrent le programmeur de tous les aspects techniques du
traitement des différents types de messages que nous avons présentés jusqu’ici. Un
MDB implémente l’interface MessageListener et la méthode onMessage(), mais
aucun autre code n’est nécessaire pour se connecter au fournisseur ou pour lancer la
consommation des messages. Les MDB utilisent également le mécanisme de confi-
guration par exception, ce qui implique que seul un petit nombre d’annotations suffit
pour les faire fonctionner (voir l’annotation @MessageDriven).
Les MDB sont différents des beans de session car ils n’implémentent pas d’interface
métier locale ou distante ; ils implémentent à la place javax.jms.MessageListener.
Les clients ne peuvent pas appeler directement les méthodes des MDB ; cependant,
comme les beans de session, les MDB utilisent un modèle de programmation éla-
boré, avec un cycle de vie, des annotations de rappel, des intercepteurs, l’injection
et les transactions. Les applications qui tirent parti de ce modèle disposent donc de
nombreuses fonctionnalités.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
448 Java EE 6 et GlassFish 3
Il est important de se rappeler que les MDB ne font pas partie du nouveau modèle
EJB Lite, ce qui signifie qu’on ne peut pas les déployer dans une application web
simple (dans un fichier war) : ils ont besoin d’un assemblage entreprise (archive ear).
Une classe MDB doit respecter les règles suivantes :
■■ Elle doit être annotée par @javax.ejb.MessageDriven ou son équivalent XML
dans un descripteur de déploiement.
■■ Elle doit implémenter, directement ou non, l’interface MessageListener.
■■ Elle doit être publique et ne doit être ni finale ni abstraite.
■■ Elle doit proposer un constructeur public sans paramètre qui servira à créer les
instances du MDB.
■■ Elle ne doit pas définir de méthode finalize().
La classe MDB peut implémenter d’autres méthodes, invoquer d’autres ressources,
etc. Les MDB sont déployés dans un conteneur et peuvent éventuellement être
assemblés avec un fichier ejb-jar.xml. Avec le modèle simplifié de Java EE 6, un
MDB peut être un simple POJO annoté et faire l’économie de la majeure partie
de sa configuration. Cependant, vous pouvez utiliser les éléments des annotations
@MessageDriven et @ActivationConfigProperty (ou leurs équivalents XML) pour
adapter la configuration de JMS à vos besoins.
@MessageDriven
Les MDB font partie des EJB les plus simples à développer car ils utilisent un mini-
mum d’annotations. @MessageDriven (ou son équivalent XML) est obligatoire car c’est
une métadonnée dont a besoin le conteneur pour savoir que la classe Java est un MDB.
L’API de cette annotation, présentée dans le Listing 13.9, est très simple et tous ses
éléments sont facultatifs.
L’élément name précise le nom du MDB (qui est, par défaut, celui de la classe). mes-
sageListenerInterface précise l’écouteur de message que le MDB implémente
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 449
(s’il implémente plusieurs interfaces, cet élément indique au conteneur EJB quelle
est l’interface MessageListener). L’élément mappedName est le nom JNDI de la des-
tination que doit surveiller le MDB. description est une simple chaîne qui décrit le
MDB lorsqu’il est déployé. L’élément activationConfig sert à préciser les proprié-
tés de configuration ; il ne prend pas de paramètre et renvoie un tableau d’annotations
@ActivationConfigProperty.
@ActivationConfigProperty
JMS permet de configurer certaines propriétés comme les sélecteurs de messages, le
mode d’acquittement, les abonnements durables, etc. Dans un MDB, ces propriétés
peuvent être configurées via l’annotation facultative @ActivationConfigProperty.
Celle-ci peut être passée en paramètre à l’annotation @MessageDriven ; par rapport
à son équivalent JMS, elle est très simple puisqu’elle est formée d’une paire nom/
valeur (voir Listing 13.10).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
450 Java EE 6 et GlassFish 3
Chaque propriété d’activation est une paire nom/valeur interprétée par le fournisseur
de messages sous-jacent, qui l’utilise pour configurer le MDB. Le Tableau 13.5 énu-
mère quelques-unes des propriétés reconnues par OpenMQ (consultez la documen-
tation de votre fournisseur JMS si vous utilisez une autre implémentation).
Propriété Description
destinationType Type de destination, qui peut être TOPIC ou QUEUE.
destination Nom de la destination.
messageSelector Chaîne de sélecteur utilisée par le MDB.
acknowledgeMode Mode d’acquittement (AUTO_ACKNOWLEDGE par défaut).
subscriptionDurability Persistance de l’abonnement (NON_DURABLE par défaut).
subscriptionName Nom d’abonné du consommateur.
Comme tous les autres EJB que nous avons présentés au Chapitre 6, les MDB peu-
vent utiliser l’injection des dépendances pour obtenir des références à des ressources
comme des sources de données JDBC, des EJB ou d’autres objets. L’injection est
le mécanisme par lequel le conteneur insère automatiquement une référence d’objet
pour les attributs annotés. Ces ressources doivent être disponibles dans le conteneur
ou dans le contexte de l’environnement. Le code suivant est donc autorisé dans un
MDB :
@PersistenceContext
private EntityManager em;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 451
@EJB
private InvoiceBean invoice;
@Resource(name = "jms/javaee6/ConnectionFactory")
private ConnectionFactory connectionFactory;
Contexte MDB
L’interface MessageDrivenContext donne accès au contexte d’exécution que fournit
le conteneur à une instance MDB. Le conteneur passe l’interface MessageDriven-
Context à cette instance qui reste associée toute la durée de vie du MDB, ce qui lui
donne la possibilité d’annuler explicitement une transaction, de connaître le prin-
cipal de l’utilisateur, etc. Cette interface hérite de javax.ejb.EJBContext sans lui
ajouter de méthode supplémentaire.
En injectant une référence à son contexte, le MDB aura accès aux méthodes du
Tableau 13.6.
Méthode Description
getCallerPrincipal Renvoie l’objet java.security.Principal associé à l’appel.
getRollbackOnly Teste si la transaction courante a été marquée pour annulation.
getTimerService Renvoie l’interface javax.ejb.TimerService.
getUserTransaction Renvoie l’interface javax.transaction.UserTransaction à
utiliser pour délimiter les transactions. Seuls les MDB avec des
transactions gérées par les beans (BMT) peuvent appeler cette
méthode.
isCallerInRole Teste si l’appelant a le rôle indiqué.
Lookup Permet au MDB de rechercher des variables d’environnement
qui lui sont propres dans le contexte des noms JNDI.
setRollbackOnly Permet à l’instance de marquer la transaction courante pour
annulation. Seuls les MDB avec des transactions gérées par les
beans (BMT) peuvent appeler cette méthode.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
452 Java EE 6 et GlassFish 3
Prêt
onMessage()
Ce comportement est identique à celui des beans de session sans état (voir Cha-
pitre 8 pour plus de détails sur les méthodes de rappel et les intercepteurs) et vous
pouvez, comme avec les autres EJB, ajouter des intercepteurs à l’aide de l’annotation
@javax.ejb.AroundInvoke.
Comme on l’a expliqué dans la section "API JMS", plus haut dans ce chapitre, les
consommateurs peuvent recevoir un message de façon synchrone en bouclant en
attendant qu’il arrive ou de façon asynchrone en implémentant l’interface Message-
Listener. Par nature, les MDB sont conçus pour fonctionner comme des consom-
mateurs asynchrones : ils implémentent une interface d’écoute des messages qui est
déclenchée par le conteneur lorsqu’un message arrive.
Un MDB peut également consommer les messages de façon synchrone, mais ce n’est
pas conseillé : les consommateurs synchrones bloquent et occupent les ressources
du serveur (les EJB ne feront rien pendant qu’ils bouclent et le conteneur ne pourra
pas les libérer). Les MDB, comme les beans de session sans état, sont placés dans un
pool d’une certaine taille. Lorsque le conteneur a besoin d’une instance, il en extrait
une du pool et l’utilise. Si chaque instance entrait dans une boucle infinie, le pool
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 453
finirait par se vider et toutes les instances disponibles seraient occupées à boucler.
Le conteneur EJB pourrait alors créer plus d’instances de MDB et ainsi augmenter
la taille du pool et consommer de plus en plus de mémoire. Pour cette raison, les
beans de session et les MDB ne doivent pas être utilisés comme des consommateurs
synchrones. Le Tableau 13.7 présente les différents modes de réception des MDB et
des beans de session.
Les MDB peuvent devenir des producteurs de message, ce qui arrive souvent
lorsqu’ils sont utilisés dans un workflow puisqu’ils reçoivent un message d’une des-
tination, le traitent et l’envoient à une autre destination. Pour ajouter cette possibilité,
il faut utiliser l’API JMS.
Une destination et une fabrique de connexions peuvent être injectées avec une
annotation @Resource ou par une recherche JNDI, puis des appels aux méthodes de
l’objet javax.jms.Session permettent de créer et d’envoyer un message. Le code
de BillingMDB (voir Listing 13.12) surveille un sujet (jms/javaee6/Topic), reçoit
des messages (méthode onMessage()) et envoie un nouveau message dans une file
d’attente (jms/ javaee6/Queue).
@Resource(name = "jms/javaee6/Queue")
private Destination printingQueue;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
454 Java EE 6 et GlassFish 3
@Resource(name = "jms/javaee6/ConnectionFactory")
private ConnectionFactory connectionFactory;
private Connection connection;
@PostConstruct
private void initConnection() {
connection = connectionFactory.createConnection();
}
@PreDestroy
private void closeConnection() {
connection.close();
}
Ce MDB utilise la plupart des concepts que nous avons présentés. Il se sert d’abord
de l’annotation @MessageDriven pour définir le nom JNDI du sujet qu’il surveille
(mappedName = "jms/javaee6/Topic"). Dans cette même annotation, il définit un
ensemble de propriétés comme le mode d’acquittement et un sélecteur de message à
l’aide d’un tableau d’annotations @ActivationConfigProperty ; enfin, il implémente
MessageListener et sa méthode onMessage().
Ce MDB doit également produire un message. Il reçoit donc par injection les deux
objets administrés nécessaires : une fabrique de connexions et une destination (la
file nommée jms/ javaee6/Queue). Il peut alors créer et fermer une instance par-
tagée de javax.jms.Connection en utilisant des méthodes de rappel du cycle de
vie. La création d’une connexion étant coûteuse, placer ce code dans les méthodes
annotées par @PostConstruct et @PreDestroy garantit qu’elles n’auront lieu qu’à la
création et à la destruction du MDB.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 455
Enfin, la méthode métier qui envoie les messages (la méthode sendPrintingMes-
sage()) ressemble à ce que nous avons déjà vu : une session JMS est créée et sert
à créer un message texte et un producteur, puis le message est envoyé. Pour plus de
clarté, nous avons omis le traitement des exceptions.
Transactions
Les MDB étant des EJB (voir Chapitre 9 pour plus d’informations), ils peuvent donc
utiliser des transactions gérées par les beans (BMT) ou par le conteneur (CMT),
annuler explicitement une transaction avec la méthode MessageDrivenContext.
setRollbackOnly(), etc. Ceci dit, les MDB ont quelques spécificités qui méritent
d’être connues.
Lorsque l’on parle de transactions, on pense toujours aux bases de données rela-
tionnelles, mais d’autres ressources sont également transactionnelles : les systèmes
de messages, notamment. Deux opérations ou plus qui doivent réussir ou échouer
ensemble forment une transaction : avec les messages, si deux messages ou plus
sont envoyés, ils doivent tous réussir ou tous échouer. En pratique, le conteneur
lancera une transaction avant l’appel de la méthode onMessage() et la validera au
retour de cette méthode (sauf si la transaction a été marquée pour annulation avec
setRollbackOnly()).
Bien que les MDB soient transactionnels, ils ne peuvent pas s’exécuter dans le
contexte de transaction du client car ils n’ont pas de client ; personne n’appelle
explicitement leurs méthodes : ils se contentent de surveiller une destination et
de consommer les messages. Aucun contexte n’est passé d’un client à un MDB,
notamment pas le contexte transactionnel du client pour la méthode onMessage().
Le Tableau 13.8 compare les CMT avec les beans de session et les MDB.
Tableau 13.8 : Transactions MDB comparées avec celles des beans de session
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
456 Java EE 6 et GlassFish 3
Dans ce chapitre, nous avons omis le traitement des exceptions dans les extraits
de code car l’API JMS permettant de les gérer est très verbeuse. Elle définit douze
exceptions différentes qui héritent toutes de javax.jms.JMSException : tous les
appels de méthodes des connexions, des sessions, des consommateurs, des mes-
sages ou des producteurs lancent une JMSException ou l’une de ses sous-classes en
cas de problème.
Il est important de noter que JMSException est une exception contrôlée. Comme on
l’a déjà évoqué, la spécification EJB distingue deux types d’exceptions :
■■ Les exceptions d’applications. Ce sont des exceptions contrôlées qui héri-
tent d’Exception et qui ne provoquent pas l’annulation d’une transaction par le
conteneur.
■■ Les exceptions systèmes. Ce sont des exceptions non contrôlées qui héritent
de RuntimeException et qui provoquent l’annulation de la transaction par le
conteneur.
Le déclenchement d’une JMSException ne provoque donc pas l’annulation d’une
transaction. Si cette annulation est nécessaire, il faut appeler explicitement la méthode
setRollBackOnly() ou relancer une exception système (comme EJBException) :
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 457
Récapitulatif
Les concepts les plus importants des messages sont les modèles P2P et Publica-
tion-abonnement, les objets administrés (fabriques de connexion et destinations), la
façon de se connecter à un fournisseur et de produire ou consommer les messages,
les mécanismes de fiabilité utilisés par JMS et l’utilisation des composants gérés
par le conteneur (MDB) pour surveiller les destinations. Nous allons ici étudier
comment ces concepts fonctionnent ensemble à travers un exemple, le compiler et
l’assembler avec Maven et le déployer sur GlassFish.
Cet exemple utilise une classe autonome (OrderSender) qui envoie des messages
dans une file d’attente (jms/javaee6/Queue). Ces messages sont des objets représen-
tant une commande de livres et de CD (OrderDTO) qui ont plusieurs attributs, dont le
montant total de la commande. À l’autre extrémité de la file, un MDB (OrderMDB) ne
consomme que les commandes dont le montant excède 1 000 €. Le montant d’une
commande est passé en paramètre à la classe OrderSender.
Maven devant structurer le code en fonction des artéfacts finaux, la classe OrderSen-
der sera déployée dans un fichier jar, OrderMDB dans une autre et la classe OrderDTO
sera commune à ces deux fichiers.
OrderDTO
L’objet qui sera envoyé dans le message JMS est un POJO qui doit implémen-
ter l’interface Serializable. La classe OrderDTO présentée dans le Listing 13.13
donne quelques informations sur la commande, dont son montant total ; ce sont ces
objets qui seront placés dans un ObjectMessage JMS et envoyés d’OrderSender à
OrderMDB.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
458 Java EE 6 et GlassFish 3
OrderSender
La classe OrderSender du Listing 13.14 est un client autonome qui utilise l’API
JMS pour envoyer un ObjectMessage à la file jms/javaee6/Queue. Elle reçoit par
injection la fabrique de connexions ainsi que la destination ; sa méthode main() crée
une instance de la classe OrderDTO – notez que le totalAmount de la commande est
un paramètre passé à la classe (dans args[0]). La Session crée un ObjectMessage
et place la commande dans le corps du message avec message.setObject(order).
Le montant total est placé dans une propriété (message.setFloatProperty()) pour
être utilisé plus tard par un sélecteur.
@Resource(mappedName = "jms/javaee6/ConnectionFactory")
private static ConnectionFactory connectionFactory;
@Resource(mappedName = "jms/javaee6/Queue")
private static Queue queue;
try {
// Crée les artéfacts nécessaires à la connexion à la file
Connection connection= connectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);
message.setFloatProperty("orderAmount", totalAmount);
producer.send(message);
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 459
OrderMDB
La classe OrderMDB (voir Listing 13.15) est un MDB annoté par @MessageDriven
qui surveille la destination jms/javaee6/Queue. Il ne s’intéresse qu’aux commandes
supérieures à 1 000 € en utilisant pour cela un sélecteur (orderAmount > 1000).
Lorsqu’un message arrive, la méthode onMessage() le consomme, le transtype
en ObjectMessage et récupère son corps (msg.getObject()). Ici, on se contente
d’afficher le message, mais d’autres traitements sont possibles.
L’émetteur et le MDB doivent être assemblés dans des fichiers jar distincts et uti-
liser des structures de répertoires et des fichiers pom.xml différents. Le fichier pom.
xml d’OrderMDB présenté dans le Listing 13.16 est quasiment identique à celui
d’OrderSender.
Les MDB utilisant des annotations du paquetage EJB (@MessageDriven), il faut
déclarer la dépendance javax.ejb. La dépendance javax.jms, quant à elle, est
nécessaire pour accéder à l’API JMS (sessions, messages, etc.). Toutes les deux ont
une portée provided car GlassFish, en tant que conteneur EJB et fournisseur JMS,
fournit ces API lors de son exécution. Vous devez également informer Maven que
vous utilisez Java SE 6 en configurant en conséquence maven-compiler-plugin.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
460 Java EE 6 et GlassFish 3
<dependencies>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.ejb</artifactId>
<version>3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.jms</artifactId>
<version>3.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 461
JMS doit créer les objets administrés nécessaires à l’envoi et à la réception des mes-
sages. Chacun d’eux possède un nom JNDI afin que les clients puissent en obtenir
une référence (ici, nous nous servons de l’injection) :
■■ La fabrique de connexions s’appelle jms/javaee6/ConnectionFactory.
■■ La file d’attente s’appelle jms/javaee6/Queue.
Ces objets étant administrés, GlassFish doit être en cours d’exécution pour qu’ils
soient créés car OpenMQ s’exécute dans GlassFish. Si la commande asadmin est
dans votre path, lancez la commande suivante à l’invite de l’interpréteur :
asadmin create-jms-resource -–restype javax.jms.ConnectionFactory
„ jms/javaee6/ConnectionFactory
asadmin create-jms-resource --restype javax.jms.Queue
„ jms/javaee6/Queue
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
462 Java EE 6 et GlassFish 3
Exécution de l’exemple
Ce montant étant supérieur à 1 000 € (la limite définie dans le sélecteur de message),
l’OrderMDB devrait le recevoir et afficher le message (ce que vous pourrez vérifier en
consultant le journal de GlassFish). Si vous passez un montant inférieur à 1 000 €,
le MDB ne recevra pas le message :
appclient -client chapter13-Sender-1.0.jar 500
Résumé
Ce chapitre a montré que les messages sont une forme asynchrone, faiblement cou-
plée, de communication entre les composants. Le MOM peut être considéré comme
un tampon placé entre les systèmes qui ont besoin de produire et de consommer les
messages à leurs propres rythmes. C’est donc une architecture différente de celle de
RPC (comme RMI) où les clients doivent connaître les méthodes du service qu’ils
utilisent.
La première section de ce chapitre s’est intéressée à l’API JMS et à son vocabulaire.
Le modèle asynchrone est une API très puissante qui peut être utilisée dans les envi-
ronnements Java SE ou Java EE. Elle repose sur les modèles P2P ou Publication-
abonnement, sur les fabriques de connexions, les destinations, les connexions, les
sessions, les messages (en-têtes, propriétés, corps) de différents types (texte, objet,
dictionnaires, flux, octets), les sélecteurs et les mécanismes de fiabilité comme
l’acquittement ou la persistance.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 13 Envoi de messages 463
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
14
Services web SOAP
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
466 Java EE 6 et GlassFish 3
Figure 14.1
<<registry>>
Le consommateur UDDI
découvre le service via un
registre.
XML/HTTP
Consommateur Service web
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 467
■■ XML (Extensible Markup Language) est la base sur laquelle sont construits et
définis les services web (SOAP, WSDL et UDDI).
Grâce à ces technologies standard, les services web ont un potentiel quasiment illi-
mité. Les clients peuvent appeler un service qui peut être associé à n’importe quel
programme et accommoder n’importe quel type et structure de données pour échanger
des messages via XML.
UDDI
Les programmes qui interagissent avec un autre via le Web doivent pouvoir trouver
les informations leur permettant de s’interconnecter. UDDI fournit pour cela une
approche standardisée permettant de trouver les informations sur un service web et
sur la façon de l’invoquer.
UDDI est une base de registres de services web en XML, un peu comme les profes-
sionnels peuvent enregistrer leurs services dans les pages jaunes. Cet enregistrement
inclut le type du métier, sa localisation géographique, le site web, le numéro de
téléphone, etc. Les autres métiers peuvent ensuite parcourir cette base et retrouver
les informations sur un service web spécifique, qui contiennent des métadonnées
supplémentaires décrivant son comportement et son emplacement. Ces informations
sont stockées sous la forme de document WSDL : les clients peuvent lire ce document
afin d’obtenir l’information et invoquer le service.
WSDL
La base de registres UDDI pointe vers un fichier WSDL sur Internet, qui peut être
téléchargé par les consommateurs potentiels. WSDL est un langage de définition
d’interfaces (IDL) permettant de définir les interactions entre les consommateurs et
les services (voir Figure 14.2). C’est donc le composant central d’un service web
puisqu’il décrit le type du message, le port, le protocole de communication, les opé-
rations possibles, son emplacement et ce que le client peut en attendre. Vous pouvez
considérer WSDL comme une interface Java, mais écrite en XML.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
468 Java EE 6 et GlassFish 3
Pour garantir l’interopérabilité, l’interface standard du service web doit être standar-
disée, afin qu’un consommateur et un producteur puissent partager et comprendre
un message. C’est le rôle de WSDL ; SOAP, de son côté, définit la façon dont le
message sera envoyé d’un ordinateur à l’autre.
SOAP
SOAP est le protocole standard des services web. Il fournit le mécanisme de com-
munication permettant de connecter les services qui échangent des données au for-
mat XML au moyen d’un protocole réseau – HTTP le plus souvent. Comme WSDL,
SOAP repose fortement sur XML : un message SOAP est un document XML
contenant plusieurs éléments (une enveloppe, un corps, etc.).
SOAP est conçu pour fournir un protocole indépendant et abstrait, permettant de
connecter des services distribués. Ces services connectés peuvent être construits à
l’aide de n’importe quelle combinaison de matériels et de logiciels reconnaissant un
protocole de transport donné.
Protocole de transport
Pour qu’un consommateur puisse communiquer avec un service web, il faut qu’ils
puissent s’envoyer des messages. Les services web étant essentiellement utilisés sur
le Web, ils utilisent généralement HTTP, mais d’autres protocoles, comme HTTPS
(Secure HTTP), TCP/IP, SMTP (Simple Mail Transport Protocol), FTP (File Transfer
Protocol), etc. sont également possibles.
XML
XML est utilisé par la plate-forme Java EE pour les descripteurs de déploiement,
les métadonnées, etc. Pour les services web, XML sert également de technologie
d’intégration pour résoudre les problèmes d’indépendance des données et d’intero-
pérabilité. Il est utilisé non seulement pour le format des messages mais également
pour définir les services (WSDL) ou la façon dont ils sont échangés (SOAP). Des
schémas sont associés à ces documents XML pour valider les données échangées.
La persistance est essentiellement couverte par la spécification JPA. Pour les services
web, la situation est plus complexe car il faut prendre en compte de nombreuses
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 469
Les services web sont un moyen standard permettant aux entreprises de communi-
quer sur un réseau. Leurs précurseurs s’appellent CORBA (Common Object Request
Broker Architecture), initialement utilisé par les systèmes Unix et DCOM (Distribu-
ted Component Object Model), son rival Microsoft. À un niveau plus bas, on trouve
RPC (Remote Procedure Call) et, plus près du monde Java, RMI (Remote Method
Invocation).
Avant le Web, il était difficile de mettre d’accord les acteurs majeurs du logiciel sur
un protocole de transport. Lorsque HTTP est devenu un standard, il a également
obtenu petit à petit le statut de support de communication universel. À peu près en
même temps, XML est devenu un standard officiel lorsque le W3C (World Wide Web
Consortium) a annoncé que XML 1.0 pouvait être déployé dans les applications. En
1998, ces deux ingrédients – HTTP et XML – étaient prêts à travailler ensemble.
SOAP 1.0, lancé en 1998 par Microsoft, fut finalement livré à la fin de 1999. Il
permettait alors de modéliser les références typées et les tableaux dans un schéma
XML. En 2000, IBM commença à travailler sur SOAP 1.1 et WSDL fut soumis au
W3C en 2001. UDDI a été créé en 2000 par OASIS (Organization for the Advan-
cement of Structured Information Standards) afin de permettre aux entreprises de
publier et de retrouver les services web. Avec SOAP, WSDL et UDDI, les standards
de facto pour la création de services web étaient désormais en place.
En juin 2002, Java a introduit les services web avec JAX-RPC 1.0 (Java API for
XML-based RPC 1.0) et JAX-RPC 1.1 a été ajouté à J2EE 1.4 en 2003, mais cette
spécification était très touffue et difficile à utiliser. Avec l’arrivée de Java EE 5,
une toute nouvelle spécification JAX-WS 2.0 (Java API for XML-based Web Ser-
vices 2.0) vit le jour et devint le modèle préféré pour les services web. Actuellement,
Java EE 6 est fourni avec JAX-WS 2.2.
Spécifications Java EE
La maîtrise de tous les standards des services web exige de passer un peu de temps
à lire tout un lot de spécifications provenant du W3C, du JCP et d’OASIS.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
470 Java EE 6 et GlassFish 3
Le W3C est un consortium qui développe et gère les technologies web comme
HTML, XHTML, RDF, CSS, etc. Il nous intéresse ici car il s’occupe également des
technologies des services web : XML, XML Schema, SOAP et WSDL.
OASIS héberge plusieurs standards liés aux services web, comme UDDI, WS-Addres-
sing, WS-Security, WS-Reliability et bien d’autres.
Si l’on revient à Java, le JCP propose un ensemble de spécifications faisant partie
de Java EE 6 et Java SE 6 – notamment JAX-WS (JSR 224), Web Services 1.2
(JSR 109), JAXB 2.2 (JSR 222), Web Services Metadata 2.0 (JSR 181) et JAXR 1.0
(JSR 93). Mises ensemble, ces spécifications sont généralement désignées informel-
lement par le terme JWS (Java Web Services).
Au premier abord, ces listes de spécifications pourraient vous faire croire que
l’écriture d’un service web en Java est un exercice compliqué, notamment lorsqu’il
s’agit d’utiliser toutes ces API. En réalité, vous n’avez pas à vous soucier de toutes
ces technologies sous-jacentes (XML, WSDL, SOAP, HTTP, etc.) car JWS s’en
occupera pour vous.
JAX-WS 2.2
JAX-WS (JSR 224) est le nouveau nom de JAX-RPC, qui a été élagué de Java EE 6,
ce qui signifie que l’on a proposé sa suppression de Java EE 7.
JAX-WS 2.2 définit un ensemble d’API et d’annotations permettant de construire et
de consommer des services web en Java. Elle fournit les outils pour envoyer et rece-
voir des requêtes de services web via SOAP en masquant la complexité du proto-
cole. Ni le consommateur ni le service n’ont donc besoin de produire ou d’analyser
des messages SOAP car JAX-WS s’occupe du traitement de bas niveau. JAX-WS
dépend d’autres spécifications comme JAXB (Java Architecture for XML Binding).
JAXB 2.2
Les services web envoient des requêtes et des réponses en échangeant des messages
XML. En Java, il existe plusieurs API de bas niveau pour traiter les documents XML
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 471
WS-Metadata 2.0
Web Services Metadata (WS-Metadata, spécification JSR 181) fournit des annota-
tions qui facilitent la définition et le déploiement des services web. Le but principal de
la JSR 181 consiste à simplifier le développement des services web : elle fournit des
outils permettant d’associer WSDL avec les interfaces Java et vice versa au moyen
d’annotations. Ces dernières peuvent être utilisées avec des classes Java ou des EJB.
JAXR 1.0
La spécification JAXR (Java API for XML Registries) définit un ensemble standard
d’API permettant aux clients Java d’accéder à UDDI. Comme JAX-RPC, JAXR
est une spécification élaguée, dont la suppression a été proposée pour la prochaine
version de Java EE. Si cette suppression est acceptée, JAXR continuera d’évoluer,
mais en dehors de Java EE.
Implémentation de référence
Metro est non pas une spécification Java EE mais une implémentation de référence
open-source des spécifications des services web Java. Elle est formée de JAX-WS
et de JAXB et reconnaît également les API JAX-RPC historiques. Metro permet
de créer et de déployer des services web et des consommateurs sécurisés, fiables,
transactionnels et interopérables. Bien que la pile Metro soit produite par la commu-
nauté GlassFish, elle peut être utilisée en dehors de celui-ci, dans un environnement
Java EE ou Java SE.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
472 Java EE 6 et GlassFish 3
Comme les entités ou les EJB, un service web utilise le modèle de POJO annoté
avec une politique de configuration par exception. Si tous les choix par défaut vous
conviennent, ceci signifie qu’un service web peut se réduire à une simple classe Java
annotée par @javax.jws.WebService. Le service CardValidator n’a qu’une seule
méthode pour valider une carte de crédit :validate() prend une carte de crédit en
paramètre et renvoie true ou false selon qu’elle est valide ou non. Ici, nous suppo-
sons que les cartes de crédit ayant un numéro impair sont valides et que celles avec
un numéro pair ne le sont pas.
Un objet CreditCard (voir Listing 14.2) est échangé entre le consommateur et le
service web. Lorsque nous avons décrit l’architecture d’un service web, nous avons
vu que les données échangées devaient être des documents XML : on a donc besoin
d’une méthode pour transformer un objet Java en XML et c’est là que JAXB entre en
jeu avec ses annotations et son API. L’objet CreditCard doit simplement être annoté
par @javax.xml.bind.annotation.XmlRootElement pour que JAXB le transforme
en XML et réciproquement.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 473
Grâce aux annotations JAXB, il n’est pas nécessaire d’écrire de code de bas niveau
pour effectuer l’analyse XML car elle a lieu en coulisse – le service web et le
consommateur manipulent un objet Java. Le consommateur peut être une classe
Java qui crée une instance de CreditCard puis invoque le service web, comme dans
le Listing 14.3.
CardValidator cardValidator =
new CardValidatorService().getCardValidatorPort();
cardValidator.validate(creditCard);
}
}
Comme vous l’avez compris, XML est utilisé pour échanger les données et définir
les services web via WSDL et les enveloppes SOAP. Pourtant, dans le Listing 14.3,
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
474 Java EE 6 et GlassFish 3
un consommateur invoquait un service web sans qu’il n’y ait trace de XML car ce
consommateur ne manipulait que des interfaces et des objets Java distants qui, à
leur tour, gèrent toute la plomberie XML et les connexions réseau. On manipule des
classes Java à un endroit de la chaîne et des documents XML à un autre – le rôle de
JAXB est de faciliter cette correspondance bidirectionnelle.
Java fournit plusieurs moyens de manipuler du XML, qui vont des API classiques
(javax.xml.stream.XmlStreamWriter et java.beans.XMLEncoder) à des modèles
de bas niveau plus complexes comme SAX (Simple API for XML), DOM (Docu-
ment Object Model) ou JAXP (Java API for XML Processing). JAXB, qui repose
sur les annotations, offre un niveau d’abstraction supérieur à celui de SAX et DOM.
JAXB définit un standard permettant de lier les représentations Java à XML et réci-
proquement. Il gère les documents XML et les définitions des schémas XML (XSD)
de façon transparente et orientée objet, qui masque la complexité du langage XSD.
À part l’annotation @XmlRootElement, le code du Listing 14.4 est celui d’une classe
Java normale. Grâce à cette annotation et à un mécanisme de sérialisation. JAXB
est capable de créer une représentation XML d’une instance de CreditCard, comme
celle qui est présentée dans le Listing 14.5.
La sérialisation, ici, consiste à transformer un objet en XML, mais JAXB permet éga-
lement de faire l’inverse : la désérialisation prendrait ce document XML en entrée et
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 475
créerait un objet CreditCard à partir des valeurs de ce document. JAXB peut aussi
produire automatiquement le schéma qui validerait automatiquement la structure
XML de la carte de crédit afin de garantir qu’elle est correcte et que les types des
données conviennent. Le Listing 14.6 montre la définition du schéma XML (XSD)
de la classe CreditCard.
<xs:complexType name="creditCard">
<xs:sequence>
<xs:element name="controlNumber" type="xs:int"
minOccurs="0"/>
<xs:element name="expiryDate" type="xs:string"
minOccurs="0"/>
<xs:element name="number" type="xs:string" minOccurs="0"/>
<xs:element name="type" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Les espaces de noms créent des préfixes uniques pour les éléments des documents
ou des applications qui sont utilisés ensemble. Leur but principal consiste à éviter
les conflits qui pourraient survenir lorsqu’un même nom d’élément apparaît dans
plusieurs documents (le marqueur <element>, par exemple, pourrait apparaître dans
plusieurs documents et avoir des significations différentes). Ceci pose un problème
non négligeable pour les services web car ils manipulent plusieurs documents en
même temps (l’enveloppe SOAP, le document WSDL, etc.) : les espaces de noms
sont donc très importants pour les services web.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
476 Java EE 6 et GlassFish 3
Liaison
respecte instancie
d'exécution
Framework
<creditCard>
<controlNumber> Désérialisation
<expiryDate> ( +validation)
<number>
<type>VISA</type>
</creditCard> Sérialisation
(+validation)
Document XML Objets
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 477
JAXBContext context =
JAXBContext.newInstance(CreditCard.class);
Marshaller m = context.createMarshaller();
m.marshal(creditCard, writer);
System.out.println(writer.toString());
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
478 Java EE 6 et GlassFish 3
Ces deux scénarios sont très importants pour les services web – un service web peut
produire ses fichiers WSDL, en fonction desquels un consommateur pourra produire
un ensemble de classes Java de façon transparente et portable.
Annotations
Par bien des aspects, JAXB ressemble à JPA. Cependant, au lieu de faire correspondre
les objets à une base de données, JAXB les lie à un document XML. Comme JPA,
JAXB définit un certain nombre d’annotations (dans le paquetage javax.xml.bind.
annotation) afin de configurer cette association et s’appuie sur la configuration par
exception pour alléger le travail du développeur. Comme le montre le Listing 14.8,
l’équivalent de @Entity des objets persistants est l’annotation @XmlRootElement de
JAXB.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 479
Listing 14.9 : Schéma de carte de crédit avec des attributs et des valeurs par défaut
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="creditCard">
<xs:sequence>
<xs:element name="expiry-date" type="xs:string"
default="01/10" minOccurs="0"/>
<xs:element name="type" type="xs:string" minOccurs="0"/>
<xs:element name="control-number" type="xs:int"
minOccurs="0"/>
<xs:sequence>
<xs:attribute name="number" type="xs:string" use="required"/>
</xs:complexType>
</xs:schema>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
480 Java EE 6 et GlassFish 3
Annotation Description
@XmlAccessorType Contrôle si les attributs ou les getters doivent être mis en
correspondance (FIELD, NONE, PROPERTY, PUBLIC_MEMBER).
@XmlAttribute Traduit un attribut ou un getter en attribut XML de type simple
(String, Boolean, Integer, etc.).
@XmlElement Traduit un attribut ou un getter non statique et non transitoire en
élément XML.
@XmlElements Agit comme un conteneur de plusieurs annotations @XmlElement.
@XmlEnum Traduit une énumération en XML.
@XmlEnumValue Identifie une constante d’énumération.
@XmlID Identifie le champ clé d’un élément XML (de type String) qui pourra
servir à désigner un élément avec l’annotation @XmlIDREF (concepts
d’ID et d’IDREF des schémas XML).
@XmlIDREF Traduit une propriété en IDREF XML dans le schéma.
@XmlList Traduit une propriété en liste.
@XmlMimeType Identifie une représentation textuelle du type MIME d’une propriété.
@XmlNs Identifie un espace de noms XML.
@XmlRootElement Annotation exigée pour toute classe liée à l’élément racine du
document XML.
@XmlSchema Traduit un nom de paquetage en espace de noms XML.
@XmlTransient Demande à JAXB de ne pas lier un attribut (analogue au mot-clé
transient de Java ou à l’annotation @Transient de JPA).
@XmlType Marque une classe comme étant un type complexe dans le schéma
XML.
@XmlValue Permet de traduire une classe en un contenu ou un type de schéma
simple.
Grâce à ces annotations, vous pouvez traduire des objets en un schéma XML spé-
cifique, ce qui est parfois nécessaire avec les services web existants. JPA définit
un ensemble d’annotations permettant d’adapter chaque partie de la traduction
(colonnes, tables, clés étrangères, etc.) pour pouvoir associer des entités à une base
de données déjà établie, et il en va de même avec les services web qui sont décrits
par un fichier WSDL écrit en XML : s’il s’agit d’un service existant, son WSDL ne
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 481
peut pas être modifié – les annotations JAXB permettent alors de le faire corres-
pondre à des objets.
INFO
Dans cette section, nous avons plusieurs fois mentionné JPA car les technologies JPA et JAXB
reposent fortement sur les annotations et permettent de traduire des objets vers différents
supports (bases de données ou XML). En termes d’architecture, les entités ne devraient servir
qu’à associer des données à une base de données et les classes JAXB, à traduire des données
en XML. Parfois, cependant, le même objet a besoin d’avoir une représentation sous forme
de base de données et sous forme XML : il est alors techniquement possible d’annoter la
même classe avec @Entity et @XmlRootElement, même si cela n’est pas vraiment conseillé.
validateResponse
WSDL
Les documents WSDL sont hébergés dans le conteneur de service web et utilisent
XML pour décrire ce que fait un service, comment appeler ses opérations et où le
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
482 Java EE 6 et GlassFish 3
trouver. Ce document XML respecte une structure bien établie, formée de plusieurs
parties (voir Listing 14.11). Le service web CardValidator, par exemple, utilise les
éléments suivants :
■■ <definitions> est l’élément racine de WSDL. Il précise les déclarations des
espaces de noms visibles dans tout le document.
■■ <types> définit les types de données utilisés par les messages. Ici, c’est la défi-
nition du schéma XML (CardValidatorService?xsd=1) qui décrit le type des
paramètres de la requête adressée au service (un objet CreditCard) et le type de
la réponse (un Boolean).
■■ <message> définit le format des données échangées entre un consommateur du
service web et le service lui-même. Ici, il s’agit de la requête (la méthode vali-
date) et de la réponse (validateResponse).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 483
<output message="tns:validateResponse"/>
</operation>
</portType>
<binding name="CardValidatorPortBinding"
type="tns:CardValidator">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document"/>
<operation name="validate">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="CardValidatorService">
<port name="CardValidatorPort"
binding="tns:CardValidatorPortBinding">
<soap:address location =
„ "http://localhost:8080/chapter14/CardValidatorService"/>
</port>
</service>
</definitions>
L’élément <xsd:import namespace> fait référence à un schéma XML qui doit être
disponible sur le réseau pour les clients du WSDL. Le Listing 14.12 montre que ce
schéma définit les types utilisés par le service web (structure d’un objet CreditCard
avec un numéro, une date d’expiration, etc.).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
484 Java EE 6 et GlassFish 3
<xs:complexType name="validateResponse">
<xs:sequence>
<xs:element name="return" type="xs:boolean"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Ce fichier WSDL et ce schéma sont généralement produits par des outils fournis
avec JAX-WS qui transforment les métadonnées en XML.
SOAP
Alors que WSDL décrit une interface abstraite du service web, SOAP fournit une
implémentation concrète en définissant la structure XML des messages échangés.
Dans le cadre du Web, SOAP est une structure de messages pouvant être délivrés par
HTTP (ou d’autres protocoles de communication) – la liaison HTTP de SOAP contient
quelques en-têtes d’extension HTTP standard. Cette structure de message est décrite
en XML. Au lieu d’utiliser HTTP pour demander une page web à partir d’un navi-
gateur, SOAP envoie un message XML via une requête HTTP et reçoit une réponse
HTTP. Un message SOAP est un document XML contenant les éléments suivants :
■■ <Envelope>. Définit le message et l’espace de noms utilisés dans le document.
Il s’agit de l’élément racine obligatoire.
■■ <Header>. Contient les attributs facultatifs du message ou l’infrastructure spéci-
fique à l’application, comme les informations sur la sécurité ou le routage réseau.
■■ <Body>. Contient le message échangé entre les applications.
■■ <Fault>. Fournit des informations sur les erreurs qui surviennent au cours du
traitement du message. Cet élément est facultatif.
Seuls l’enveloppe et le corps sont obligatoires. Dans notre exemple, une application
cliente appelle le service web pour valider une carte de crédit (une enveloppe SOAP
pour la requête) et reçoit un booléen indiquant si cette carte est valide ou non (une
autre enveloppe SOAP pour la réponse). Les Listings 14.13 et 14.14 montrent les
structures de ces deux messages SOAP.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 485
<soap:Body>
<cc:validate>
<arg0>
<controlNumber>1234</controlNumber>
<expiryDate>10/10</expiryDate>
<number>9999</number>
<type>VISA</type>
</arg0>
</cc:validate>
</soap:Body>
</soap:Envelope>
Nous venons de voir un document WSDL simple, ainsi qu’une requête et une réponse
SOAP. Lorsque les services web proposent plusieurs opérations avec des paramètres
complexes, ces documents XML deviennent un véritable cauchemar pour le déve-
loppeur ; heureusement, JAX-WS leur facilite la vie en masquant cette complexité.
Ceci dit, vous devrez hélas parfois vous plonger dans la structure WSDL.
Le document WSDL étant le contrat qui lie le consommateur et le service, il peut ser-
vir à écrire le code Java pour ces deux parties : c’est ce que l’on appelle la méthode
descendante, également méthode par contrat car elle part du contrat (le WSDL) en
définissant les opérations, les messages, etc. Lorsque le consommateur et le fournis-
seur sont d’accord sur le contrat, on peut implémenter les classes Java en fonction
de celui-ci. Metro fournit quelques outils permettant de produire des classes à partir
d’un document WSDL.
Dans l’autre approche, la méthode ascendante, la classe de l’implémentation existe
déjà et il suffit de créer le WSDL. Là encore, Metro dispose d’outils pour effectuer
cette opération. Dans les deux cas, le code doit parfois être ajusté pour correspondre
au WSDL ou vice versa, et c’est là que JAX-WS vient à votre secours : grâce à un
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
486 Java EE 6 et GlassFish 3
Le modèle JAX-WS
Comme la plupart des composants de Java EE 6, les services web s’appuient sur le
paradigme de la configuration par exception. Seule l’annotation @WebService est
nécessaire pour transformer un POJO en service web, mais cette classe doit respecter
les règles suivantes :
■■ Elle doit être annotée par @javax.jws.WebService ou son équivalent XML dans
un descripteur de déploiement.
■■ Pour transformer un service web en entité EJB, la classe doit être annotée par
@javax.ejb.Stateless (voir Chapitre 7).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 487
Annotations
Au niveau du service, les systèmes sont définis en termes de messages XML, d’opé-
rations WSDL et de messages SOAP. Cependant, au niveau Java, les applications
sont décrites en termes d’objets, d’interfaces et de méthodes. Il est donc nécessaire
d’effectuer une traduction des objets Java vers les opérations WSDL. L’environne-
ment d’exécution de JAXB utilise les annotations pour savoir comment sérialiser/
désérialiser une classe vers/à partir de XML. Généralement, ces annotations sont
cachées au développeur du service web. De même, JWS se sert d’annotations pour
déterminer comment sérialiser un appel de méthode vers un message de requête
SOAP et comment désérialiser une réponse SOAP vers une instance du type du
résultat de la méthode.
La spécification WS-Metadata (JSR 181) définit deux sortes d’annotations :
■■ Les annotations de traduction WSDL. Elles appartiennent au paquetage
javax.jws et permettent de modifier les associations entre WSDL et Java. Les
annotations @WebMethod, @WebResult, @WebParam et @OneWay sont utilisées sur le
service web pour adapter la signature des méthodes exposées.
■■ Les annotations de traduction SOAP. Elles appartiennent au paquetage javax.
jws.soap et permettent d’adapter la liaison SOAP (@SOAPBinding et @SOAP
MessageHandler).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
488 Java EE 6 et GlassFish 3
Comme toutes les autres spécifications de Java EE 6, ces annotations peuvent être
redéfinies par un descripteur de déploiement XML facultatif (webservices.xml).
Les sections qui suivent décrivent plus précisément chacune d’elles.
@WebService
L’annotation @javax.jws.WebService marque une classe ou une interface Java
comme étant un service web. Lorsqu’elle est utilisée directement sur la classe
(comme dans les exemples que nous avons présentés jusqu’à maintenant), le pro-
cesseur d’annotations du conteneur produira l’interface – les deux extraits de code
qui suivent sont donc équivalents. Voici l’annotation sur une classe :
@WebService
public class CardValidator {
Cette annotation possède un certain nombre d’attributs (voir Listing 14.15) per-
mettant de personnaliser le nom du service web dans le fichier WSDL (éléments
<wsdl:portType> ou <wsdl:service>) et son espace de noms, ainsi que l’empla
cement du fichier WSDL lui-même (attribut wsdlLocation).
Lorsque l’on utilise @WebService, toutes les méthodes publiques du service web qui
n’utilisent pas l’annotation @WebMethod sont exposées.
@WebMethod
Par défaut, toutes les méthodes publiques d’un service web sont exposées dans le
WSDL et utilisent toutes les règles d’association par défaut. L’annotation @javax.jws.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 489
@WebMethod(operationName = "ValidateCreditCard")
public boolean validate(CreditCard creditCard) {
// Logique métier
}
@WebMethod(exclude = true)
public void validate(String ccNumber) {
// Logique métier
}
}
@WebResult
L’annotation @javax.jws.WebResult fonctionne en relation avec @WebMethod pour
contrôler le nom de la valeur renvoyée par le message dans le WSDL. Dans le Lis-
ting 14.17, le résultat de la méthode validate() est renommé en IsValid.
@WebMethod
@WebResult(name = "IsValid")
public boolean validate(CreditCard creditCard) {
// Logique métier
}
}
@WebParam
@javax.jws.WebParam, dont l’API est présentée dans le Listing 14.18, ressemble
à @WebResult car elle personnalise les paramètres des méthodes du service web.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
490 Java EE 6 et GlassFish 3
@WebMethod
public boolean validate(@WebParam(name = "Credit-Card")
„ // Logique métier
}
}
@OneWay
L’annotation @OneWay peut être utilisée avec les méthodes qui ne renvoient aucun
résultat, comme celles de type void. Cette annotation n’a aucun élément et peut être
considérée comme une interface de marquage informant le conteneur que l’appel
de cette méthode peut être optimisé (en utilisant un appel asynchrone, par exemple)
puisqu’il n’y a pas de valeur de retour.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 491
@WebMethod(operationName = "ValidateCreditCard")
@WebResult(name = "IsValid")
public boolean validate(@WebParam(name = "CreditCard")
„ CreditCard creditCard) {
String lastDigit = creditCard.getNumber().substring(
„ creditCard.getNumber().length() - 1,
„ creditCard.getNumber().length());
if (Integer.parseInt(lastDigit) % 2 != 0) {
return true;
} else {
return false;
}
}
@WebMethod(exclude = true)
public void validate(String ccNumber) {
// Logique métier
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
492 Java EE 6 et GlassFish 3
<binding name="ValidatorPortBinding"
type="tns:CreditCardValidator">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document"/>
<operation name="ValidateCreditCard">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="CardValidatorService">
<port name="ValidatorPort" binding="tns:ValidatorPortBinding">
<soap:address location="REPLACE_WITH_ACTUAL_URL"/>
</port>
</service>
</definitions>
<xs:element name="ValidateCreditCard"
type="tns:ValidateCreditCard"/>
<xs:element name="ValidateCreditCardResponse"
type="tns:ValidateCreditCardResponse"/>
<xs:complexType name="ValidateCreditCard">
<xs:sequence>
<xs:element name="CreditCard"
type="tns:creditCard" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="creditCard">
<xs:sequence>
<xs:element name="controlNumber"
type="xs:int" minOccurs="0"/>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 493
<xs:element name="expiryDate"
type="xs:string" minOccurs="0"/>
<xs:element name="number" type="xs:string" minOccurs="0"/>
<xs:element name="type" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ValidateCreditCardResponse">
<xs:sequence>
<xs:element name="IsValid" type="xs:boolean"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Prêt
Appel de méthode
Comme elles s’exécutent dans un conteneur, les extrémités de servlet et EJB auto-
risent l’injection des dépendances et les méthodes de rappel du cycle de vie : si elle
existe, le conteneur appellera la méthode de rappel @PostConstruct lorsqu’il crée
une instance d’un service web et la méthode de rappel @PreDestroy lorsqu’il la
détruit.
Une différence entre les extrémités de servlet et les extrémités EJB est que ces
dernières peuvent utiliser des intercepteurs, qui sont l’implémentation Java EE du
concept de programmation orientée aspects (POA) décrit au Chapitre 8.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
494 Java EE 6 et GlassFish 3
Méthode Description
getMessageContext Renvoie le MessageContext pour la requête en cours de
traitement au moment de l’appel. Permet d’accéder aux en-têtes
du message SOAP, etc.
Vous pouvez invoquer un service web en utilisant le fichier WSDL et certains outils
de génération de classes Java relais (stubs). L’appel d’un service web ressemble à
l’appel d’un objet distribué avec RMI : comme ce dernier, JAX-WS permet au pro-
grammeur d’utiliser un appel de méthode local pour invoquer un service se trouvant
sur un autre hôte. La différence est qu’un service web sur l’hôte distant peut être
écrit dans un autre langage de programmation (notez cependant que vous pouvez
aussi appeler du code non Java avec RMI-IIOP). Le fichier WSDL établit le contrat
entre le consommateur et le service, et Metro fournit un outil de conversion WSDL
vers Java (wsimport) qui produit des classes et des interfaces Java à partir du code
WSDL – ces interfaces sont appelées SEI (service endpoint interfaces) car ce sont
des représentations Java d’une extrémité de service web (servlet ou EJB). Cette SEI
agit comme un proxy qui route l’appel Java local vers le service web distant via
HTTP ou d’autres protocoles de transport.
Lorsqu’une méthode de ce proxy est appelée (voir Figure 14.6), elle convertit ses
paramètres en message SOAP (la requête) et l’envoie à l’extrémité du web service.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 495
Pour obtenir le résultat, la réponse SOAP est reconvertie en une instance du type ren-
voyé. Pour l’utiliser, il n’est pas nécessaire de connaître le fonctionnement interne
du proxy ni d’étudier son code. Avant de compiler le consommateur client, il faut
produire la SEI afin d’obtenir la classe proxy pour l’appeler dans le code.
<<component>> <<component>>
Consommateur Service web
WSDL
Java SOAP / HTTP Java
interface interface
<<component>> <<component>>
validate
Proxy consommateur Extrémité de service web
validateResponse
Figure 14.6
Un consommateur appelle un service web via un proxy.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
496 Java EE 6 et GlassFish 3
Le lasses produites par l’outil wsimport peuvent être directement utilisées de la
façon suivante :
CardValidatorService cardValidatorService =
new CardValidatorService();
CardValidator cardValidator =
cardValidatorService.getCardValidatorPort(); cardValidato
r.validate(creditCard);
INFO
Les outils wsimport et wsgen sont fournis avec le JDK 1.6. wsimport prend en entrée un
fichier WSDL et produit les artéfacts JAX-WS – une SEI notamment. wsgen lit une classe
extrémité de service web et produit le WSDL. Vous pouvez accéder directement à ces outils
ou passer par l’interface en ligne de commande de GlassFish, par une tâche Ant ou par une
extension Maven.
Récapitulatif
Nous allons rassembler tous ces concepts en écrivant un service web et un consom-
mateur, puis en les déployant et en les testant avec GlassFish. Nous utiliserons pour
cela les annotations JAXB et JAX-WS et produirons une SEI avec le but wsimport
de Maven. Plusieurs étapes sont nécessaires pour écrire un service web : nous les
présenterons en même temps que le service CardValidator.
Le service web CardValidator teste si une carte de crédit est valide. Il n’a qu’une
seule méthode qui prend un objet CreditCard en paramètre, applique un algorithme
et renvoie true si cette carte est valide, false sinon. Une fois que ce service a été
déployé dans GlassFish, on utilise wsimport pour produire les artéfacts nécessaires
au consommateur. Celui-ci peut ensuite invoquer le service web pour valider des
cartes de crédit.
Nous utiliserons deux projets Maven : l’un pour assembler le service web dans un
fichier war (chapter14- service-1.0.war), l’autre pour assembler le consommateur
dans un fichier jar (chapter14-consumer- 1.0.jar).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 497
La classe CreditCard
CardValidator (voir Listing 14.24) est également un POJO, mais annoté par l’an-
notation @WebService de JAX-WS. Ce n’est pas une extrémité EJB car elle n’a
pas d’annotation @Stateless : c’est donc une extrémité servlet qui sera déployée
dans un fichier war (chapter14-service-1.0.war). Elle possède une méthode vali-
date() qui prend un objet CreditCard en paramètre et dont l’algorithme teste si la
carte est valide en fonction de son numéro : les numéros impairs sont considérés
comme valides, les pairs, comme non valides. Cette méthode renvoie un booléen.
if (Integer.parseInt(lastDigit) % 2 != 0) {
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
498 Java EE 6 et GlassFish 3
return true;
} else {
return false;
}
}
Pour rester simple, nous n’utilisons pas d’autres associations Java vers WSDL : il
n’y a donc pas d’annotation @WebMethod, @WebResult ou @WebParam, ce qui nous
permet de constater la simplicité d’écriture d’un service web avec les règles d’asso-
ciation par défaut.
Le service web CardValidator doit être compilé et assemblé dans un fichier war
(<packaging>war</packaging>). Le fichier pom.xml du Listing 14.25 déclare la
dépendance jaxws-rt pour utiliser la version 2.2 de JAX-WS. Java SE 6 est fourni
avec une implémentation de JAXB et JAX-WS : préciser la version 1.6 dans l’élé-
ment maven-compiler-plugin de Maven devrait suffire à indiquer explicitement que
vous voulez utiliser Java SE 6 (<source>1.6</source>) mais, si vous voulez contrô-
ler les versions des dépendances et que vous exigez absolument la version 2.2 de
JAX-WS, il est préférable d’ajouter explicitement la dépendance jaxws-rt.
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 499
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Les descripteurs de déploiement étant facultatifs avec Java EE 6, nous n’avons pas
besoin de fichier web.xml ou webservices.xml. Cependant, Maven nous obligeant
quand même à ajouter un fichier web.xml dans un war, nous devons modifier l’at-
tribut failOnMissingWebXml de maven-war-plugin en le mettant à false ; sinon
Maven échouera lors de la compilation.
Pour compiler et assembler le service web, il suffit d’ouvrir une ligne de commande
dans le répertoire contenant le fichier pom.xml et d’entrer la commande Maven
suivante :
mvn package
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
500 Java EE 6 et GlassFish 3
Il est intéressant de noter que GlassFish reconnaît le module web (le fichier war aurait
pu contenir des pages web, des servlets, etc.) comme étant un service web. Si vous
utilisez la console d’administration de GlassFish présentée à la Figure 14.7 (http://
localhost:4848/), vous constaterez que chapter14-service-1.0 est déployé sous
Applications -> Web Applications et que le service web CardValidator est déployé
sous le nœud Web Services.
Figure 14.7
Services web déployés dans la console d’administration de GlassFish.
Si vous cliquez sur le lien View WSDL de cette page, une fenêtre du navigateur
affichera l’URL suivante, qui présente le WSDL du service web (voir Figure 14.8) :
http://localhost:8080/chapter14-service-1.0/CardValidatorService?wsdl
Il est intéressant de noter que vous n’avez pas créé ce WSDL et que vous ne l’avez
pas non plus déployé dans le fichier war : c’est la pile Metro qui le produit automa-
tiquement en se servant des annotations contenues dans le service web.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 501
Figure 14.8
Le WSDL produit par Metro.
Le service web est désormais en ligne et nous savons où trouver son WSDL. Grâce
à lui et à l’aide de l’outil wsimport, le consommateur pourra produire les artéfacts
nécessaires à l’appel du service. Le Listing 14.26 montre le code du consommateur.
@WebServiceRef
private static CardValidatorService cardValidatorService;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
502 Java EE 6 et GlassFish 3
creditCard.setControlNumber(1234);
CardValidator cardValidator =
cardValidatorService.getCardValidatorPort();
System.out.println(cardValidator.validate(creditCard));
}
}
Cette classe Main crée une instance de CreditCard, initialise quelques données,
obtient une référence au service web, invoque sa méthode validate() et affiche
le résultat (true ou false selon que la carte est valide ou non). Le point important
est que le consommateur n’a aucune de ces classes : CardValidatorService, Card–
Validator et CreditCard lui sont totalement inconnues. Ce code ne pourra donc pas
être compilé tant que ces classes n’ont pas été générées.
Avant de compiler la classe Main du consommateur, nous devons créer les artéfacts
avec l’outil wsimport. Heureusement, Maven possède un but wsimport qui est exé-
cuté automatiquement au cours de la phase generate-sources du cycle de vie –
comme on l’a expliqué au Chapitre 1, cette phase sert à produire le code et s’exécute
avant la compilation. La seule chose à faire consiste donc à informer le but wsimport
de l’emplacement du document WSDL. Lorsque nous avons déployé le web service
dans GlassFish et que nous avons demandé l’affichage du WSDL, nous avons pu
constater que ce document se trouvait à l’URL suivante :
http://localhost:8080/chapter14-service-1.0/CardValidatorService?wsdl
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 503
<groupId>com.apress.javaee6</groupId>
<artifactId>chapter14-consumer</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifest>
<mainClass>com.apress.javaee6.chapter14.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
<configuration>
<wsdlUrls>
<wsdlUrl>
http://localhost:8080/chapter14-service-1.0/CardValidatorService?wsdl
</wsdlUrl>
</wsdlUrls>
<keep>true</keep>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
504 Java EE 6 et GlassFish 3
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Pour mieux comprendre ce qui se passe en coulisse, commençons par créer les arté-
facts à l’aide de la commande Maven suivante :
mvn clean generate-sources
parsing WSDL...
generating code...
[INFO]----------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO]----------------------------------------
Si vous êtes curieux, vous pouvez vous rendre dans le répertoire target\
jaxws\wsimport\java et vérifier les classes qui ont été produites. Vous y trouve-
rez bien sûr les classes CardValidator, CardValidatorService et CreditCard mais
également une classe pour la requête SOAP (Validate) et une autre pour la réponse
(ValidateResponse). Ces classes sont remplies d’annotations JAXB et JAXW car
elles sérialisent l’objet CreditCard et se connectent au service web distant. Ne vous
souciez pas du code produit. Le fichier jar peut être compilé et assemblé :
mvn package
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 14 Services web SOAP 505
Cette commande appellera le service web via HTTP et obtiendra une réponse indi-
quant si la carte de crédit est valide ou non.
Résumé
Les services web sont utilisés par l’intégration B2B (business-to-business) et sont
une technologie essentielle pour SOA. Amazon, eBay, Google, Yahoo! fournissent
des services web à leurs utilisateurs et de nombreuses sociétés les utilisent beau-
coup en interne. Dans ce chapitre, nous avons présenté plusieurs standards (UDDI,
WSDL, SOAP, XML, etc.) et nous nous sommes intéressés aux spécifications de
Java EE qui les prennent en charge (JAX-WS, JAXB, WS-Metadata, etc.).
JAXB (Java Architecture for XML Binding) définit un standard pour lier les repré-
sentations Java à XML et vice versa. Elle fournit un haut niveau d’abstraction car
elle repose sur les annotations. Bien que JAXB puisse être utilisée avec n’importe
quel type d’application Java, elle s’intègre particulièrement bien dans l’espace des
services web car les informations échangées sont écrites en XML.
Puis nous avons étudié WSDL et SOAP. Ces spécifications sont vitales pour les
services web car elles décrivent, respectivement, leurs interfaces et les messages
échangés.
JAX-WS suit un modèle de développement simple et n’utilise qu’un petit nombre
d’annotations pour ajuster la traduction de Java en WSDL. L’écriture d’un service
web (extrémité de servlet ou d’EJB) ou d’un consommateur en est donc d’autant plus
simple puisqu’il suffit d’un POJO annoté avec, éventuellement, des descripteurs de
déploiement.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
506 Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
15
Services web REST
La pile des services web (SOAP, WSDL, WS-*) décrite au chapitre précédent four-
nit à la fois une interopérabilité pour l’injection des messages et le style RPC. Avec
l’avènement du Web 2.0, des frameworks web comme Rails sont apparus et un
nouveau type de services web est devenu à la mode : les services web REST.
De nombreux acteurs essentiels du Web, comme Amazon, Google et Yahoo!, ont
abandonné leurs services SOAP en faveur de services REST orientés ressources.
Lorsqu’il s’agit de choisir entre des services SOAP et REST, de nombreux facteurs
entrent en ligne de compte.
REST (Representational State Transfer) est un type d’architecture reposant sur le
fonctionnement même du Web, qu’il applique aux services web. Pour concevoir un
service REST, il faut connaître le protocole HTTP (Hypertext Transfer Protocol), le
principe des URI (Uniform Resource Identifiers) et respecter quelques règles. Il faut
raisonner en termes de ressources.
REST est intégré à Java EE 6 via JAX-RS (Java API for RESTful Web Services), que
nous utiliserons dans ce chapitre.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
508 Java EE 6 et GlassFish 3
Ressources
Les ressources jouent un rôle central dans les architectures REST. Une ressource
est tout ce que peut désigner ou manipuler un client, toute information pouvant être
référencée dans un lien hypertexte. Elle peut être stockée dans une base de données,
un fichier, etc. On évite autant que possible d’exposer des concepts abstraits sous
forme de ressources et l’on privilégie les objets simples.
Certaines ressources de l’application CD-BookStore pourraient donc être :
■■ une liste des livres publiés par Apress et consacrés à Java ;
■■ l’ouvrage Ruby On Rails ;
■■ le résumé d’Ola Bini.
D’autres exemples de ressources sont :
■■ les données météorologiques de Paris en 2008 ;
■■ vos informations de contact ;
■■ le centième nombre de Fibonacci ;
■■ une transaction en cours d’exécution dans un gestionnaire de transactions ;
■■ le nombre d’amis communs de Joe et Bill ;
■■ les photos intéressantes postées sur Flickr au cours des 24 dernières heures ;
■■ les photos intéressantes postées sur Flickr le 01/01/2009.
URI
Une ressource web est identifiée par une URI, qui est un identifiant unique formé
d’un nom et d’une adresse indiquant où trouver la ressource. Il existe différents types
d’URI : les adresses web, les UDI (Universal Document Identifiers), les URI (Uni-
versal Resource Identifiers) et, enfin, les combinaisons d’URL (Uniform Resource
Locators) et d’URN (Uniform Resource Names). Voici quelques exemples d’URI :
■■ http://www.pearson.fr/catalogue/programmation/
■■ http://www.pearson.fr/Resources/titles/27440100325790/Images
/27440100325790L.gif
■■ http://www.pearson.fr/contacts/
■■ http://www.weather.com/weather/2008?location=Paris,France
■■ http://www.flickr.com/explore/interesting/2009/01/01
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 509
■■ http://www.flickr.com/explore/interesting/24hours
■■ http://www.movies.com/catalog/titles/movies/123456
■■ http://www.movies.com/categories/aventure
Les URI devraient être aussi descriptives que possible et ne désigner qu’une seule
ressource, bien que des URI différentes qui identifient des ressources différentes
puissent mener aux mêmes données : à un instant donné, la liste des photos inté-
ressantes publiées sur Flickr le 01/01/2009 était la même que la liste des photos
déposées au cours des 24 dernières heures, bien que l’information convoyée par les
deux URI ne fût pas la même.
Représentations
On peut vouloir obtenir la représentation d’un objet sous forme de texte, de XML,
de PDF ou sous un autre format. Un client traite toujours une ressource au travers
de sa représentation ; la ressource elle-même reste sur le serveur. La représentation
contient toutes les informations utiles à propos de l’état d’une ressource. La liste
des ouvrages sur Java mentionnée précédemment, par exemple, a au moins deux
représentations :
■■ La page HTML telle qu’elle est affichée par un navigateur, http://www.apress.
com/book/catalog?category=32 ;
Deux solutions sont possibles pour choisir entre les différentes représentations d’une
ressource. La première consiste à proposer une URI par représentation : c’est ce que
fait le site d’Apress pour la liste des livres sur Java. Cependant, en ce cas, les deux
URI sont différentes et ne semblent pas directement liées. Voici un ensemble d’URI
mieux organisé :
■■ http://www.apress.com/book/catalog/java
■■ http://www.apress.com/book/catalog/java.csv
■■ http://www.apress.com/book/catalog/java.xml
La première URI est la représentation par défaut de la ressource, les représentations
supplémentaires lui ajoutent simplement l’extension de leur format : .csv, .xml,
.pdf, etc.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
510 Java EE 6 et GlassFish 3
L’autre solution consiste à n’exposer qu’une seule URI pour toutes les représenta-
tions (http://www. apress.com/book/catalog/java, par exemple) et à utiliser un
mécanisme appelé négociation du contenu, que nous présenterons un peu plus loin.
WADL
Alors que les services web SOAP utilisent WSDL pour décrire le format des
requêtes possibles, WADL (Web Application Description Language) sert à indiquer
les interactions possibles avec un service web REST. Il facilite le développement des
clients, qui peuvent ainsi charger et interagir directement avec les ressources. Nous
ne présenterons pas WADL dans ce livre car il n’est pas obligatoire pour les services
REST et parce qu’il est peu utilisé.
HTTP
Requêtes et réponses
Un client envoie une requête à un serveur afin d’obtenir une réponse (voir
Figure 15.1). Les messages utilisés pour ces échanges sont formés d’une enveloppe
et d’un corps également appelé document ou représentation.
Figure 15.1 Client
GET /index.xhtml
Serveur
requête
Requête et réponse http.
200 OK <html><head>...
réponse
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 511
INFO
Nous utilisons ici cURL (http://curl.haxx.se/) comme client, mais nous aurions pu également
utiliser Firefox ou n’importe quel autre client web. CURL est un outil en ligne de commande
permettant de transférer des fichiers par HTTP, FTP, Gopher, SFTP, FTPS, SCP, TFTP et bien
d’autres protocoles encore en utilisant une URL. Avec lui, vous pouvez envoyer des com-
mandes HTTP, modifier les en-têtes HTTP, etc. : c’est donc un outil parfait pour simuler les
actions d’un utilisateur dans un navigateur web.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
512 Java EE 6 et GlassFish 3
Méthodes de HTTP
Le Web est formé de ressources bien identifiées, reliées ensemble et auxquelles accé-
der au moyen de requêtes HTTP simples. Les requêtes principales de HTTP sont de
type GET, POST, PUT et DELETE. Ces types sont appelés verbes ou méthodes. HTTP
définit quatre autres verbes plus rarement utilisés, HEAD, TRACE, OPTIONS et CONNECT.
GET
GET est une méthode de lecture demandant une représentation d’une ressource. Elle
doit être implémentée de sorte à ne pas modifier l’état de la ressource. En outre, GET
doit être idempotente, ce qui signifie qu’elle doit laisser la ressource dans le même
état, quel que soit le nombre de fois où elle est appelée. Ces deux caractéristiques
garantissent une plus grande stabilité : si un client n’obtient pas de réponse (à cause
d’un problème réseau, par exemple), il peut renouveler sa requête et s’attendre à la
même réponse que celle qu’il aurait obtenue initialement, sans corrompre l’état de
la ressource sur le serveur.
POST
À partir d’une représentation texte, XML, etc., POST crée une nouvelle ressource
subordonnée à une ressource principale identifiée par l’URI demandée. Des
exemples d’utilisation de POST sont l’ajout d’un message à un fichier journal, d’un
commentaire à un blog, d’un livre à une liste d’ouvrages, etc. POST modifie donc
l’état de la ressource et n’est pas idempotente (envoyer deux fois la même requête
produit deux nouvelles ressources subordonnées). Si une ressource a été créée sur
le serveur d’origine, le code de la réponse devrait être 201 (Created). La plupart des
navigateurs modernes ne produisent que des requêtes GET ou POST.
PUT
Une requête PUT est conçue pour modifier l’état de la ressource stockée à une cer-
taine URI. Si l’URI de la requête fait référence à une ressource inexistante, celle-ci
sera créée avec cette URI. PUT modifie donc l’état de la ressource, mais elle est
idempotente : même si l’on envoie plusieurs fois la même requête PUT, l’état de la
ressource finale restera inchangé.
DELETE
Une requête DELETE supprime une ressource. La réponse à DELETE peut être un mes-
sage d’état dans le corps de la réponse ou aucun code du tout. DELETE est idempo-
tente, mais elle modifie évidemment l’état de la ressource.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 513
Autres
Comme on l’a évoqué, il existe d’autres actions HTTP, plus rarement employées :
■■ HEAD ressemble à GET sauf que le serveur ne renvoie pas de corps dans sa réponse.
HEAD permet par exemple de vérifier la validité d’un client ou la taille d’une entité
sans avoir besoin de la transférer.
■■ TRACE retrace la requête reçue.
■■ OPTIONS est une demande d’information sur les options de communication dispo-
nibles pour la chaîne requête/réponse identifiée par l’URI. Cette méthode permet
au client de connaître les options et/ou les exigences associées à une ressource,
ou les possibilités d’un serveur sans demander d’action sur une ressource et sans
récupérer aucune ressource.
■■ CONNECT est utilisé avec un proxy pouvant se transformer dynamiquement en
tunnel (une technique grâce à laquelle le protocole HTTP sert d’enveloppe à
différents protocoles réseau).
Négociation du contenu
La négociation du contenu, décrite dans la section 12 du standard HTTP, est défi-
nie comme "le fait de choisir la meilleure représentation pour une réponse donnée
lorsque plusieurs représentations sont disponibles". Les besoins, les souhaits et les
capacités des clients varient : la meilleure représentation pour l’utilisateur d’un télé-
phone portable au Japon peut, en effet, ne pas être la plus adaptée à un lecteur de
flux RSS en France.
La négociation du contenu utilise entre autres les en-têtes HTTP Accept, Accept-
Charset, Accept-Encoding, Accept-Language et User-Agent. Pour obtenir, par
exemple, la représentation CSV de la liste des livres sur Java publiés par Apress,
l’application cliente (l’agent utilisateur) demandera http://www.apress.com/book/
catalog/java avec un en-tête Accept initialisé à text/csv. Vous pouvez aussi ima-
giner que, selon la valeur de l’en-tête Accept-Language, le contenu de ce document
CSV pourrait être traduit par le serveur dans la langue correspondante.
Types de contenu
HTTP utilise des types de supports Internet (initialement appelés types MIME) dans
les en-têtes Content-Type et Accept afin de permettre un typage des données et une
négociation du contenu ouverts et extensibles. Les types de support Internet sont
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
514 Java EE 6 et GlassFish 3
divisés en cinq catégories : text, image, audio, video et application. Ces types
sont à leur tour divisés en sous-types (text/plain, text/xml, text/xhtml, etc.).
Voici quelques-uns des plus utilisés :
■■ text/html. HTML est utilisé par l’infrastructure d’information du World Wide
Web depuis 1990 et sa spécification a été décrite dans plusieurs documents infor-
mels. Le type de support text/html a été initialement défini en 1995 par le groupe
de travail IETF HTML. Sa définition complète se trouve dans la RFC 2854.
■■ text/plain. Il s’agit du type de contenu par défaut car il est utilisé pour les
‑messages textuels simples.
■■ image/gif, image/jpeg, image/png. Le type de support image exige la présence
d’un dispositif d’affichage (un écran ou une imprimante graphique, par exemple)
permettant de visualiser l’information.
■■ text/xml, application/xml. XML 1.0 a été publié par le W3C en février 1998.
La définition complète de ce support est décrite dans la RFC 3023.
■■ application/json. JSON (JavaScript Object Notation) est un format textuel léger
pour l’échange de données. Il est indépendant des langages de programmation.
Codes d’état
Un code HTTP est associé à chaque réponse. La spécification définit environ
60 codes d’états ; l’élément Status-Code est un entier de trois chiffres qui décrit le
contexte d’une réponse et qui est intégré dans l’enveloppe de celle-ci. Le premier
chiffre indique l’une des cinq classes de réponses possibles :
■■ 1xx : Information. La requête a été reçue et le traitement se poursuit.
■■ 2xx : Succès. L’action a bien été reçue, comprise et acceptée.
■■ 3xx : Redirection. Une autre action est requise pour que la requête s’effectue.
■■ 4xx : Erreur du client. La requête contient des erreurs de syntaxe ou ne peut
pas être exécutée.
■■ 5xx : Erreur du serveur. Le serveur n’a pas réussi à exécuter une requête pourtant
apparemment valide.
Voici quelques codes d’état que vous avez sûrement déjà dû rencontrer :
■■ 200 OK. La requête a réussi. Le corps de l’entité, si elle en possède un, contient
la représentation de la ressource.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 515
La Figure 15.2 montre un exemple d’utilisation d’ETag. Pour obtenir une res-
source livre, on utilise une action GET en lui indiquant l’URI de la ressource (GET /
book/12345). Le serveur répondra par une représentation XML du livre, un code 200
OK et un ETag. La seconde fois que l’on demandera la même ressource et que l’on
passera la valeur de cet ETag dans la requête, le serveur ne renverra pas la représen-
tation de la ressource, mais uniquement une réponse avec un code 304 Not Modified
afin d’informer le client que cette ressource n’a pas été modifiée depuis son dernier
accès.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
516 Java EE 6 et GlassFish 3
Utilisation du cache
200 OK <book>...</book>
et du code 304. ETag: 328987
GET/book/12315
If-Ncn-Match: 328987
Contrairement à SOAP et à la pile WS-*, qui reposent sur les standards du W3C ;
REST n’est pas un standard : c’est uniquement un style d’architecture respectant
certains critères de conception. Les applications REST, cependant, dépendent
fortement d’autres standards :
■■ HTTP ;
■■ URI, URL ;
■■ XML, JSON, HTML, GIF, JPEG, etc. (représentation des ressources).
Sa prise en compte par Java a été spécifiée par JAX-RS, mais REST est comme un
patron de conception : c’est une solution réutilisable d’un problème courant, qui
peut être implémentée en différents langages.
Le terme REST a été employé pour la première fois par Roy Thomas Fielding dans
le Chapitre 5 de sa thèse de doctorat, Architectural Styles and the Design of Network-
based Software Architectures (université de Californie, Irvine, 2000, http://www.
ics.uci.edu/~fielding/pubs/dissertation/top.htm). Sa dissertation est une rétros-
pective des architectures choisies pour développer le Web.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 517
Dans sa thèse, Fielding étudie les parties du Web qui fonctionnent très bien et en
extrait les principes de conception qui pourraient rendre aussi efficaces les autres
systèmes hypermédias distribués – qu’ils soient ou non liés au Web.
La raison initiale qui a poussé au développement de REST était donc de créer un
modèle architectural du Web. Roy T. Fielding étant également l’un des auteurs de la
spécification HTTP, il n’est pas surprenant que ce protocole corresponde particuliè-
rement bien à l’architecture décrite dans sa dissertation.
JAX-RS 1.1
Pour écrire des services web REST, il suffit d’un client et d’un serveur reconnais-
sant le protocole HTTP. N’importe quel navigateur et un conteneur de servlet HTTP
pourraient donc faire l’affaire, au prix d’un peu de configuration XML et d’ajus-
tement du code. Mais, au final, ce code pourrait devenir peu lisible et difficile à
maintenir : c’est là que JAX-RS vole à notre secours. Sa première version, finalisée
en octobre 2008, définit un ensemble d’API mettant en avant une architecture REST.
C’est le standard JCP pour les services web REST (JSR 311). Au moyen d’annota-
tions, il simplifie l’implémentation de ces services et améliore la productivité. La
spécification ne couvre que la partie serveur de REST.
JAX-RS 1.1 est une version de maintenance axée sur l’intégration avec Java EE 6
et ses nouvelles fonctionnalités. Les nouveautés principales de JAX-RS 1.1 sont les
suivantes :
■■ Le support de beans de session sans état comme ressources racine.
■■ Il est désormais possible d’injecter des ressources externes (gestionnaire de
persistance, sources de données, EJB, etc.) dans une ressource REST.
■■ Les annotations JAX-RS peuvent s’appliquer à l’interface locale d’un bean ou
directement à un bean sans interface.
Implémentation de référence
Jersey est l’implémentation de référence officielle de JAX-RS par Sun. C’est un pro-
jet open-source couvert à la fois par les licences CDDL et GPL. Jersey est extensible
au moyen de son API et fournit également une API cliente (qui n’est pas couverte
par la spécification).
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
518 Java EE 6 et GlassFish 3
L’approche REST
Nous savons comment fonctionne le Web : pourquoi les services web devraient-ils se
comporter différemment ? Après tout, ils échangent souvent uniquement des ressources
bien identifiées, liées à d’autres au moyen de liens hypertextes. L’architecture du Web
ayant prouvé sa tenue en charge au cours du temps, pourquoi réinventer la roue ?
Pour créer, modifier et supprimer une ressource livre, pourquoi ne pas utiliser les
verbes classiques de HTTP ? Par exemple :
■■ Utiliser POST sur des données (au format XML, JSON ou texte) afin de créer
une ressource livre avec l’URI http://www.apress.com/books/. Le livre créé,
la réponse renvoie l’URI de la nouvelle ressource : http://www.apress.com/
books/123456.
■■ Utiliser GET pour lire la ressource (et les éventuels liens vers d’autres ressources
à partir du corps de l’entité) à l’URI http://www.apress.com/books/123456.
■■ Utiliser PUT pour modifier la ressource située à l’URI http://www.apress.com/
books/123456.
En se servant de verbes HTTP, nous pouvons donc effectuer toutes les actions CRUD
(Create, Read, Update, Delete) sur une ressource.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 519
Comment obtenir la liste des livres sur Java publiés par Apress ? En faisant pointer
son navigateur sur le site web d’Apress : http://www.apress.com. Même si cette
page ne contiendra sûrement pas les informations exactes que l’on recherche, on
s’attend à ce qu’elle donne accès, par un moyen ou un autre, à la liste des livres
consacrés à Java. La page d’accueil offre un moteur de recherche de tous les livres
d’Apress, mais également un répertoire de livres classés par technologies. Si l’on
clique sur le nœud Java, la magie de l’hypertexte opère et l’on obtient la liste com-
plète des livres sur Java publiés par Apress. Elle est assez longue et, bien que le
nombre d’ouvrages ne soit pas indiqué, le lien vers le format CSV et nos compé-
tences en shell permettront de faire ce calcul en quelques cycles d’horloge (il y a ici
144 livres dans la catégorie Java) :
curl http://www.apress.com/resource/csv/bookcategory?cat=32 |
„ sed -n 2~1p | wc -l
(..)
144
Supposons que nous ayons sauvegardé le lien dans notre gestionnaire des favoris
et qu’au cours du parcours de la liste l’ouvrage The Definitive Guide to Grails, de
Graeme Rocher et Jeff Brown, attire notre attention : le lien hypertexte sur le titre
de ce livre nous mènera à la page contenant le résumé, la biographie des auteurs,
etc. et nous pourrons noter qu’un des livres mentionnés dans la section Related titles
est également susceptible de nous intéresser. Nous voudrons comparer l’ouvrage de
Graeme Rocher avec Practical JRuby on Rails Web 2.0 Projects: Bringing Ruby on
Rails to Java, d’Ola Bini. Les pages des livres d’Apress nous donnent accès à une
représentation plus concrète sous la forme de prévisualisations : nous pouvons alors
ouvrir une prévisualisation, lire la table des matières et faire notre choix.
Voici ce que l’on fait quotidiennement avec nos navigateurs. REST applique les
mêmes principes à vos services où les livres, les résultats des recherches, une table
des matières ou la couverture d’un livre peuvent être définis comme des ressources.
Interface uniforme
L’une des principales contraintes constituant une architecture REST est l’utilisation
d’une interface uniforme pour la gestion des ressources. Nous pouvons choisir l’in-
terface que nous voulons, du moment que nous l’utilisons de la même façon partout,
d’une ressource à une autre, d’un service à un autre. Il ne faut jamais s’en écarter ou
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
520 Java EE 6 et GlassFish 3
Accessibilité
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 521
Les URI uniques permettent de créer des liens vers les ressources et, comme elles
sont exposées via une interface uniforme, chacun sait exactement comment interagir
avec elles : ceci permet d’utiliser une application de façon insoupçonnée.
Connectivité
En théorie des graphes, on dit qu’un graphe est connexe lorsque tous les sommets
peuvent se connecter les uns aux autres via un chemin quelconque. Un graphe est dit
fortement connexe s’il existe un chemin direct de u à v et un chemin direct de v à u
pour toute paire de sommets (u, v). REST demande à ce que les ressources soient
les plus connectées possible. C’est le fameux principe "L’hypermédia est le moteur
de l’état de l’application".
Ce principe provient, là encore, d’un examen du succès du Web. Les pages contien-
nent des liens pour passer de page en page de façon logique et simple, et l’on peut
donc considérer que le Web est très bien connecté. S’il existe une relation forte entre
deux ressources, celles-ci doivent être connectées. REST précise que les services
web devraient également tirer parti de l’hypermédia pour informer le client de ce qui
est disponible et de la façon de s’y rendre. Il met en avant la découverte des services.
À partir d’une seule URI, un agent utilisateur accédant à un service bien connecté
pourra découvrir toutes les actions et ressources disponibles, leurs différentes repré-
sentations, etc.
Lorsqu’un agent utilisateur recherche, par exemple, la représentation d’un CD (voir
Listing 15.1), celle-ci peut contenir non seulement le nom de l’artiste, mais égale-
ment un lien – une URI – vers sa biographie. C’est ensuite à l’agent utilisateur de
la récupérer ou non. La représentation pourrait également contenir des liens vers
d’autres représentations de la ressource ou vers les actions possibles.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
522 Java EE 6 et GlassFish 3
Un autre aspect essentiel du principe de l’hypermédia est qu’il doit piloter l’état de
l’application. Pour résumer, le fait que le service web fournisse un ensemble de liens
permet au client de faire passer l’application d’un état à l’autre en suivant simplement
un lien.
Dans l’extrait XML précédent, le client pouvait ainsi changer l’état de l’application
en apportant un commentaire sur l’album. La liste des commentaires de cet album
est une ressource accessible par l’URI http://music.com/album/789/comments. Ce
service web utilisant une interface uniforme, tout client connaissant le format de
l’URI, les types de contenus disponibles et le format des données savent exacte-
ment comment interagir avec lui : un GET récupérera la liste des commentaires exis-
tants, un POST créera un nouveau commentaire, etc. À partir de cette requête initiale
simple, le client peut réaliser de nombreuses actions, et c’est la raison pour laquelle
l’hypermédia pilote l’état de l’application.
Sans état
La dernière fonctionnalité de REST est l’absence d’état, ce qui signifie que toute
requête HTTP est totalement indépendante puisque le serveur ne mémorisera jamais
les requêtes qui ont été effectuées. Pour plus de clarté, l’état d’une ressource et celui
de l’application sont généralement différenciés : l’état de la ressource doit se trouver
sur le serveur et être partagé par tous, tandis que celui de l’application doit rester
chez le client et être sa seule propriété. Si l’on revient à l’exemple du Listing 15.1,
l’état de l’application est que le client a récupéré une représentation de l’album Ella
and Louis, mais le serveur ne mémorise pas cette information. L’état de la ressource,
quant à lui, est l’information sur l’album : le serveur doit évidemment la mémoriser
et le client peut le modifier. Si le panier virtuel est une ressource dont l’accès est
réservé à un seul client, l’application doit stocker l’identifiant de ce panier dans la
session du client.
L’absence d’état a de nombreux avantages, notamment une meilleure adaptation à
la charge : aucune information de session à gérer, pas besoin de router les requêtes
suivantes vers le même serveur, gestion des erreurs, etc. Si vous devez mémoriser
l’état, le client devra faire un travail supplémentaire pour le stocker.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 523
@GET
@Produces("text/plain")
public String getBookTitle() {
return "H2G2";
}
}
BookResource étant une classe Java annotée par @Path, la ressource sera hébergée
à l’URI /book. La méthode getBookTitle() est annotée par @GET afin d’indiquer
qu’elle traitera les requêtes HTTP GET et qu’elle produit du texte (le contenu est
identifié par le type MIME text/plain). Pour accéder à la ressource, il suffit d’un
client HTTP, un navigateur, par exemple, pouvant envoyer une requête GET vers
l’URL http://www.myserver.com/book.
Le modèle JAX-RS
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
524 Java EE 6 et GlassFish 3
■■ Les classes ressources racine (celles ayant une annotation @Path) doivent avoir
un constructeur par défaut public. Les classes ressources non racine n’exigent
pas ce constructeur.
■■ La classe ne doit pas définir la méthode finalize().
Par nature, JAX-RS repose sur HTTP et dispose d’un ensemble de classes et d’an-
notations clairement définies pour gérer HTTP et les URI. Une ressource pouvant
avoir plusieurs représentations, l’API permet de gérer un certain nombre de types
de contenu et utilise JAXB pour sérialiser et désérialiser les représentations XML
et JSON en objets. JAX-RS étant indépendant du conteneur, les ressources peuvent
être évidemment déployées dans GlassFish, mais également dans un grand nombre
d’autres conteneurs de servlets.
Listing 15.3 : Une ressource livre qui crée et récupère des livres dans une base de données
@Path("books")
@Stateless
@Produces({"application/xml", "application/json"})
@Consumes({"application/xml", "application/json"})
public class BookResource {
@Context
private UriInfo uriInfo;
@PersistenceContext(unitName = "chapter15PU")
private EntityManager em;
@GET
public List<Book> getAllBooks() {
Query query = em.createNamedQuery("findAllBooks");
List<Book> books = query.getResultList();
return books;
}
@POST
public Response createNewBook(JAXBElement<Book> bookJaxb) {
Book book = bookJaxb.getValue();
em.persist(book);
URI bookUri = uriInfo.getAbsolutePathBuilder().path(
„ book.getId().toString()).build();
return Response.created(bookUri).build();
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 525
La valeur de l’annotation @Path est relative à un chemin d’URI. Lorsqu’elle est utili-
sée sur des classes, celles-ci sont considérées comme des ressources racine, car elles
fournissent la racine de l’arborescence des ressources et l’accès aux sous-ressources.
@Path("/customers")
public class CustomersResource {
@GET
public List getCustomers() {
// ...
}
}
Vous pouvez également intégrer dans la syntaxe de l’URI des templates de chemins
d’URI : ces variables seront évaluées à l’exécution. La documentation Javadoc de
l’annotation @Path décrit la syntaxe des templates :
@Path("/customers/{customername}")
@Path peut également s’appliquer aux méthodes des ressources racine, ce qui per-
met de regrouper les fonctionnalités communes à plusieurs ressources, comme le
montre la Figure 15.4 (ignorez pour l’instant les annotations @GET, @POST et @DELETE
car nous les décrirons plus loin, dans la section "Méthodes ou interface uniforme").
@GET
public List<Item> getListOfItems() {
// ...
}
@GET
@Path("{itemid}")
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
526 Java EE 6 et GlassFish 3
@PUT
@Path("{itemid}")
public void putItem(@PathParam("itemid") String itemid,
Item item) {
// ...
}
@DELETE
@Path("{itemid}")
public void deleteItem(@PathParam("itemid") String itemid) {
// ...
}
@GET
@Path("/books/")
public List<Book> getListOfBooks() {
// ...
}
@GET
@Path("/books/{bookid}")
public Book getBook(@PathParam("bookid") String bookid) {
// ...
}
}
Si @Path est appliquée à la fois sur la classe et une méthode, le chemin relatif de
la ressource produite par cette méthode est la concaténation de la classe et de la
méthode. Pour obtenir un livre par son identifiant, par exemple, le chemin sera
/items/books/1234. Si l’on demande la ressource racine /items, seule la méthode
sans annotation @Path sera sélectionnée (getListOfItems(), ici). Si l’on demande
/items/books, c’est la méthode getListOfBooks() qui sera invoquée. Si @Path
("/items") n’annotait que la classe et aucune méthode, le chemin d’accès de toutes
les méthodes serait le même et il faudrait utiliser le verbe HTTP (GET, PUT) et la
négociation du contenu (texte, XML, etc.) pour les différencier.
Nous avons besoin d’extraire des informations sur les URI et les requêtes lorsqu’on
les manipule. Le Listing 15.4 a déjà montré comment extraire un paramètre du
chemin à l’aide de @javax.ws.rs.PathParam et JAX-RS fournit tout un ensemble
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 527
d’annotations pour extraire les différents paramètres qu’une requête peut envoyer
(@QueryParam, @MatrixParam, @CookieParam, @HeaderParam et @FormParam).
L’annotation @PathParam permet d’extraire la valeur d’un paramètre template d’une
URI. Le code suivant, par exemple, extraira l’identifiant du client (98342) de l’URI
http://www.myserver. com/customers/98342 :
@Path("/customers")
public class CustomersResource {
@GET
public Customer getCustomer(@PathParam("customerid")
customerid) {
// ...
}
}
Deux autres annotations sont liées aux détails internes de HTTP, ce que l’on ne voit
pas directement dans les URI : les cookies et les en-têtes. @CookieParam extrait la
valeur d’un cookie, tandis que @HeaderParam permet d’obtenir la valeur d’un en-
tête. Le code suivant, par exemple, extrait l’identifiant de session du cookie :
@Path("/products")
public class ItemsResource {
@GET
public Book getBook(@CookieParam("sessionid") int sessionid) {
// ...
}
}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
528 Java EE 6 et GlassFish 3
L’annotation @FormParam précise que la valeur d’un paramètre doit être extraite d’un
formulaire situé dans le corps de la requête.
On peut ajouter @DefaultValue à toutes ces annotations pour définir une valeur par
défaut pour le paramètre que l’on attend. Cette valeur sera utilisée si les métadon-
nées correspondantes sont absentes de la requête. Si le paramètre age ne se trouve
pas dans la requête, par exemple, le code suivant utilisera la valeur 50 par défaut :
@Path("/customers")
public class CustomersResource {
@GET
public Response getCustomers(@DefaultValue("50")
@QueryParam("age") int age) {
// ...
}
}
Avec REST, une ressource peut avoir plusieurs représentations : un livre peut être
représenté comme une page web, un document XML ou une image affichant sa cou-
verture. Les annotations @javax.ws.rs.Consumes et @javax.ws.rs.Produces peu-
vent s’appliquer à une ressource qui propose plusieurs représentations : elle définit
les types de médias échangés entre le client et le serveur. L’utilisation de l’une de ces
annotations sur une méthode ressource redéfinit celle qui s’appliquait sur la classe
de la ressource pour un paramètre d’une méthode ou une valeur de retour. En leur
absence, on suppose que la ressource supporte tous les types de médias (*/*). Dans
le Listing 15.5, CustomerResource produit par défaut une représentation en texte
brut, sauf pour certaines méthodes.
@GET
public String getAsPlainText() {
// ...
}
@GET
@Produces("text/html")
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 529
@GET
@Produces("application/json")
public List<Customer> getAsJson() {
// ...
}
@PUT
@Consumes("text/plain")
public void putBasic(String customer) {
// ...
}
@POST
@Consumes("application/xml")
public Response createCustomer(InputStream is) {
// ...
}
}
Si une ressource peut produire plusieurs types de médias Internet, la méthode choi-
sie correspondra au type qui convient le mieux à l’en-tête Accept de la requête
HTTP du client. Si, par exemple, cet en-tête est :
Accept: text/plain
et que l’URI est /customers, c’est la méthode getAsPlainText() qui sera invoquée.
Le client aurait également pu utiliser l’en-tête suivant :
Accept: text/plain; q=0.8, text/html
Cet en-tête annonce que le client peut accepter les types text/plain et text/html
mais qu’il préfère le dernier avec un facteur de qualité de 0.8 ("je préfère huit fois
plus le text/html que le text/plain"). En incluant cet en-tête dans une requête
adressée à /customers, c’est la méthode getAsHtml() qui sera invoquée.
Dans le Listing 15.5, la méthode getAsJson() renvoie une représentation de type
application/json. JSON est un format léger pour l’échange de données : comme
vous pourrez le constater en étudiant le Listing 15.6, il est moins verbeux et plus
lisible que XML.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
530 Java EE 6 et GlassFish 3
"version": "1",
"customer": [
{
"firstName": "Alexis",
"lastName": "Midon",
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"postalCode": 10021
},
"phoneNumbers": [
"212 555-1234",
"646 555-4567"
]
},
{
"firstName": "Sebastien",
"lastName": "Auvray",
"address": {
"streetAddress": "2 Rue des Acacias",
"city": "Paris",
"postalCode": 75015
},
"phoneNumbers": [
"+33 555-1234",
]
}
]
})
Jersey convertira pour vous la liste des clients au format JSON en utilisant les four-
nisseurs d’entités prédéfinis MessageBodyReader et MessageBodyWriter.
INFO
Jersey ajoute une association BadgerFish de JAXB XML vers JSON. BadgerFish (http:// bad-
gerfish.ning.com/) est une convention de traduction d’un document XML en objet JSON afin
qu’il soit plus facile à manipuler avec JavaScript.
Fournisseurs d’entités
Lorsque les entités sont reçues dans des requêtes ou envoyées dans des réponses,
l’implémentation JAX-RS doit pouvoir convertir les représentations en Java et vice
versa : c’est le rôle des fournisseurs d’entités. JAXB, par exemple, traduit un objet
en représentation Java et réciproquement. Il existe deux variantes de fournisseurs
d’entités : MessageBodyReader et MessageBodyWriter.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 531
Pour traduire le corps d’une requête en Java, une classe doit implémenter l’interface
javax.ws.rs.ext.MessageBodyReader et être annotée par @Provider. Par défaut,
l’implémentation est censée consommer tous les types de médias (*/*), mais l’anno-
tation @Consumes permet de restreindre les types supportés. Le code du Listing 15.7,
par exemple, prend un flux XML en entrée (représenté par un objet javax.xml.
bind.Element) et le convertit en objet Customer.
De la même façon, un type Java peut être traduit en corps de réponse. Une classe
Java voulant effectuer ce traitement doit implémenter l’interface javax.ws.rs.
ext.MessageBodyWriter et être annotée par l’interface @Provider. L’annotation
@Produces indique les types de médias supportés. Le code du Listing 15.8 montre
comment convertir une entité Customer en un corps HTTP en texte brut.
Listing 15.8 : Fournisseur produisant une représentation en texte brut d’une entité Customer
@Provider
@Produces("text/plain")
public class CustomerWriter
implements MessageBodyWriter<Customer> {
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
532 Java EE 6 et GlassFish 3
return Customer.class.isAssignableFrom(type);
}
Type Description
byte[] Tous types de médias (*/*)
java.lang.String Tous types de médias (*/*)
java.io.InputStream Tous types de médias (*/*)
java.io.Reader Tous types de médias (*/*)
java.io.File Tous types de médias (*/*)
javax.activation.DataSource Tous types de médias (*/*)
javax.xml.transform.Source Types XML (text/xml, application/xml et
application/-*+xml)
Nous avons vu comment le protocole HTTP gérait ses requêtes, ses réponses et
ses méthodes d’actions (GET, POST, PUT, etc.). JAX-RS définit ces méthodes HTTP
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 533
à l’aide d’annotations : @GET, @POST, @PUT, @DELETE, @HEAD et @OPTIONS. Seules les
méthodes publiques peuvent être exposées comme des méthodes de ressources. Le
Listing 15.9, par exemple, montre une ressource customer exposant les méthodes
CRUD.
@GET
public List<Customer> getListOfCustomers() {
// ...
}
@POST
@Consumes("application/xml")
public Response createCustomer(InputStream is) {
// ...
}
@PUT
@Path("{customerid}")
@Consumes("application/xml")
public Response updateCustomer(@PathParam("customerid")
String customerId,
InputStream is) {
// ...
}
@DELETE
@Path("{customerid}")
public void deleteCustomer(@PathParam("customerid")
String customerId) {
// ...
}
}
Lorsqu’une méthode ressource est invoquée, les paramètres annotés par l’une des
annotations d’extraction vues précédemment sont initialisés. La valeur d’un para-
mètre non annoté (appelé paramètre entité) est obtenue à partir du corps de la requête
et convertie par un fournisseur d’entités.
Les méthodes peuvent renvoyer void, Response ou un autre type Java. Response
indique qu’il faudra fournir d’autres métadonnées – lorsque l’on crée un nouveau
client, par exemple, il faudra renvoyer son URI.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
534 Java EE 6 et GlassFish 3
Informations contextuelles
@Context
UriInfo uriInfo;
@GET
@Produces("application/json")
public JSONArray getListOfCustomers() {
JSONArray uriArray = new JSONArray();
for (Customer customer : customerDAO.findAll()) {
UriBuilder ub = uriInfo.getAbsolutePathBuilder();
URI userUri = ub.path(userEntity.getCustomerId()).build();
uriArray.put(userUri.toASCIIString());
}
return uriArray;
}
}
En-têtes
Comme on l’a vu précédemment, les informations transportées entre le client et
le serveur sont formées non pas uniquement du corps d’une entité, mais égale-
ment d’en-têtes (Date, Server, Content-Type, etc.). Les en-têtes HTTP font partie
de l’interface uniforme et les services web REST les utilisent. La classe javax.
ws.rs.core.HttpHeaders permet aux développeurs de ressources d’y accéder : une
instance de HttpHeaders peut être injectée dans un attribut ou dans un paramètre
de méthode au moyen de l’annotation @Context afin de permettre d’accéder aux
valeurs des en-têtes sans tenir compte de leur casse.
Si le service fournit des ressources localisées, par exemple, le code suivant permettra
d’extraire la valeur de l’en-tête Accept-Language :
@GET
@Produces{"text/plain"}
public String get(@Context HttpHeaders headers) {
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 535
List<java.util.Locale> locales =
headers.getAcceptableLanguages()
// ou headers.getRequestHeader("accept-language")
...
}
Construction d’URI
Les liens hypertextes sont un élément central des applications REST. Pour évoluer à
travers les états de l’application, les services web REST doivent gérer les transitions
et la construction des URI. Pour ce faire, JAX-RS fournit une classe javax.ws.rs.
core.UriBuilder destinée à remplacer java.net.URI et à faciliter la construction
d’URI. UriBuilder dispose d’un ensemble de méthodes permettant de construire de
nouvelles URI ou d’en fabriquer à partir d’URI existantes. Le code du Listing 15.11
s’en sert pour renvoyer un tableau d’URI.
@GET
@Produces("application/json")
public JSONArray getCustomersAsJsonArray() {
JSONArray uriArray = new JSONArray();
for (UserEntity userEntity : getUsers()) {
UriBuilder ub = uriInfo.getAbsolutePathBuilder();
URI userUri = ub.path(userEntity.getUserid()).build();
uriArray.put(userUri.toASCIIString());
}
return uriArray;
}
}
UriBuilder peut également servir à traduire des templates d’URI de façon simple.
L’URI http://www.myserver.com/products/books?author=Goncalves peut, par
exemple, être obtenue de la façon suivante :
UriBuilder.fromUri("http://www.myserver.com/")
.path("{a}/{b}").queryParam("author", "{value}")
.build("products", "books", "Goncalves");
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
536 Java EE 6 et GlassFish 3
Les codes que nous avons présentés jusqu’à maintenant s’exécutaient dans un monde
parfait, où tout se passe bien et où il n’y a pas besoin de traiter les exceptions. Mal-
heureusement, ce monde n’existe pas et, tôt ou tard, une ressource nous explosera
en plein visage parce que les données reçues ne sont pas valides ou que des parties
du réseau ne sont pas fiables.
Comme le montre le Listing 15.12, nous pouvons lever à tout instant une exception
WebApplicationException ou l’une de ses sous-classes dans un fournissseur de res-
sources. Cette exception sera capturée par l’implémentation de JAX-RS et convertie
en réponse HTTP. L’erreur par défaut est un code 500 avec un message vide, mais
la classe javax.ws.rs.WebApplicationException offre différents constructeurs
permettant de choisir un code d’état spécifique (défini dans l’énumération javax.
ws.rs.core.Response.Status) ou une entité.
Pour écrire un code DRY (acronyme de Don’t Repeat Yourself), nous pouvons four-
nir des fournisseurs de traduction d’exception, qui traduisent une exception en un
objet Response. Si cette exception est lancée, l’implémentation JAX-RS la capturera
et appellera le fournisseur de traduction d’exception approprié. Comme le montre
le Listing 15.13, ces fournisseurs sont des implémentations d’ExceptionMapper<E
extends java.lang.Throwable> annotées par @Provider.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 537
return Response.status(404).entity(ex.getMessage()).
„ type("text/plain").build();
}
}
Les exceptions non contrôlées et les erreurs qui n’entrent pas dans les deux cas
précédents seront relancées comme toute exception Java non contrôlée.
Cycle de vie
Lorsqu’une requête arrive, la ressource cible est résolue et une nouvelle instance
de la classe ressource racine correspondante est créée. Le cycle de vie d’une classe
ressource racine dure donc le temps d’une requête, ce qui implique que la classe
ressource n’a pas à s’occuper des problèmes de concurrence et qu’elle peut donc
utiliser les variables d’instance en toute sécurité.
S’ils sont déployés dans un conteneur Java EE (servlet ou EJB), les classes res-
sources et les fournisseurs JAX-RS peuvent également utiliser les annotations de
gestion du cycle de vie et de la sécurité définies dans la JSR 250 : @PostConstruct,
@PreDestroy, @RunAs, @RolesAllowed, @PermitAll, @DenyAll et @DeclareRoles. Le
cycle de vie d’une ressource peut se servir de @PostConstruct et de @PreDestroy
pour ajouter de la logique métier respectivement après la création d’une ressource
et avant sa suppression. La Figure 15.3 montre que ce cycle de vie est équivalent à
celui de tous les composants sans état de Java EE.
Prêt
Appel de méthode
Récapitulatif
Rassemblons tous ces concepts pour écrire une ressource livre, l’assembler et la
déployer dans GlassFish puis la tester avec cURL. L’idée consiste à avoir une entité
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
538 Java EE 6 et GlassFish 3
Book associée à une base de données et une classe BookResource produisant une
représentation REST du livre. BookResource est également un bean de session sans
état, ce qui autorise une démarcation des transactions avec le gestionnaire d’entités
pour les opérations CRUD. Ces classes déployées, nous pourrons créer, récupérer
ou supprimer des livres à l’aide des méthodes HTTP avec cURL et obtenir à la fois
des représentations XML et JSON.
Cet exemple respecte la structure de répertoires de Maven et assemble toutes les
classes dans un fichier war (chapter15-resource-1.0.war). Les classes doivent se
trouver dans les répertoires suivants :
■■ src/main/java. Répertoire pour l’entité Book et le service web REST
BookResource.
■■ src/main/resources. Contient le fichier persistence.xml utilisé par la res-
source pour faire correspondre l’entité Book à la base de données Derby.
■■ src/webapp/WEB-INF. Contient le fichier web.xml qui déclare la servlet Jersey.
■■ pom.xml. Le fichier POM (Project Object Model) de Maven, qui décrit le projet
et ses dépendances.
L’entité Book
Vous connaissez déjà le code de l’entité Book mais, comme le montre la Figure 15.14,
un élément très important a été ajouté ici : l’annotation @XmlRootElement de JAXB,
qui permet d’obtenir une représentation XML d’un livre.
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String title;
private Float price;
@Column(length = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 539
Cette entité doit également être assemblée avec un fichier persistence.xml (que
nous ne présentons pas ici).
BookResource est un service web REST implémenté comme un bean de session sans
état et qui utilise un gestionnaire d’entités pour créer, supprimer et obtenir des livres.
En-tête
L’en-tête de BookResource est important car il utilise plusieurs annotations. Avec
JAX-RS, les utilisateurs peuvent accéder aux ressources en publiant une URI. L’an-
notation @Path("books") indique le chemin de la ressource (c’est-à-dire l’URL pour
l’atteindre) – ici, elle est de la forme http:// localhost:8080/rs/books.
Les annotations @Produces et @Consumes définissent les types de contenus par défaut
que produit ou consomme cette ressource – XML et JSON, ici. Vous pouvez redé-
finir ce type de contenu méthode par méthode. Enfin, l’annotation @Stateless que
nous avons étudiée au Chapitre 7 informe le conteneur que cette ressource doit éga-
lement être considérée comme un EJB et qu’elle autorise la démarcation des tran-
sactions lors des accès à la base de données. Cette ressource reçoit par injection une
référence à un gestionnaire d’entités et une UriInfo :
@Path("books")
@Produces({"application/xml", "application/json"})
@Consumes({"application/xml", "application/json"})
@Stateless
public class BookResource {
@PersistenceContext(unitName = "chapter15PU")
private EntityManager em;
@Context
private UriInfo uriInfo;
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
540 Java EE 6 et GlassFish 3
également un objet JAXB. Lorsque le XML a été sérialisé en objet Book (bookJaxb.
getValue()), on utilise le gestionnaire d’entités pour rendre le livre persistant.
Cette méthode renvoie un objet Response représentant l’URI du nouveau livre. Nous
pourrions renvoyer le code d’état 200 OK pour indiquer que la création a réussi mais,
selon les principes de REST, la méthode devrait renvoyer un code 201 (ou 204)
pour indiquer que la requête a été traitée et qu’elle a produit une nouvelle ressource,
désignée par l’URI renvoyée dans la réponse.
@POST
public Response createNewBook(JAXBElement<Book> bookJaxb) {
Book book = bookJaxb.getValue();
em.persist(book);
URI bookUri = uriInfo.getAbsolutePathBuilder().
„ path(book.getId().toString()).build();
return Response.created(bookUri).build();
}
Pour créer une ressource avec ce code, nous avons le choix entre envoyer un docu-
ment XML ou un document JSON – ce dernier est plus léger. La commande cURL
utilise une méthode POST en lui passant les données dans un format JSON qui doit
respecter l’association JSON/XML utilisée par Jersey (n’oubliez pas que le XML
est à son tour obtenu à partir de l’objet Book à l’aide des règles JAXB) :
curl -X POST --data-binary
„ "{ \"title\":\"H2G2\", \"description\":\"Scifi IT book\",
„ \"illustrations\":\"false\",\"isbn\":\"134-234\",
„ \"nbOfPage\":\"241\", ➥ \"price\":\"24.0\" }
„ " -H "Content-Type: application/json"
„ http://localhost:8080/chapter15-resource-1.0/rs/books –v
Le mode verbeux de cURL (activé par l’option -v) affiche la requête et la réponse
HTTP. Vous pouvez voir dans la réponse l’URI de la ressource livre qui a été créée
(son identifiant est 601) :
> POST /chapter15-resource-1.0/rs/books HTTP/1.1
> User-Agent: curl/7.19.0 (i586-pc-mingw32msvc) libcurl/7.19.0 zlib/1.2.3
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Content-Length: 127
>
< HTTP/1.1 201 Created
< X-Powered-By: Servlet/3.0
< Server: GlassFish/v3
< Location: http://localhost:8080/chapter15-resource-1.0/rs/books/601
< Content-Type: application/xml
< Content-Length: 0
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 541
{"description":"Scifi IT book","illustrations":"false",
„ "isbn":"1234-234","nbOfPage":"241","price":"24.0","title":"H2G2"}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
542 Java EE 6 et GlassFish 3
Avec le mode verbeux de cURL, on voit la requête DELETE qui est envoyée et le code
d’état 204 No Content qui apparaît dans la réponse pour indiquer que la ressource
n’existe plus :
curl -X DELETE http://localhost:8080/chapter15-resource-
„ 1.0/rs/books/61 -v
> DELETE /chapter15-resource-1.0/rs/books/61 HTTP/1.1
> User-Agent: curl/7.19.0 (i586-pc-mingw32msvc)
„ libcurl/7.19.0 zlib/1.2.3
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 204 No Content
< X-Powered-By: Servlet/3.0
< Server: GlassFish/v3
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>
com.sun.jersey.config.property.packages
</param-name>
<param-value>com.apress.javaee6.chapter15</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 543
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/rs/*</url-pattern>
</servlet-mapping>
</web-app>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapter15-resource</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>Chapter 15 - REST Resource</name>
<parent>
<groupId>com.apress.javaee6</groupId>
<artifactId>chapters</artifactId>
<version>1.0</version>
</parent>
</dependencies>
<dependency>
<groupId>javax.ws.rs</groupId>
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
544 Java EE 6 et GlassFish 3
<artifactId>jsr311-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.ejb</artifactId>
<version3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Pour compiler et assembler les classes, ouvrez une fenêtre de commandes et pla-
cez-vous dans le répertoire contenant le fichier pom.xml, puis entrez la commande
suivante :
mvn package
Une fois le code assemblé et lorsque GlassFish et Derby s’exécutent, nous pouvons
déployer le fichier war à l’aide de la commande asadmin. Ouvrez une fenêtre de
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Chapitre 15 Services web REST 545
Exécution de l’exemple
Lorsque l’application a été déployée, vous pouvez utiliser cURL en ligne de com-
mande pour créer des ressources livres à l’aide de requêtes POST ou obtenir des
ressources spécifiques avec des requêtes GET. Pour supprimer des livres, utilisez la
requête DELETE :
// Création d’un livre
curl -X POST --data-binary
„ "{ \"title\":\"H2G2\", \"description\":\"Scifi IT book\",
„ \"illustrations\":\"false\",\"isbn\":\"134-234\",
„ \"nbOfPage\":\"241\", \"price\":\"24.0\" }"
„ -H "Content-Type: application/json"
„ http://localhost:8080/chapter15-resource-1.0/rs/books
{"description":"Scifi IT book","illustrations":"false",
„ "isbn":"1234-234","nbOfPage":"241","price":"24.0","title":"H2G2"}
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
546 Java EE 6 et GlassFish 3
Résumé
Le chapitre précédent a présenté les services web SOAP. Ceux-ci utilisent une enve-
loppe spéciale, sont décrits avec WSDL et peuvent être invoqués par différents pro-
tocoles, dont HTTP. Ce chapitre a décrit les services web REST, et vous connaissez
maintenant la différence entre une implémentation de JAX-WS et JAX-RS.
Nous avons commencé par présenter REST. À l’aide de verbes simples (GET, POST,
PUT, etc.) et d’une interface uniforme, nous avons vu comment accéder à n’importe
quelle ressource déployée sur le Web. Puis nous nous sommes intéressés à HTTP, un
protocole reposant sur un mécanisme de requêtes/réponses utilisant une négociation
pour choisir le type de contenu approprié en réponse à une requête.
La mise en cache permet d’optimiser le trafic réseau grâce à des requêtes condition-
nelles et à des en-têtes ETag. Nous avons vu que REST pouvait tirer parti de cette
optimisation car, comme lui, elle repose sur HTTP.
JAX-RS est une API Java fournie avec Java EE 6 qui simplifie le développement
des services web REST. À l’aide d’annotations, nous pouvons définir le chemin et
les sous-chemins permettant d’accéder à une ressource, extraire différents types de
paramètres ou traduire du code Java en méthodes HTTP (@GET, @POST, etc.). Lorsque
l’on développe des services web REST, il est important de raisonner en termes de
ressources, d’étudier la façon dont elles sont reliées et de savoir gérer leur état avec
HTTP.
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Index
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
548 Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Index 549
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
550 Java EE 6 et GlassFish 3
D exception
d'application 294
dataTable (JSF) 369 système 294
dateParam (JSTL) 354 executeUpdate(), méthode de Query 173
dblook, programme 40 expression (JSP) 344
DCOM (Distributed Component Object Model) 469
debug (Facelets) 359
déclaration (JSP) 343 F
decorate
(Facelets) 359 faces-config.xml, fichier 311, 318, 328, 390, 391,
(JSF) 372 392, 395, 396, 398, 399, 400, 401, 406, 407, 409
define FacesContext
(Facelets) 359 classe 387, 389, 401, 406, 408, 409
(JSF) 372 objet implicite 381, 389
DELETE, instruction JPQL 170 FacesMessage, classe 389, 401, 402, 408, 409
Destination, interface 431, 435, 453, 454 FacesServlet, classe 386, 387, 388, 390, 391, 392,
detach(), méthode d'EntityManager 158 398, 399
Direction d'une relation 106 facet (JSF) 369, 376
DISTINCT, clause JPQL 166 find(), méthode d'EntityManager 151, 154, 188
DOM, API 13 flush(), méthode d'EntityManager 151, 156
forEach (JSTL) 350, 355
formatDate (JSTL) 352
E
formatNumber (JSTL) 352
EAI (Enterprise Application Integration) 465 form (JSF) 371
ear forTokens (JSTL) 350
archive 15 forward, action JSP 345
fichier archive 208 fragment
EclipseLink, framework 54 (Facelets) 359
ECMA (European Computer Manufacturers (JSF) 372
Association) 339 FROM, clause JPQL 167
editableValueHolder (JSF) 377 Future, interface 253
EJBContext, interface 247
EJBException, exception 291, 294
ejb-jar.xml, fichier 208, 249, 281, 292, 297 G
EJBTransactionRequiredException, exception 291
getProperty, action JSP 345
element, action JSP 345
getReference(), méthode d'EntityManager 151, 154
endsWith (JSTL) 358
getResultList(), méthode de Query 173
EntityManager
getSingleResult(), méthode de Query 173
classe 62
gestionnaire d'entités 59 graphicImage (JSF) 368
interface 142, 146 GROUP BY, clause JPQL 169
EntityManagerFactory, classe 62, 144
EntityNotFoundException, exception 154 H
EntityTransaction, classe 152
escapeXml (JSTL) 358 HAVING, clause JPQL 170
ETag, en-tête HTTP 515, 546 headerValues, objet implicite 381
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Index 551
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
552 Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Index 553
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
554 Java EE 6 et GlassFish 3
customer 27921 at Fri Mar 11 19:07:20 +0100 2011 Propriété de Albiri Sigue <tag.tog@gmail.com>
Référence
Java EE 6 ™
et GlassFish 3 ™
Développement
web Niveau : Tous niveaux
Configuration : Multiplate-forme