Spring MVC 2.5+ par l’exemple – Partie I

L'objet de ce billet est d'illustrer progressivement, pas à pas, par un exemple, simple mais pratique, la création d'une application web avec Spring MVC utilisant exclusivement (et abusivement) les annotations de Spring apparues depuis la version 2.5 ( ex. @Controller, @Autowired).
Ce billet s'inscrit dans une série d'articles nous permettant d'introduire Spring WebFlow 2.
Dans cette première partie de la série, nous mettons en place ensemble, pas à pas, l'environnement (Eclipse & Maven) et les dépendances nécessaires. Et nous terminons cette partie par écrire une classe JUnit pour tester le controller Spring MVC (seulement avec les annotations) afin de valider notre configuration et nous lançons le client navigateur afin de tester le déploiement sous jetty.
Nous enchaînons, dans un second billet, par expliciter les couches DAO (à base de jdbc template), service et l'approfondissement du controller Spring MVC qui consomme les méthodes de la couche service.
Pour ce faire, nous présentons progressivement l'exemple qui consiste à rechercher un client par son nom (obligatoire), le prénom(facultatif). Si un ou plusieurs clients existent, on affiche la liste puis lorsqu'un client est sélectionné, on affiche l'écran qui permet d'actualiser ses détails.
Si aucun client ne correspond à la recherche, on affiche un formulaire, pré-rempli, de création de ce client puis à la validation on lui renvoie l'écran de modification des détails.
Cet exemple nous servira pour introduire Spring WebFlow.
Vous pouvez télécharger les sources de la première partie springswf-partie1.

VUE GLOBALE:

Voici les parties qui seront développées dans cette série d'articles. Le premier de cette série consiste à mettre en place et à valider l'environnement et la configuration Spring MVC en partant d'un exemple qui détaille toutes les étapes.

MISE EN PLACE DE L'ENVIRONNEMENT:

- Création du projet Eclipse et Maven 2,
- Gestion des dépendances maven : pom.xml,
- Configuration minimale du fichier web.xml,
- Configuration xml du fichier Spring MVC
- Création d'un controller SpringMVC,
- Création d'une classe JUnit4 de test du Controller,
- Validation de la configuration de Spring MVC.

CREATION, PAS A PAS, DE L'EXEMPLE SPRING MVC COMPLET:

- Configuration Spring des beans des deux couches DAO et service,
- Création des beans DAO,
- Création des beans service,
- Compléter le controller Spring MVC.

INTRODUCTION DE SPRING WEBFLOW 2.
- Intégrer Spring webflow avec Spring MVC (Environnement et config),
- Les différents fichiers de configuration,
- Tests unitaires.

CONCLUSION

Voilà maintenant nous pouvons commencer par la première partie qui prépare toute la suite.

PREMIERE PARTIE

MISE EN PLACE DE L'ENVIRONNEMENT:

1- Création du projet Maven:
Nous allons créer ensemble, étape par étape, un projet sous Eclipse et Maven2 (sous windows). Avec la console Dos, se positionner dans le répertoire workspace d'Eclipse puis taper la commande:

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=com.netapsys.springswf  -DartifactId=springswf

Ce qui génère la structure du projet maven dans le répertoire springswf.
S'assurer enfin que le contenu du fichier généré pom.xml pointe sur la version java1.5 ou plus : Pour cela, vérifier dans le pom.xml les lignes suivantes:

<artifactid>maven-compiler-plugin</artifactid>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>

Il est important d'ajouter (si ce n'est pas fait) les lignes ci-après, entre les tags <build>, afin de configurer les plugins utiles (exemple celui de jetty). Vous pouvez retrouver le pom.xml dans le zip à télécharger.

<finalName>springswf</finalName>
		<defaultGoal>jetty:run</defaultGoal>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
					<verbose>true</verbose>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<version>${jetty-version}</version>
				<configuration>
					<scanIntervalSeconds>30</scanIntervalSeconds>
				</configuration>
			</plugin>

Il reste encore à faire de ce projet maven un projet Eclipse à l'aide de la commande:

mvn eclipse:eclipse

Pour finir, importer ce projet dans Eclipse.

2-Configuration des dépendances Maven pour SpringMVC:

Nous ajoutons au fichier pom.xml les dépendances nécessaires à SpingMVC en insérant entre les deux tags:

<dependencies> ......<dependencies/>

les lignes suivantes:

<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring</artifactid>
<version>2.5.6</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-web</artifactid>
<version>2.5.6</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-webmvc</artifactid>
<version>2.5.6</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-test</artifactid>
<version>2.5.6</version>
</dependency>

Nous ajoutons les dépendances pour JUnit et log4j en insérant également:

<dependency>
<groupid>junit</groupid>
<artifactid>junit</artifactid>
<version>4.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupid>commons-logging</groupid>
<artifactid>commons-logging</artifactid>
<version>1.1</version>
</dependency>
<dependency>
<groupid>log4j</groupid>
<artifactid>log4j</artifactid>
<version>1.2.14</version>
</dependency>

