Navigation

Introduction à Seam

Un article de ODcWiki.

Jump to: navigation, search
Image:Auteur.png Auteur : hdarmet.


Java Enterprise Edition 5 propose deux technologies de « haut niveau » pour développer des applications Web : JavaServer Faces pour la partie présentation et EJB 3.0/JPA pour la partie métier. Aujourd’hui, ces deux technologies coexistent plus qu’elles ne coopèrent : il n’existe aucune API permettant de les coupler aisément. Conséquence : les bonnes pratiques pour écrire, en Java, les applications Web JSF/EJB 3.0 de demain restent à inventer.

C’est à cet exercice que s’est livré une équipe de JBoss dirigée par Gavin King, le célébrissime père d’Hibernate. Le résultat est un produit spécifique JBoss appelé « seam » (couture en anglais) que l’équipe de G. King aimerait normaliser via le JCP (Java Community Process) sous le JSR (Java Specification Request) 299.

L’objectif de cet article est de vous présenter concrètement ce qu’est « seam », quels sont ces principes fondamentaux, ses avantages, ses limites. Comme il y a une réelle probabilité que « seam » (appelé « WebBeans » dans le JSR 299 en cours d’élaboration) soit la base des développements Java/Web de demain, il est utile de savoir dès aujourd’hui de quoi il s’agit.

Sommaire

L’environnement de Seam

Seam est une API d’intégration. Seam intègre d’abord et avant tout JavaServer Faces et EJB 3.0/JPA. Elle permet aussi d’intégrer JBoss Rules (moteur de règles JBoss) et JPBM (solution de Workflow JBoss). Autant de sujets ne pouvant pas être abordé dans un article qui a vocation d’être court, je ne présente ici que l’intégration JSF et EJB 3.0/JPA.

JSF (JavaServer Faces) est un framework Web puissant, assez complexe, basé sur les couches techniques standard de Java Web (JSP servlets) et concurrent de solutions comme Struts ou Webworks. Le principe de JSF s’inspire de celui d’une application client lourde : on définit un arbre de composants graphiques (une fenêtre qui contient des panels, qui a leur tour contiennent des champs ou des boutons, etc) et on « branche » des petits traitements sur ces composants. Par exemple on associe une méthode à l’activation d’un bouton, une autre méthode au changement de contenu d’un champ, et ainsi de suite.

L’arbre de composants graphiques JSF est traditionnellement défini via une JSP, à l’aide d’un jeu de balises JSF. Dans l’exemple suivant :

<f:view xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core">
 
<html>
    <body>
      <h:form>
        <h:outputText  value="id : " /><h:inputText value="#{connection.id}" /><br>
        <h:outputText  value="mdp : " /><h:inputSecret value="#{connection.mdp}" /><br>
        <h:messages /><br>
        <h:commandLink value="connexion" action="#{connection.connect}" />
      </h:form>
		
    </body>
</html>
</f:view>

La fenêtre graphique est définie au sein de la balise « f:view ». Elle contient un formulaire (balise « f:form ») regroupant des contrôles élémentaires qui sont : soit des labels (balises « h:outputText ») soit des champs en saisie (balises « h:inputText ») soit un hyper lien (balise « h:commandLink »). Le code de cette JSP n’intègre directement aucun code Java. A la place, des références vers du code java sont mentionnées dans les balises JSF. Ces références s’expriment dans un langage spécifique à JSF, appelé JSF EL (JSF Expression language) qui prend la forme suivante : « #{…} », proche du JSP EL plus traditionnel.

L’expression « #{connection.id} » associé à l’attribut value d’un champ de saisie indique que ce champ de saisie présente la propriété « id » du JavaBean « connection ». Cela signifie qu’il existe en session, en requête ou au niveau de l’application web tout entière, un JavaBean enregistré sous l’étiquette « connect », et que ce JavaBean définit les méthodes « getId » et « setId ». De même, l’expression « #{connection.connect} » associé à l’attribut « action » de l’hyper lien indique qu’en cas d’activation de ce lien, la méthode « connect() » de ce même JavaBean « user » doit être invoquée. Quand s’agit-il d’une propriété ? Quand s’agit-il d’une méthode ? C’est l’attribut qui décide : pour un attribut « value », une propriété est recherchée. Pour un attribut « action », ce sera une méthode respectant la signature : String method().

Notez que ce mécanisme suffit pour créer des IHM Web complexes. Il n’est plus utile de mettre en place un modèle MVC explicite comme Struts avec un JavaBean formulaire et un objet commande.

