Spring MVC Cedric Dumoulin
Plan Bibliographie Injecter des composants Spring et JEE Header Footer et Body Internationalization Validation Gestion des erreurs
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_framework.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-30-Tutorial-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/
Injection d’objet
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") Déclare un composant /** * A simple bean that will be injected elsewhere */ @Component public class InjectedBean { private String firstname = "John"; private String lastname = "Doe"; //.. Getter an setter .. }
Spring injecte le bean du bon type Injecter un composant @Inject Peut se faire sur un attribut, un setter, … On peut spécifier le nom @Inject("injectedComponent") @Controller public class HomeController { /** * Try to inject a bean */ @Inject protected InjectedBean injectedBean; // .. } Spring injecte le bean du bon type
EJB integration
Webographie 22.2 Accessing EJBs http://docs.spring.io/spring/docs/4.0.0.BUILD-SNAPSHOT/spring-framework-reference/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" 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"> <jee:remote-slsb id="myComponent" jndi-name="java:global/ipint13.springetejb.ear/ipint13.springetejb.domain/MyServiceBean" business-interface="ipint13.springetejb.domain.MyService"/> </beans:beans> Dans le fichier de configuration la location de la def du namespace le nom spring l’interface du bean JEE le nom jndi est affiché par le serveur dans ses logs
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 methode 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>
Header, footer, body …
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 + partie commune Vous définissez le body Le framework ce charge de construire la page en assemblant les parties pour vous
Webographie 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/
Internationalisation i18n
Webographie http://viralpatel.net/blogs/spring-3-mvc-internationalization-i18n-localization-tutorial-example/
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 Pair 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" %> <html> <head> <title><spring:message code="page.home.title"/></title> </head> <body> <h1> <spring:message code="page.home.hello"/> </h1> <P> <spring:message code="page.home.timeOnServer"/> ${serverTime}. </P> </body> </html> La taglib utilisée Le message
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>
Atelier Créer un nouveau projet Spring. Spring génère une page « home ». Internationaliser cette page.
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-mvc-internationalization-i18n-localization-tutorial-example/
Validation
Documentation http://docs.spring.io/spring/docs/3.2.5.RELEASE/spring-framework-reference/htmlsingle/#validation 7.8.4 Spring MVC 3 Validation
Que valider ? Il faut valider pour se garantir : Spring 3.x 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
Erreur de conversion Spring 3.x @RequestMapping(method = RequestMethod.POST) protected String onSubmit( @ModelAttribute("commandAjout") CommandAjout commandAjout, BindingResult result, SessionStatus status ) throws Exception { if( result.hasErrors()) { return "formulaire"; } groupe.addMembre(commandAjout.getNouveauMembre()); status.setComplete(); return "confirmation"; retourne au formulaire en cas d’erreurs 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 plusieur s @ModelAttribute BindingResult result contient les éventuelles erreurs de conversion doit être placé immédiatement après le @ModelAttribute auquel il se réfere
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
appel la validation, Utilise le BindResult Validation explicite /** Declare a validator object */ Validator contactValidator = new ContactValidator(); @RequestMapping(value = "/addContact2.html", method = RequestMethod.POST) public String addContact2(@ModelAttribute("command") Contact contact, BindingResult result, Model model) { 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()) { System.err.println("errors encountered !!"); return "contact"; } … Déclare un validator Nécessite un objet validator appel la validation, Utilise le BindResult Verifie le résultat
(attributeName, errorCode, defaultMsg) Exemple de Validator public class ContactValidator implements Validator { /* * This Validator validates *just Contact instances */ public boolean supports(Class clazz) { return Contact.class.equals(clazz); } public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmptyOrWhitespace(e, "firstname", "firstname.empty", "First Name is required"); "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"); (attributeName, errorCode, defaultMsg) errorCode i18n
Spring et @Valid Préparation 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() ); }
Spring et @Valid Utilisation /** 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 !
JSR-303 Bean validation API Annotation JSR-303 nécessite validation-api.jar Nécessite une implémentation du standard ! Les contraintes de validation sont directement dans l’objet à valider Voir doc Spring
Gestion des erreurs
Documentation 18.2 JSP & JSTL http://docs.spring.io/spring/docs/4.0.0.BUILD-SNAPSHOT/spring-framework-reference/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 public class ContactValidator implements Validator { /* * This Validator validates *just Contact instances */ public boolean supports(Class clazz) { return Contact.class.equals(clazz); } public void validate(Object obj, Errors e) { ValidationUtils.rejectIfEmptyOrWhitespace(e, "firstname", "firstname.empty", "First Name is required"); "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"); (attributeName, errorCode, defaultMsg) errorCode i18n
Affiché uniquement si l’erreur existe ! 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"> <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> <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> … Affiché uniquement si l’erreur existe !