Au fur et à mesure que nous avançons dans le projet, nous mettons à jour le pom.xml en justifiant autant que possible.

3-Configuration du web.xml:

Notre projet exemple étant conforme JEE nous commençons donc par configurer (strict minimum) le fichier web.xml. Celui-ci contient :
La déclaration de la servlet frontale de Spring nommée DispatcherServlet,
Et enfin, il indique que toutes les requêtes .html seront interceptées par ce DispatcherServlet.
A noter que le nom <servlet-name> est sprinswf et que le fichier de configuration de Spring MVC se nomme springswf-servlet.xml ( par convention, ajout du suffix -servlet au nom de la servlet). Faisant ainsi, Spring recherche automatiquement ce fichier, par conséquent, ceci allège la configuration xml.

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app 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" version="2.4">
  <display-name>Spring MVC et Spring WebFlow </display-name>
  <servlet>
		<servlet-name>springswf</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
  </servlet>
	<!--Toutes les requêtes se terminant par .html seront servies par la servlet frontale:DispatcherServlet -->
  <servlet-mapping>
		<servlet-name>springswf</servlet-name>
		<url-pattern>*.html</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
	<welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

4- Configuration de Spring MVC:

Le fichier springswf-servlet.xml, dont le contenu (strict nécessaire) est donné ci-après, comporte en commentaires toutes les explications nécessaires. Il serait utile de noter la version 2.5 des .xsd dans le tag <beans>.
Essentiellement dans ce fichier de configuration, nous indiquons les packages à scanner par Spring à la recherche des beans annotés.
Aussi, la déclaration concise <context:annottion-config/> permet d'activer les annotations, en particulier @Autowired, @Reporsitory, qui vont être utilisées constamment dans cet exemple.
Enfin, nous précisons les règles qui déterminent la vue à rendre au client.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
 
	<!-- - Tous les controllers sont automatiquement détectés grâce à l'annotation @Controller.
		- On définit ici dans quel package le post processor doit chercher ces beans annotés.	-->
  	<context:component-scan base-package="com.netapsys.fr.springswf.web"/>
 
	<!-- Activates various annotations to be detected in bean classes: Spring's
		@Required and @Autowired, as well as JSR 250's @PostConstruct,@PreDestroy and 
		@Resource (if available) and JPA's @PersistenceContext & @PersistenceUnit.
	-->
	<context:annotation-config/>
	<!-- Ceci est le view resolver, il permet de définir la technologie de vue utilisée et comment
		sélectionner une vue. Ici on prendra la solution la plus simple elle permet de mapper 
		le nom de la vue retournée avec la sélection d'une jsp. Ex. si le nom de la vue retournée est "hello" alors on utilisera le fichier
		WEB-INF/jsp/hello.jsp pour construire la vue. 
	-->
	<bean id="internalViewResolver"
	 class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
	 p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>
</beans>

Les fichiers de config spring pour les tests sont dans le répertoire src/test/java/config. Ainsi, on peut spécifier des valeurs (exemple, la datasource cf. seconde partie) qui sont réservées uniquement aux tests.
Dans notre exemple, les contenus des fichiers springswf-servlet-test.xml et springswf-servlet.xml sont identiques.

NOTES:
a)Le même fichier log4j.xml est placé à deux endroits src/main/resources et dans WEB-INF/ afin d'être vu par les classes JUnit et par la webapp.
b)Les classes de tests JUnit sont exécutables à la fois sous maven et sous eclipse. N'hésitez pas à updater le classpath d'eclipse via la commande:

mvn eclipse:clean eclipse:eclipse

Chaque fois que des dépendances du pom.xml sont actualisées. Puis de faire un "refresh" voire un clean du projet eclipse.

5- Création du controller de Spring MVC
La servlet Dispatcher de Spring MVC intercepte les requêtes .html et les renvoie à la classe annotée avec le stéréotype @Controller.
Ci-après le code de la classe, nommée ClientController.java. C'est ce controller qui sera complété, dans la prochaine partie, afin de répondre à toutes les requêtes CRUD (create, Read, Update et Delete) provenant du navigateur client.
Ici le controller comporte uniquement une seule méthode qui répondra à la requête HTTP GET /getInfos.html.
Des explications supplémentaires sont fournies dans le code.

package com.netapsys.fr.springswf.web;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller("clientController")
public class ClientController {
    final Logger logger=Logger.getLogger(getClass().getName());
    /**
     * Handler de la méthode Get pour l'URL /getInfos.html.  
     * @param model une map des données qui sont utilisables dans la vue
     * @return <code>null</code> donc la vue choisie par défaut,càd ici /WEB-INF/jsp/getInfos.jsp.
     **/
    @RequestMapping(value="/getInfos.html",method = RequestMethod.GET)
    public String getInfos(ModelMap model){
        logger.info("method getInfos called in the controller");
        model.addAttribute("infos",new String("\tL'environnement Spring MVC est OK"));
        return null;
    }
}

