Spring MVC
Transcription
Spring MVC
Cedric Dumoulin Plan Bibliographie Header Footer et Body Internationalization (i18n) Validation Gestion des erreurs Injecter des composants Spring et JEE Bibliographie Spring Framework http://docs.spring.io/spring/docs/3.2.5.RELEASE/spring -framework-reference/htmlsingle/#overview Designing and Implementing a Web Application with Spring http://spring.io/guides/tutorials/web/ Bibliographie Spring IO http://spring.io/ Developing a Spring Framework MVC application step-by-step (2.5) http://docs.spring.io/docs/Spring-MVC-step-by-step/ Spring MVC Framework Tutorial http://www.tutorialspoint.com/spring/spring_web_mvc_fra mework.htm Wikipedia http://en.wikipedia.org/wiki/Spring_Framework Quick start http://projects.spring.io/spring-framework/#quick-start Bibliographie Spring 3.x tutorials http://www.roseindia.net/spring/spring3/index.shtml http://yannart.developpez.com/java/spring/tutoriel/ http://www.theserverside.com/tutorial/Spring-30Tutorial-Setting-Up-Configuring-The-Environment Guides Accessing Data with JPA http://spring.io/guides/gs/accessing-data-jpa/ Designing and Implementing a Web Application with Spring http://spring.io/guides/tutorials/web/ Rappel Spring MVC serveur applis appli1 servlet1 select appli2 appli3 ctrls url=ctrl2 pages view1 view1 select view2 view2 url=ctrl3 view2 reponse views url=ctrl1 ‘/’ select ‘appli1/ctrl1’ requete servlets view3 Rappel Spring MVC serveur applis appli1 servlet1 select appli2 appli3 ctrls views url=ctrl1 ‘/’ select ‘appli1/ctrl1’ requete servlets viewResolver déclaré ds springconfig.xml url=ctrl2 pages view1 view1 select view2 view2 url=ctrl3 view3 view2 reponse déclaré dans web-inf Dans une Classe JSP dans WEB-INF Tous les sites proposent des pages cohérentes Avec un header, un footer, un body, un menu … Faut-il coder ces éléments dans toutes les pages ? Et si on doit modifier le footer, faut-il alors modifier toutes les pages ? Solution : Utiliser un framework permettant de separer le header, le footer, le body et le menu … Ex: Tiles Principe Définir une seule fois les parties communes: Un header, un footer un main menu Assembler les pages Seule le body change il faut coder celui-ci à part. Une page == body + parties communes Vous définissez le body Le framework ce charge de construire la page en assemblant les parties pour vous Mise en œuvre layout1.jsp header1.jsp footer1.jsp 2) Assembler avec des définitions 3) choisir la ‘view’ dans le contrôleur menu2.jsp menu1.jsp body1.jsp body2.jsp 1) Définir des Tiles En pratique Master definition <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd"> <tiles-definitions> <definition name=" classicLayout" template="/WEB-INF/views/layout1.jsp"> <put-attribute name="title" value="My Title"/> <put-attribute name="header" value="/WEB-INF/views/header1.jsp"/> <put-attribute name="body" value=""/> <put-attribute name="footer" value="/WEB-INF/views/footer1.jsp"/> </definition> </tiles-definitions> En pratique View <tiles-definitions> <definition name= "view1" extends="classicLayout "> <put-attribute name="title" value="Another Title"/> <put-attribute name="body" value="/WEB-INF/views/body1.jsp"/> </definition> </tiles-definitions> Layout.jsp (attention aux pieges ) <%@ page contentType="text/html;charset=UTF-8"%> <%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%> <!DOCTYPE html> <html lang="en" class="no-js"> <head> <meta charset="UTF-8"> <title><tiles:insertAttribute name="title" ignore="true" /></title> <tiles:insertAttribute name="stylecss" /> </head> <body> <tiles:insertAttribute name="header" /> <tiles:insertAttribute name="body" /> <tiles:insertAttribute name="footer" /> <tiles:insertAttribute name="scriptjs" /> </body> </html> Configurer Spring <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <beans:bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> <beans:property name="definitions"> <beans:list> <beans:value>/WEB-INF/tiles.xml</beans:value> <beans:value>/WEB-INF/views/**/views.xml</beans:value> </beans:list> </beans:property> </beans:bean> <beans:bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <beans:property name="viewClass"> <beans:value> org.springframework.web.servlet.view.tiles3.TilesView </beans:value> </beans:property> <beans:property name="order" value="0"/> </beans:bean> Ajouter les dépendances <!-- tiles --> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-jsp</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-core</artifactId> <version>3.0.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-api</artifactId> <version>3.0.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-servlet</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-template</artifactId> <version>3.0.3</version> </dependency> Spring MVC et Tiles serveur applis appli1 servlet1 select appli2 appli3 view1 url=ctrl2 pages view2 url=ctrl3 view2 reponse definitions tiles ctrls url=ctrl1 ‘/’ select ‘appli1/ctrl1’ requete servlets lay out 1.js p select me nu2 .jsp bo dy1 .jsp hea der1 foo .jsp ter 1.js me p nu1. jsp bo dy 2.js p Spring MVC et Tiles serveur applis appli1 servlet1 select appli2 appli3 definitions tiles ctrls url=ctrl1 ‘/’ select ‘appli1/ctrl1’ requete servlets TilesViewResolver déclaré ds springconfig.xml view1 url=ctrl2 pages lay out 1.js p select me nu2 .jsp bo dy1 .jsp view2 hea der1 foo .jsp ter 1.js me p nu1. jsp bo dy 2.js p url=ctrl3 view2 reponse déclaré dans web-inf Dans une Classe tiles.xml fichiers jsp sous WEB-INF Webographie Spring MVC 4 Tiles 3 Integration Tutorial http://frameworkonly.com/spring-mvc-4-tiles-3integration/ Spring 3 MVC: Tiles Plugin Tutorial with Example in Eclipse http://viralpatel.net/blogs/spring-3-mvc-tiles-plugin- tutorial-example-eclipse/ Tiles https://tiles.apache.org/ Utilisation dans le contrôleur Spring Le contrôleur Spring doit renvoyer le nom logique de la page a afficher C’est le nom spécifié dans le fichier tiles.xml lors de la déclaration d’un assemblage … Atelier ateliers 2 (i18n, valid).docx Header Footer Body (Tiles) Spring fournit la notion de composant On peut injecter des composants dans d’autre composant Même principe que JEE Declarer un composant: @Component, @Named Injecter un composant: @Inject, @Autowired Déclarer un composant Par défaut, le nom est le nom simple de la classe (commençant par une minuscule) 2 tags équivalent: @Component et @Named On peut spécifier le nom @Component("injectedComponent") /** * A simple bean that will be injected elsewhere */ @Component public class InjectedBean { private String firstname = "John"; private String lastname = "Doe"; //.. Getter an setter } .. Déclare un composant Déclarer un composant (2) Indiquer a Spring de scanner les classes Dans le fichier de context <context:component-scan basepackage=‘’my.pacakge.base’’ /> Injecter un composant @Inject (et @Autowired, @Resource) Peut se faire sur un attribut, un setter, … Recherche si il existe un bean du nom de l’attribut, et compatible avec le type Si non recherche un bean compatible avec le type Si non erreur On ne peut pas spécifier le nom Sauf pour @Resource(‘name’) @Controller public class HomeController { /** * Try to inject a bean */ @Inject protected InjectedBean injectedBean; // .. } Spring injecte le bean du bon type Webographie 22.2 Accessing EJBs http://docs.spring.io/spring/docs/4.0.0.BUILDSNAPSHOT/spring-frameworkreference/htmlsingle/#ejb Injecter des EJB Session dans Spring C’est possible ! En deux temps: déclarer le bean Session en tant que Component Spring injecter le component Spring Déclarer le bean session en tant que Component declaration du namespace <?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" la location de la def du namespace xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation= "http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd"> Dans le fichier de configuration <jee:remote-slsb id="myComponent" jndi-name="java:global/ipint13.springetejb.ear/ipint13.springetejb.domain/MyServiceBean" business-interface="ipint13.springetejb.domain.MyService"/> </beans:beans> le nom jndi est affiché par le serveur dans ses logs le nom spring l’interface du bean JEE Injecter le bean Methode classique @Controller public class HomeController { @Inject protected MyService injectedBean; /** * Simply selects the home view to render by returning its name. */ @RequestMapping(value = "/", method = RequestMethod.GET) public String home(Locale locale, Model model) { // … if( injectedBean == null ) { logger.info("The bean is not injected !."); return "home"; } // Injection works ! model.addAttribute("myInjectedBean", injectedBean ); return "success"; } Accéder à un objet JNDI ou EJB <jee:jndi-lookup> Acces par JNDI <jee:local-slsb> Acces a un bean local <jee:remote-slsb> Acces à un bean distant <jee:local-slsb id="myComponent" jndi-name="ejb/myBean" business-interface="com.mycom.MyComponent"/> <bean id="myController" class="com.mycom.myController"> <property name="myComponent" ref="myComponent"/> </bean> Accéder à un objet JNDI ou EJB Autre méthode Permet d’utiliser le nom jndi directement dans les annotations A tester <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"> <property name="alwaysUseJndiLookup" value="true" /> </bean> Atelier ateliers 2 (i18n, valid).pdf Spring et JEE Webographie http://viralpatel.net/blogs/spring-3-mvc- internationalization-i18n-localization-tutorialexample/ Principe Utilise la notion de « Locale » : Pays_langue FR_fr, US_en Dans le code, on utilise des constantes à la place du texte Les constantes sont définies dans des fichiers Pairs nom=valeur Un fichier par langue Un fichier par défaut Tous les fichiers ont le même nom Mais des extensions en fonction du locale: messages.properties messages_FR_fr.properties Alternatives: Des pages différentes en fonction du locale Possible avec Tiles Spring MVC Les fichiers properties Permet d’utiliser des constantes dans les pages Les définitions sont dans des fichiers .properties fichier local = fr fichier par défaut pas définit = venant du fichier par défaut La déclaration dans la page <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@taglib uri="http://www.springframework.org/tags" prefix="spring"%> <%@ page session="false" %> La taglib utilisée <html> <head> <title><spring:message code="page.home.title"/></title> </head> Le message <body> <h1> <spring:message code="page.home.hello"/> </h1> <P> <spring:message code="page.home.timeOnServer"/> ${serverTime}. </P> </body> </html> La configuration Dans le fichier de configuration Spring [servlet]-config.xml Spécifier que l’on veut utiliser les messages de properties <!-- Specify the source for i18n --> <beans:bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <beans:property name="basename" value="classpath:messages" /> <beans:property name="defaultEncoding" value="UTF-8" /> </beans:bean> Laisser l’utilisateur choisir sa langue Il faut ajouter un choix du local Il faut prendre en compte le changement voir tuto http://viralpatel.net/blogs/spring-3-mvcinternationalization-i18n-localization-tutorial-example/ Atelier Créer un nouveau projet Spring. Spring génère une page « home ». Internationaliser cette page. Documentation http://docs.spring.io/spring/docs/3.2.5.RELEASE/spri ng-framework-reference/htmlsingle/#validation 7.8.4 Spring MVC 3 Validation Tutorials http://www.codejava.net/frameworks/spring/sprin g-mvc-form-validation-example-with-beanvalidation-api Que valider ? Il faut valider pour se garantir : • De mauvaises saisies dans les formulaires • De données saisies non valables pour le métier Les mauvaises saisies peuvent être détectés par: la conversion de la requête http objet command Les contraintes métiers peuvent être détectés par: des objets de validation Validation Action de valider des données en fonction du métier ex: 0<= age <150 Plusieurs possibilités avec Spring les technique se sont empilées avec le temps Les plus récentes: Validation explicite @Valid JSR-303 Bean Validation API Spring 4 et la validation Deux méthodes (complémentaires) Erreurs de conversion Détecté esquand Spring convertit les données du formulaire dans un bean Bean Validation API a.k.a. JSR 303 for Bean Validation 1.0 and JSR 349 for Bean Validation 1.1 Fonctionne par annotations Actionnée à la demande (@Valid) Validator Objects Déclare et utilise des objets de validations Appel explicite du validator Spring 3.x Méthode 1 Erreurs de conversion @RequestMapping(method = RequestMethod.POST) protected String onSubmit( @ModelAttribute("commandAjout") CommandAjout commandAjout, BindingResult result, SessionStatus status ) throws Exception { if( result.hasErrors()) { return "formulaire"; } retourne au formulaire en cas d’erreurs groupe.addMembre(commandAjout.getNouveauMembre()); status.setComplete(); return "confirmation"; } efface la session si ok @ModelAttribute permet de récupérer l’objet command. Il est peuplé à partir de la requete, donc avec les valeurs saisies dans le formulaire. Il y a conversion implicite String -> type dans l’objet commande Il peut y avoir plusieurs @ModelAttribute BindingResult result contient les éventuelles erreurs de conversion doit être placé immédiatement après le @ModelAttribute auquel il se réfere Méthode 2 Bean Validation Principe: annote le bean Spécifie les messages d’erreurs import javax.validation.constraints.Size; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; public class User { @NotEmpty @Email private String email; @NotEmpty(message = "Please enter your password.") @Size(min = 6, max = 15, message = "Your password must between 6 and 15 characters") private String password; . . . // getters + setters } Validation explicite lors de la conversion @Controller public class LoginController { @RequestMapping(value = "/login", method = RequestMethod.GET) public String viewLogin(Map<String, Object> model) { User user = new User(); model.put("userForm", user); return "LoginForm"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String doLogin(@Valid @ModelAttribute("userForm") User userForm, BindingResult result, Map<String, Object> model) { } } if (result.hasErrors()) { return "LoginForm"; } return "LoginSuccess"; Affichage des erreurs <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> ... <form:form action="login" commandName="userForm"> <tr> <td align="left" width="20%">Email: </td> <td align="left" width="40%"><form:input path="email" size="30"/></td> <td align="left"><form:errors path="email" cssClass="error"/></td> </tr> <tr> <td>Password: </td> <td><form:password path="password" size="30"/></td> <td><form:errors path="password" cssClass="error"/></td> </tr> <tr> <td></td> <td align="center"><input type="submit" value="Login"/></td> <td></td> </tr> </form:form> Librairie requises validation-api-1.1.0.Final.jar hibernate-validator-5.0.1.Final.jar Maven : <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.0.1.Final</version> </dependency> Méthode 3 Objet Validator public class ContactValidator implements Validator { Return vrai pour chaque type pris en charge ( pour @Valid ) /* * This Validator validates *just Contact instances */ public boolean supports(Class clazz) { (attributeName, errorCode, defaultMsg) return Contact.class.equals(clazz); } errorCode i18n } public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmptyOrWhitespace(e, "firstname", "firstname.empty", "First Name is required"); ValidationUtils.rejectIfEmptyOrWhitespace(e, "lastname", "lastname.empty", "Last Name is required"); Contact p = (Contact) obj; if (p.getAge() < 0) { e.rejectValue("age", "negativevalue", "Age should be >0"); } else if (p.getAge() > 110) { e.rejectValue("age", "too.darn.old", "Age seem too old"); } } Validation explicite avec Validator /** Declare a validator object */ Validator contactValidator = new ContactValidator(); Nécessite un objet validator Déclare un validator @RequestMapping(value = "/addContact2.html", method = RequestMethod.POST) public String addContact2(@ModelAttribute("command") Contact contact, BindingResult result, Model model) { appel la validation, Utilise le BindResult contactValidator.validate(contact, result); // Check the binding results. Binding and validations errors are contained // in the BindingResult object. // If there is some binding or validation errors, stop and return // to the form. if( result.hasErrors()) { Verifie le résultat System.err.println("errors encountered !!"); return "contact"; } … } Affichage des erreurs Même méthode que pécédemment Appel automatique des validators à la conversion Annotation JSR-303 nécessite validation-api.jar Peut être utilisé avec Spring MVC nécessite de déclarer les validators (dans le controller par exemple) /** * Register a validator that will be lookup when a parameter is binded to a handler * argument (with @ModelAttribute() for example). * @param binder */ @InitBinder protected void initBinder(WebDataBinder binder) { // register the ContactValidator used to validate objects of type Contact. binder.setValidator(new ContactValidator() ); } Appel automatique des validators Utilisation (@Valid) /** Handler called when theform is submitted. * The @Valid annotation is used to validate the input model. Spring lookup for a * validator accepting the class. */ @RequestMapping(value = "/addContact.html", method = RequestMethod.POST) public String addContact(@Valid @ModelAttribute("command") Contact contact, BindingResult result, Model model) { … } // Check the binding results. Binding and validations errors are contained // in the BindingResult object. // If there is some binding or validation errors, stop and return // to the form. if( result.hasErrors()) { System.err.println("errors encountered !!"); return "contact"; } Un validator pour ce type doit être accessible ! Affichage des erreurs dans le formulaire Documentation 18.2 JSP & JSTL http://docs.spring.io/spring/docs/4.0.0.BUILDSNAPSHOT/spring-frameworkreference/htmlsingle/#view-jsp Comment afficher les erreurs dans la page ? Spring renvoie l’objet Errors dans la réponse Cet objet contient toutes les erreurs Chaque erreur est identifiées par un id le nom de la propriété en général Le tag <form:errors …> utilise cet objet pour afficher des messages On indique l’id de l’erreur à vérifier le nom de la propriété en général … L’objet Error est remplie par le Validator (ou @Valid) public class ContactValidator implements Validator { /* * This Validator validates *just Contact instances */ public boolean supports(Class clazz) { (attributeName, errorCode, defaultMsg) return Contact.class.equals(clazz); } errorCode i18n } public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmptyOrWhitespace(e, "firstname", "firstname.empty", "First Name is required"); ValidationUtils.rejectIfEmptyOrWhitespace(e, "lastname", "lastname.empty", "Last Name is required"); Contact p = (Contact) obj; if (p.getAge() < 0) { e.rejectValue("age", "negativevalue", "Age should be >0"); } else if (p.getAge() > 110) { e.rejectValue("age", "too.darn.old", "Age seem too old"); } } Le <form:form …> déclare où doivent être placé les messages d’erreurs <%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <head> <title>Spring 3 MVC Series - Contact Manager</title> </head> <body> <h2>Contact Manager</h2> <form:form method="post" action="addContact.html"> … Affiché uniquement si <table> <tr> <td><form:label path="firstname">First Name</form:label></td> <td><form:input path="firstname" /></td> <%-- Show errors for firstname field --%> <td><form:errors path="firstname" /></td> </tr> <tr> <td><form:label path="lastname">Last Name</form:label></td> <td><form:input path="lastname" /></td> <%-- Show errors for lastname field --%> <td><form:errors path="lastname" /></td> </tr> l’erreur existe ! Atelier ateliers 2 (i18n, valid).pdf Formulaire et Validation