L’autre technologie intégrée par Seam est EJB 3.0/JPA. Il s’agit cette fois de composants serveurs. Ecrire un composant EJB 3.0 est très simple. Il faut définir une interface et une classe JavaBean implémentant cette interface. Le code Java ne fait appel à aucune API spécifique aux EJB. C’est un véritable POJO, à l’instar de ce qui existe pour Spring. Pour faire de notre classe un composant EJB, il suffit de rajouter deux annotations :

Une annotation sur l’interface qui indique le type d’accès souhaité (@Local ou @Remote) Une annotation sur la classe du POJO qui indique le mode de gestion souhaité (@Stateless ou @Stateful).

Connection.java :

package com.objetdirect.examples.odace.actions;
import javax.ejb.Local;
 
@Local
public interface Connection {
	
	public String getId();
	public void setId(String id);
	public String getPassword();
	public void setPassword(String password);
	public String connect();
}

ConnectionBean.java :

package com.objetdirect.examples.odace.actions;
import javax.ejb.Stateful;
 
@Stateful
public class ConnectionBean implements Connection {
 
	String id;
	String password;
 
	public String getId() { return id; }
	public void setId(String id) {
		this.id = id;
	}
 
	public String getPassword() { return password; }
	public void setPassword(String password) {
		this.password = password;
	}
 
	public String connect() {
		...
	}
}

Et c’est tout. Aucun fichier de configuration n’est à enrichir.

Premiers pas avec Seam

L’idée fondamentale de « seam » est d’utiliser les POJO EJB 3.0 comme bean JSF. Les expressions JSF EL atteindront alors des méthodes et propriétés publiques de l’EJB. Ainsi les deux mondes sont réunis : la couche de présentation manipule des objets disposant directement de la panoplie des services proposés par la norme EJB, en particulier la gestion des transactions et la sécurité.

Pour inscrire un EJB 3.0 dans un contexte JSF (session, requête, etc), il faut l’annoter par @Name et éventuellement par @Scope si le contexte par défaut ne vous convient pas :

package com.objetdirect.examples.odace.actions;
import javax.ejb.Stateful;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
 
@Stateful
@Name("connection")
@Scope(ScopeType.SESSION)
public class ConnectionBean implements Connection {
...

Dans cet exemple, un SessionBean sans état est utilisé comme bean JSF. Il est inscrit dans la session sous l’étiquette « user ». Question : quand et par qui est il créé ? Il est créé par JSF/Seam à la première mention de l’étiquette « user » dans une expression JSF EL.

JSF propose nativement un système comparable : les beans « managés » (managed beans) : le développeur déclare dans le fichier de configuration de JSF (appelé généralement faces-config.xml) des JavaBeans dits « managés » en précisant chaque fois : la classe du bean, l’étiquette sous laquelle le bean sera retrouvé, l’endroit où il sera stocké (requête, session, application). Toute référence à l’étiquette d’un bean managé est automatiquement reconnue, le bean ciblé est retrouvé, et même créé si nécessaire.

Seam vient donc en remplacement de ce mécanisme. Les annotations @Name et @Scope contiennent les informations que le fichier faces-config.xml contient pour les managed beans. La solution proposée par seam s’avère plus pratique : ajouter des annotations – très bien gérées par l’IDE comme Eclipse – est à la fois plus souple et plus robuste qu’une déclaration XML.

Au cœur de Seam : Injection et outjection

Seam va plus loin avec son mécanisme d’injection/« outjection ». L’idée est d’échanger des informations entre les beans (EJB et autres) entre eux et avec les pages JSF. Ce mécanisme repose sur la notion de contexte. Les beans comme les pages JSF peuvent récupérer des informations depuis ces contextes (c’est l’injection) ou en déposer (c’est l’outjection). Pour rechercher une information à partir d’un contexte depuis un bean géré par Seam (généralement un EJB SessionBean) il suffit de créer un attribut dans ce bean est de l’annoter avec @In :

package com.objetdirect.examples.odace.actions;
import javax.ejb.Stateful;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.In;
 
@Stateful
@Name("connection")
@Scope(ScopeType.SESSION)
public class ConnectionBean implements Connection {
 
	String id;
	String password;
	@In("entityManager")
	EntityManager em;
 
...
 