La méthode getInfos mappe la requête http://localhost:8080/springswf/getInfos.html de type GET comme l'indique les paramètres de l'annotation @RequestMapping et place un attribut dans la session via ModelMap.
Elle retourne ensuite la valeur nulle qui siginife par défaut qu'on réutilise la même vue. Ici, la vue sera /WEB-INF/jsp/getInfos.jsp conformément à la ViewResolver du fichier de configuration de Spring MVC.


6-Création d'une classe JUnit4 de test du controller

Le controller de Spring MVC est un simple POJO. Il n'hérite d'aucune classe. Par conséquent, il est facilement testable. La classe JUnit qui suit le prouve.
L'annotation @RunWith de JUnit4 permet de choisir le lanceur souhaité. Nous avons pris SpringJUnit4ClassRunner.class ce qui permet d'activer l'auto-injection avec peu d'effort. C'est dans les tests que l'autowiring de Spring est d'un apport confortable.
Je ne peux m'imaginer faire des classes Junit de tests avec Spring sans recourir à l'autowiring.

package com.netapsys.tests.springswf.web.tests;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.ui.ModelMap;
import com.netapsys.fr.springswf.web.ClientController;
@ContextConfiguration(locations={"classpath:/config/springswf-servlet-test.xml"})
@RunWith( SpringJUnit4ClassRunner.class) /* activate autowiring injection */
public class ClientControllerTest  {
	final static Logger logger =Logger.getLogger(ClientControllerTest.class.getClass().getName());
	private static ModelMap model;
	protected  ClientController clientController;	
	@Autowired
	public void setClientController(ClientController clientControllerSpring) {
		this.clientController = clientControllerSpring;
	}
	@BeforeClass() 	public static void testAvantTout(){	model = new ModelMap();	}
	@AfterClass() public static void apresTousLesTests(){	model.clear();	}	
	@Before public void initAvant(){
		Assert.assertNotNull(clientController);		/** test que auto-injection Spring est ok*/
	}	
	@After public  void testApres(){}
 
	/**	TEST */
	@Test public void testGetInfos() {	
	    clientController.getInfos( model);
		logger.info( model.get("infos") );
		Assert.assertNotNull(model.get("infos"));
	}
}

7-Validation de l'environnement et de la configuration
Enfin, nous allons nous assurer ensemble que notre environnement et notre configuration sont valides en lançant successivement les commandes:

mvn test
/// 
Ensuite
///[dos]
mvn jetty:run

Puis lancer le navigateur (Firefox) avec l'adresse .

Vous devrez obtenir, si rien n'est oublié, le message indiquant que l'environnement Spring MVC est OK.
Enfin, nous donnons les deux fichiers jsp qui ont servi :

[jsp]
<html>
<!-- index.jsp  -->
<head>
<title>Spring mvc sample</title>
</head>
<body>
<% 
   final String URL="getInfos.html?";      
%>
Cliquer <a href="<%=URL%>">Tester l'environnement Spring MVC</a>
</body>
</html>
[jsp]
<!-- /WEB-INF/jsp/getInfos.jsp -->
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"  pageEncoding="ISO-8859-1"%>
<%@ page isELIgnored ="false" %>
 <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Spring MVC sample</title>
</head>
<body>
<br/><b>Infos:</b><br/>
<% 
	final String path=request.getContextPath();
  	final String infos=(String)request.getAttribute("infos");
 %>
<h1>
<% if( infos!=null && ! "".equals(infos) ) { %>
 <%=infos%>
 <% }else{%>
 <%="Votre environnement ne semble pas être OK" %>
 <%}%>
</h1>
</body>
</html>

Pour conclure, nous avons désormais l'environnement configuré pour aborder la seconde partie qui approfondira notre compréhension de Spring MVC.
Rappelons que la troisième partie permettra d'introduire Spring WebFlow2.

9 commentaires

  1. Bonjour !

    Merci pour cet article et en particulier la mention à Groovy. Cependant, il y a une « petite » erreur. Je m’appelle bien « Cédric » et non « Guillaume ». Certes, vous n’êtes pas bien loin 😉

  2. I’ve truly study several great things the following. Surely importance social bookmarking pertaining to revisiting. I ponder the best way a lot try you placed to develop these kinds of outstanding insightful web page.

  3. Je me permet de commenter ce vieux post pour dire qu’il y a bien une interface pour soumettre des jobs de test.

    En faite vous l’avez dans le lien avec lequel vous envoyez des requetes POST.

    ex : http://localhost/testswarm/addjob

    Je l’ai trouvé complètement par hasard alors que je voulais intégrer une page qui permettait justement de soumettre des jobs de tests ^^, et au lieu de tomber sur la mienne, je suis tomber la dessus. 🙂

    J’espere que cela pourra en aider plus d’un !

    Pour information j’utilise testswarm sur un serveur externe.

  4. Somebody essentially help to make severely articles I’d state. That is the first time I frequented your website page and thus far? I amazed with the analysis you made to create this actual publish incredible. Fantastic task!

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.