Struts 2, un lifting complet Struts est un des premiers frameworks Web open source du monde Java. Depuis 2000, il s est imposé dans la communauté des développeurs Web, notamment grâce à son adoption par les plus grands éditeurs et intégrateurs d applications. En offrant une implémentation du pattern Model / View / Controller, Struts facilite alors la construction d applications à base de Servlets et de JSP. Struts avait bien des avantages, mais révèle maintenant des limites, qui en font aujourd hui un système legacy. Parmi ces limitations, on peut notamment citer une implémentation peu évolutive et une complexité excessive, nécessitant une compétence propre au framework. D autres frameworks n ont pas tardés à arriver sur le marché, chacun avec ses spécificités. Actuellement, la tendance est aux outils non intrusifs, une approche prônée en particulier par le framework Spring. La tendance est également à une réduction du nombre de langages, facilitée par l introduction des annotations dans le langage Java. La programmation par aspects est également utilisée pour fournir des mécanismes d interceptions. Struts 2 est issue de la fusion en 2006 des nouveaux développements autour de Struts, d une part, et du framework WebWork de OpenSymphony, d autre part. Struts 2 est plus extensible avec une architecture à base de plugins, et apporte également de nouvelles fonctionnalités comme le support d AJAX, la configuration par annotations et des mécanismes d interceptions. Les actions dans Struts 2 Struts 2 est avant tout un framework basé sur des actions. Une action est un traitement déclenché suite à une requête HTTP. L action retourne un dénouement sous forme d une chaîne de caractères. Une décision de navigation peut être prise en fonction de la valeur de ce dénouement. Il peut s agir de l affichage d une autre page JSP, d un message d erreur ou bien encore de l envoi d un e-mail. Action PAYER échec réussite Page Paiement Page Confirmation Figure 1 : Action Struts 2 Dans Struts 2, les actions peuvent maintenant êtres implémentées par des POJO (Plain Old Java Object). Ainsi, il est plus facile de tester les classes de façon isolées et le code source devient moins couplé au framework. Également, la configuration XML peut être réduite au minimum par l utilisation des annotations et par la préférence donnée aux conventions plutôt qu à la configuration. Un exemple de projet Struts 2 Nous présenterons le modèle de programmation de Struts 2 à travers un exemple. Nous nous focaliserons sur les principaux apports de Struts 2. Neoxia 2008 1 / 13
L application exemple proposée se compose d un ensemble de pages Web : Une page d accueil (Welcome.jsp) permet de choisir entre s authentifier et créer un nouveau compte. Une page d authentification (Login.jsp) permet de se connecter à l application. Une page de création de compte (Register.jsp) permet de créer un compte. Une page de confirmation (Confirmation.jsp) permet de valider la création du compte. Cette page n est pas spécifiquement couverte dans l article, dans la mesure où elle utilise uniquement des fonctions déjà décrites par ailleurs. Une page d accueil des membres (Member.jsp) accueille les utilisateurs connectés. Configuration et conventions Struts 2 nécessite un nombre très restreint de fichiers de configuration et s appuie essentiellement sur des conventions de nommage. Ce principe est souvent désigné par convention over configuration. Ainsi, une action n a pas à hériter d une classe particulière du framework. Il n est pas non plus nécessaire de la déclarer dans un fichier de configuration. Il existe néanmoins un minimum de configuration à effectuer dans le fichier de configuration WEB- INF/web.xml présenté dans le Listing 1. <?xml version="1.0" encoding="utf-8"?> <web-app id="webapp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>neoxia Application Struts2</display-name> <filter> <filter-name>struts2</filter-name> <filter-class> org.apache.struts2.dispatcher.filterdispatcher </filter-class> <init-param> <param-name>actionpackages</param-name> <param-value>com.neoxia.action</param-value> </init-param> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app> Listing 1 : Configuration de l application Web (web.xml) Dans le fichier de configuration, on déclare un filtre baptisé struts2 et comportant un paramètre actionpackages de valeur com.neoxia.action. Ce paramètre permet de préciser le package où se trouvent les classes actions. Neoxia 2008 2 / 13
La page d accueil Le Listing 2 présente le code de la page d accueil (Welcome.jsp). <%@ page contenttype="text/html; charset=utf-8"%> <%@ taglib prefix="s" uri="/struts-tags"%> <html> <head><title>bienvenue</title></head> <body> <h3>bonjour, authentifiez-vous pour accéder au service, ou créez un nouveau compte!</h3> <ul> <li><a href="<s:url value="login.jsp"/>">authentification</a></li> <li><a href="<s:url value="register.jsp"/>">créer nouveau compte</a></li> </ul> </body> </html> Listing 2 : Page d accueil (Welcome.jsp) Le tag Struts <s:url value="login.jsp"/> permet de construire une URL désignant la page d authentification Login.jsp. Le tag Struts <s:url value="register.jsp"/> permet de construire une URL désignant la page de création de compte Register.jsp. La page d authentification Le Listing 3 présente le code de la page d authentification (Login.jsp). <s:if test="errorcode=='loginerror'"> Nom d'utilisateur ou mot de passe incorrect! </s:if> <s:form action="login"> <s:textfield label="nom d'utilisateur" name="username"/> <s:password label="mot de passe" name="password" /> <s:submit/> </s:form> Listing 3 : Page d authentification (Login.jsp) Formulaire La page comporte un formulaire définit par le tag <s:form action="login">. Lorsque le formulaire est soumis, l action login est exécutée. Par convention, Struts invoquera la méthode execute() de la classe LoginAction. Neoxia 2008 3 / 13
Le formulaire se compose de divers tags Struts : un champ de saisie <s:textfield> baptisé username, un champ de saisie de mot de passe <s:password> baptisé password, et un bouton de soumission du formulaire <s:submit>. Le tag <s:if test="errorcode=='loginerror'"> est un test conditionnant l affichage d un message en cas d échec d authentification. L expression OGNL errorcode=='loginerror' correspond à la condition à évaluer. L action login est implémentée par la classe LoginAction présenté dans le Listing 4. Neoxia 2008 4 / 13
@Results({ @Result(name="success", type=servletdispatcherresult.class, value="member.jsp"), @Result(name="input", type=servletdispatcherresult.class, value="login.jsp") ) public class LoginAction implements ServletRequestAware { private String username; private String password; private String message; private User user; private UserManager usermanager; private HttpServletRequest request; public String execute() { if (isinvalid(username) isinvalid(password)) return "input"; user = usermanager.finduser(username, password); if ( user!= null) { request.getsession(true).setattribute("user", user); return "success"; else { errorcode = "loginerror"; return "input"; private boolean isinvalid(string value) { return (value == null value.length() == 0); public void setservletrequest(httpservletrequest httpservletrequest) { request = httpservletrequest; public String geterrorcode() { public void seterrorcode(string errorcode) { public String getusername() { public void setusername(string username) { public String getpassword() { public void setpassword(string password) { public User getuser() { public void setuser(user user) { public UserManager getusermanager() { public void setusermanager(usermanager usermanager) { Listing 4 : Action login pour l authentification d un utilisateur (LoginAction.java) Action et dénouements La classe LoginAction implémente la méthode execute() qui est appelée par Struts au moment de l exécution de l action. Avant d exécuter l action, Struts injecte les valeurs des champs du formulaire dans les propriétés de l action, en particulier : il appelle la méthode setusername( ) pour injecter la valeur récupérée depuis le champ de saisie username, il appelle la méthode setpassword( ) pour injecter la valeur récupérée depuis le champ de saisie password. Neoxia 2008 5 / 13
La méthode execute() retourne une chaîne de caractères correspondant au dénouement de l action. Elle retourne le dénouement "success", en cas de réussite, et "input", en cas d échec. La méthode execute() utilise un service UserManager pour valider l authenticité de l utilisateur. En cas de réussite, l utilisateur connecté (instance de la classe User) est affecté à l attribut user de la session HTTP. En cas d échec, la valeur "loginerror" est affectée à la propriété errorcode de l action. A l issue de l exécution de l action, Struts récupère les valeurs des propriétés de l action pour les recopier dans les attributs de requête (HttpServletRequest), en particulier : il appelle la méthode getuser( ) et recopie la valeur retournée dans l attribut user de la requête, il appelle la méthode geterror( ) et recopie la valeur retournée dans l attribut errorcode de la requête. Accès à la session La classe LoginAction implémente l interface ServletRequestAware. La classe doit alors implémenter la méthode setservletrequest( ) qui permet à Struts d injecter la requête (HttpServletRequest). La méthode execute() peut ainsi accéder à la session (HTTPServlet) via la requête. Règles de navigation La classe LoginAction est également décorée par des annotations qui définissent les règles de navigation en fonction de la valeur du dénouement. L annotation @Results regroupe des annotations @Result, définissant chacune une règle de navigation : L annotation @Result(name="success",, value="member.jsp") décrit la navigation vers la page d accueil des membres (Member.jsp) dans le cas du dénouement success. L annotation @Result(name="input",, value="login.jsp") décrit un retour vers la même page (Member.jsp) dans le cas du dénouement input. L utilisateur peut ainsi ressaisir les champs du formulaire. Dans les deux cas, l attribut type=servletdispatcherresult.class indique qu un forward (API Servlet) est effectué, plutôt qu une redirection. Dans la terminologie Struts 2, ce comportement de navigation est appelé result type. Les règles de navigation peuvent également êtres déclarées dans le fichier de configuration struts.xml. Struts 2 propose de multiples result types tels qu une redirection ou une transformation XSL. Il est aussi possible de fournir l implémentation de son propre result type, par exemple pour déclencher l envoi d un e- mail. Intégration avec le framework Spring L utilisation du framework Spring permet d injecter une instance du service UserManager dans l action LoginAction. Le framework Spring est intégré avec Struts 2 via un plug-in fourni. Pour effectuer l intégration, il est nécessaire de déployer le JAR du plug-in, ainsi que les JAR de Spring dans le répertoire WEB-INF/lib. Un bean Spring baptisé usermanager est déclaré dans un fichier de configuration Spring baptisé applicationcontext.xml, et déposé dans le répertoire WEB-INF. Le bean usermanager est alors injecté automatiquement dans l action LoginAction via la méthode setusermanager( ). Neoxia 2008 6 / 13
La page d accueil des membres Le Listing 5 présente le code de la page d accueil des membres (Member.jsp) qui reçoit les utilisateurs authentifiés. <h3>bonjour, <s:label value="%{user.username" />!</h3> <ul> <s:url id="logouturl" action="logout" /> <li><s:a href="%{logouturl">deconnexion</s:a> </ul> Listing 5 : Page d accueil des membres (Member.jsp) La page se compose de divers tags Struts : un label qui affiche le nom de l utilisateur connecté, un lien qui permet de se déconnecter. La valeur du label <s:label> est paramétrée par l expression OGNL %{user.username. Lors de l évaluation de l expression, OGNL essaye d abord de récupérer un attribut user dans la requête. À défaut, OGNL essaye ensuite de récupérer un attribut user dans la session. Ici, user correspond à l attribut user de la requête. Cet attribut contient une instance de la classe User, déposée par Struts après l exécution de l action LoginAction. OGNL appelle finalement la méthode getusername() de l instance récupérée. Le tag <s:url id="logouturl" action="logout"/> construit l URL de l action logout et baptise l URL logouturl. Cette URL est de la forme : http://www.neoxia.com:8080/registration/logout.action Le lien <s:a> est également paramétré par l expression OGNL %{logouturl qui fait référence à l URL logouturl. La page de création de compte La page de création de compte permet de saisir des informations relatives à un utilisateur, afin de créer le compte. Les informations saisies sont validées selon les critères suivants : Tous les champs sont obligatoires. L adresse e-mail doit avoir une syntaxe valide (nom@domaine.extension). Le mot de passe doit contenir au minimum 6 caractères. La Figure 2 illustre le formulaire, quand des données invalides sont saisies. Neoxia 2008 7 / 13
Figure 2 : Page de création de compte avec contrôle de saisie Struts 2 introduit une validation des champs de formulaires fondée sur des annotations. Les contraintes sont déclarées sur les propriétés à valider, directement dans le code Java. Par ailleurs, il est toujours possible d utiliser une configuration par XML de la validation, ou de mixer les deux approches. Le Listing 6 présente le code de la page de création de compte (Register.jsp). <s:form action="register" validate="true"> <s:textfield name="username" label="nom d'utilisateur" /> <s:textfield name="email" label="e-mail" /> <s:password name="password" label="mot de passe" /> <s:submit value="créer"/> </s:form> Listing 6 : Page de création de compte (Register.jsp) La page comporte un formulaire définit par le tag <s:form action="register" >. Lorsque le formulaire est soumis, l action register est exécutée. L attribut validate="true" indique que la validation doit s effectuer coté client, c'est-à-dire dans le navigateur, grâce à du code JavaScript généré automatiquement. Attention, l absence de l attribut validate (ou son affectation à false) ne désactive pas la validation, qui est toujours effectuée côté serveur. En revanche, dans ce cas, les messages d erreurs à afficher doivent explicitement être déclarés dans la page JSP via des tags Struts fielderror. Le Listing 7 présente le code de l action register. Neoxia 2008 8 / 13
@Results({ @Result(name="success", type=servletdispatcherresult.class, value="/confirmation.jsp"), @Result(name="input", type=servletdispatcherresult.class, value="/register.jsp") ) @Validation public class RegisterAction implements ServletRequestAware { private User user; private String username; private String email; private String password; private HttpServletRequest request; public String execute() { if (isinvalid(getusername()) isinvalid(getemail()) isinvalid(getpassword())) { return "input"; user = new User(username,email,password); request.getsession(true).setattribute("user", user); return "success"; @EmailValidator(message="Vous devez entrer une adresse email valide.") @RequiredStringValidator(message="Champ obligatoire.", trim=true) public void setemail(string email) { @RequiredStringValidator(message="Champ obligatoire.", trim=true) public void setusername(string username) { @StringLengthFieldValidator(message="Le mot de passe doit contenir au minimum 6 caractères.", trim=true, minlength="6") public void setpassword(string password) { Listing 7 : Action register de création de compte (RegisterAction.java) La classe RegisterAction est décorée par l annotation @Validation qui indique qu elle contient des propriétés à valider, en particulier : La méthode setemail( ) porte l annotation @EmailValidator qui indique que la propriété email doit respecter le format d une adresse e-mail. La méthode setusername() porte l annotation @RequiredStringValidator qui indique que la propriété username est obligatoire. L attribut trim indique que les espaces en fin de chaîne doivent être supprimés. La méthode setpassword( ) porte l annotation @StringLengthFieldValidator(, minlength=6, ), qui indique que la propriété password doit avoir une taille minimale de 6 caractères. Pour toutes ces annotations, l attribut message permet d indiquer que le message à afficher si la règle de validation n est pas respectée. Struts 2 propose de nombreux types de validation et permet même d écrire ses propres validateurs. Neoxia 2008 9 / 13
Le support d AJAX Struts 2 permet de créer facilement des pages AJAX, permettant ainsi de rafraîchir une portion de page sans la recharger intégralement D un point de vue technique, Struts 2 s appuie, à la fois, sur du code JavaScript, côté navigateur, et sur du code Java, côté serveur. Le toolkit AJAX utilisé est Dojo qui fournit entre autres des widgets JavaScript. La Figure 3 illustre le formulaire de création de compte modifié. Un lien Help! est ajouté, permettant d afficher de l aide directement dans la page. Ce lien montre et masque alternativement l aide à chaque clic, sans rafraîchir intégralement la page. Figure 3 : Aide affichée grâce à AJAX Le Listing 8 présente les modifications effectuées sur le code de la page Register.jsp. <head> <s:head theme="ajax"/> </head> <s:url id="helpurl" action="help" /> <s:a href="%{helpurl" theme="ajax" targets="helpdiv">help!</s:a> <s:div id="helpdiv" theme="ajax"/> Listing 8 : Page de création de compte avec aide (Register.jsp) La page de création de compte comporte diverses modifications : Le tag <s:head theme="ajax"/> permet d activer la prise en charge d AJAX dans la page. Le tag <s:div id="helpdiv" theme="ajax"/> définit une zone identifiée par l id helpdiv. Le tag <s:a theme="ajax" targets="helpdiv"> correspond à un lien de type AJAX. L attribut targets="helpdiv" indique de rafraîchir la zone identifiée par l id helpdiv à partir du résultat de l action help. Dans ce cas, le résultat doit être une page qui génère un fragment HTML. Le Listing 9 présente le code de l action help qui gère l affichage et le masquage de l aide. Neoxia 2008 10 / 13
@Result(name="success", type=servletdispatcherresult.class, value="ajaxresult.jsp") public class HelpAction implements ServletRequestAware { private String helpusername = "John Smith"; private String helpemail = "john.smith@domain.com"; private String helppassword = "<minimum 6 caractères>"; private HttpServletRequest request; private Boolean help; public String execute() { help = (Boolean) request.getsession(true).getattribute("help"); if (help == null ) { help = false; help ^= true; request.getsession(true).setattribute("help", help); return "success"; public String gethelpusername() { public void sethelpusername(string helpmessage) { public String gethelpemail() { public void sethelpemail(string helpemail) { public String gethelppassword() { public void sethelppassword(string helppassword) { public Boolean gethelp() { public void sethelp(boolean help) { public void setservletrequest(httpservletrequest httpservletrequest) { Listing 9 : Action help de gestion de l affichage et du masquage de l aide (HelpAction.java) L annotation @Result(name="success",, value="ajaxresult.jsp") demande à Struts de renvoyer vers la page AjaxResult.jsp, qui génère le fragment HTML d aide. A l issue de l exécution de l action, Struts récupère les valeurs des propriétés de l action pour les recopier dans des attributs de requête (HttpServletRequest), en particulier : il appelle la méthode gethelp( ) et recopie la valeur retournée dans l attribut help de la requête, il appelle de même les méthodes gethelpusername( ), gethelpemail( ) et gethelppassword( ), puis recopie respectivement les valeurs retournées dans les attributs helpusername, helpemail et helppassword de la requête. Le Listing 10 présente le code de la page de génération du fragment HTML d aide. Neoxia 2008 11 / 13
<% request.setattribute("decorator", "none"); response.setheader("cache-control","no-cache"); //HTTP 1.1 response.setheader("pragma","no-cache"); //HTTP 1.0 response.setdateheader ("Expires", 0); //pas de cache sur serveur proxy %> <s:if test="help""> <ul> <li><i>nom d'utilisateur</i> : <s:property value="helpusername"/><br/> <li><i>email</i> : <s:property value="helpemail"/><br/> <li><i>mot de passe</i> : <s:property value="helppassword"/> </ul> </s:if> Listing 10 : Page de génération du fragment HTML d aide (AjaxResult.jsp) Le tag <s:property value="helpusername"/> permet d insérer la valeur issue de l attribut helpusername de la requête. Plus loin Struts 2 est un framework Web à action, classique dans sa conception, et répondant à la plupart des attentes actuelles des développeurs Java. Il saura en particulier séduire les utilisateurs de son prédécesseur, encore largement présent dans les applications existantes. Néanmoins, Struts 2 est bien une rénovation de fond en comble de Struts dont l architecture devenait vieillissante. Struts 2 gagne en cohérence et en élégance, tout en apportant de nombreuses améliorations dont : configuration facilitée grâce aux annotations et aux conventions, classes d actions POJO et découplées de l API Servlet, validation de saisie améliorée, support d AJAX. Cependant, même si Struts 2 partage le même style architectural que le Struts premier du nom, son implémentation n est pas une évolution à partir du code source de Struts, mais bien une réécriture complète, à partir du framework WebWork. En ce sens, il souffre encore à ce jour d un manque de maturité : La documentation de base est relativement insuffisante, mais des livres sont d ores et déjà disponibles. À la mise à jour du framework, on constate à l occasion certaines instabilités dues notamment aux problèmes de compatibilité. En résumé, Struts 2 est un framework encore jeune, mais à fort potentiel d adoption. On peut cependant regretter parfois une certaine frilosité, et un certain manque d innovation par rapport à d autre frameworks tels que Seam. Neoxia 2008 12 / 13
Références bibliographiques et Web Site officiel de Struts 2 http://struts.apache.org/2.x/ Quelques livres en anglais : Ian Roughley, Practical Apache Struts 2 Web 2.0 Projects, Apress, 2007. Don Brown, Chad Davis, Scott Stanlick, Struts 2 in Action, Manning Publications, 2008. Budi Kurniawan, Struts 2 Design and Programming: A Tutorial, BrainySoftware, 2008. James Holmes, Struts 2: The Complete Reference, McGraw-Hill Osborne Media. (à paraître). F. T. Neoxia 2008 13 / 13