	public String connect() {
		List cnx = (List)em.createQuery(
"from User u where u.id=:id and u.password=:password").
			setParameter("id", id).
			setParameter("password", password).getResultList();
		…
	}
}

Dans cette snippet, l’attribut « em » sera automatiquement initialisé par Seam en recherchant dans les contextes, l’objet (ici un EntityManager) connu sous l’étiquette « entityManager ». Il faut bien sûr que cet objet soit disponible, donc construit au préalable par Seam. Comme il ne s’agit pas d’un bean créé par nous, donc annoté par nous, conformément aux principes de Seam, une autre méthode que l’annotation par @Name et @Scope doit être utilisée.

Seam propose de piloter la création de composants correspondant à des classes non annotées, via un fichier de configuration nommé component.xml. Voici le contenu de ce fichier permettant de créer le gestionnaire d’entités dont le bean de connexion à besoin pour rechercher les couples identifiant/mot de passe :

<components 
	xmlns="http://jboss.com/products/seam/components"
	xmlns:core="http://jboss.com/products/seam/core">
	
    <component name="entityManager" scope="session"
        auto-create="true" class="org.jboss.seam.core.ManagedPersistenceContext">
       <property name="persistenceUnitJndiName">java:/odaceEntityManagerFactory</property>
     </component>
    	
    <component name="org.jboss.seam.core.init">
        <property name="debug">true</property>
        <property name="jndiPattern">ODace/#{ejbName}/local</property>
    </component>
	
    <component class="org.jboss.seam.core.Ejb" 
           installed="false"/>
 
</components>

Une fois de plus on y trouve les informations nécessaires pour créer et conserver un objet java dans un univers Web : classe, étiquette, portée.

La notion de contexte de Seam est très proche de la notion de portée du Web et la recouvre d’ailleurs :


  • Le contexte « event » est circonscrit au traitement d’une unique requête. Il correspond donc à la portée java/Web standard « request ».
  • Le contexte « session » est lié à une session utilisateur. Il correspond dont à la portée java Web homonyme.
  • Le contexte « application » est lié à l’application Web toute entière. Il correspond dont à la portée java Web homonyme.
  • Le contexte « conversation » existe sur plusieurs échanges http (donc plusieurs requêtes). C’est une sorte de contexte « session », thread safe et nettoyé régulièrement.
  • Il existe d’autres contexte spécifiques aux autres produits que Seam permet d’intégrer (JPBM par exemple).

L’outjection est le mécanisme inverse. En marquant @Out un attribut du bean, on provoque un report dans le contexte souhaité de l’objet désigné par cet attribut.

package com.objetdirect.examples.odace.actions;
import javax.ejb.Stateful;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Out;
 
@Stateful
@Name("connection")
@Scope(ScopeType.SESSION)
public class ConnectionBean implements Connection {
 
	String id;
	String password;
	@Out("user", scope=ScopeType.SESSION)
	User user;
 
...
 
	public void createNewUser() {
		user = new User();
		…
	}
}

L’objet User désigné par l’attribut user est réinjecté automatiquement dans la session http sous l’étiquette « user », à la sortie de la méthode createNewUser.

Utiliser Seam

Seam est un framework qui simplifie effectivement considérablement le travail d’intégration d’une application Web. C’était sa vocation première. Néanmoins, deux remarques s’imposent :

  • En utilisant des EJB comme beans JSF, Seam intègre dans la couche présentation, des objets qui ont vocation être d’abord métier. Seam favorise donc le mélange des genres : IHM et traitements. D’ailleurs Gavin King avoue que la façon la plus simple d’utiliser Seam est d’utiliser une architecture 2-tiers. C’est assez paradoxal, mais c’est ainsi !
  • Le seconde remarque est une conséquence de la première. JSF impose, dans certains cas, pour les JSF beans, une signature particulière faisant référence à des classes définies par JSF. Si ces méthodes sont implémentées par des EJB, il faut donc que ces EJB dépendent de l’API de JSF, qui dépend de l’API servlets. L’ordre normal de dépendance des API est complètement inversé !

Il est bien sûr possible de faire une « vraie » architecture 3 tiers avec Seam, en séparant soigneusement les EJB qui ont vocation d’être des JSF beans et ceux qui sont de véritables services métier. Mais on perd en simplicité et en robustesse (l’utilisation directe de gestionnaires d’entités gérés par Seam au sein des EJB Seam, permet d’éviter tous les problèmes liés à l’instanciation paresseuse – lazy instanciation – des entités). La tentation est donc très grande – et probablement pertinente – de se contenter d’une architecture de complexité minimale.

Boîte à outils
Dernière modification de cette page le 21 mai 2007 à 07:23