1. Introduction à Spring MVC
1.1. Présentation du framework Spring
- Spring est un framework Java open-source qui facilite le développement d'applications robustes et modulaires.
graph TD A[Spring MVC] --> B(Concepts de base) A --> C(Composants principaux) A --> D(Fonctionnement) B --> B1(Model-View-Controller) B1 --> B1a(Modèle : Données) B1 --> B1b(Vue : Présentation) B1 --> B1c(Contrôleur : Logique) B --> B2(Framework Java) B --> B3(Basé sur Spring) C --> C1(Contrôleurs) C1 --> C1a(@Controller) C1 --> C1b(@RequestMapping) C --> C2(Modèles) C2 --> C2a(Model) C2 --> C2b(ModelAndView) C --> C3(Vues) C3 --> C3a(JSP) C3 --> C3b(Thymeleaf) C --> C4(DispatcherServlet) C --> C5(Annotations) D --> D1(Requête HTTP) D1 --> D2(DispatcherServlet) D2 --> D3(Route vers Contrôleur) D3 --> D4(Traite la logique) D4 --> D5(Renvoie Modèle + Vue) D5 --> D6(Rendu final)
- Il repose sur deux concepts clés :
- L’Inversion de Contrôle (IoC)
- L’Injection de Dépendances (DI)
- Spring MVC est un module de Spring dédié aux applications web, basé sur le modèle MVC (Modèle-Vue-Contrôleur).
1. L'Inversion de Contrôle (IoC)
L'Inversion de Contrôle est un principe où le contrôle du flux d'exécution est inversé : un conteneur ou framework gère la création et la fourniture des objets au lieu que le code les appelle directement.
Exemple sans IoC
public class UserRepository {
public String getUserData() {
return "Données de l'utilisateur";
}
}
public class UserService {
private UserRepository repository;
public UserService() {
this.repository = new UserRepository();
}
public void displayUserData() {
System.out.println(repository.getUserData());
}
}
public class Main {
public static void main(String[] args) {
UserService service = new UserService();
service.displayUserData(); // Affiche : Données de l'utilisateur
}
}
Problème : UserService
crée lui-même UserRepository
, ce qui entraîne un couplage fort.
Avec IoC
public class UserRepository {
public String getUserData() {
return "Données de l'utilisateur";
}
}
public class UserService {
private UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public void displayUserData() {
System.out.println(repository.getUserData());
}
}
public class Container {
public static UserService createUserService() {
UserRepository repository = new UserRepository();
return new UserService(repository);
}
}
public class Main {
public static void main(String[] args) {
UserService service = Container.createUserService();
service.displayUserData(); // Affiche : Données de l'utilisateur
}
}
Avantage : Le conteneur gère la création des objets, réduisant le couplage.
2. L'Injection de Dépendances (DI)
L'Injection de Dépendances est une technique pour implémenter l'IoC en fournissant les dépendances d'une classe depuis l'extérieur.
a) Injection par le constructeur
public interface UserRepository {
String getUserData();
}
public class DatabaseUserRepository implements UserRepository {
@Override
public String getUserData() {
return "Données depuis la base de données";
}
}
public class UserService {
private UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public void displayUserData() {
System.out.println(repository.getUserData());
}
}
public class Main {
public static void main(String[] args) {
UserRepository repository = new DatabaseUserRepository();
UserService service = new UserService(repository);
service.displayUserData(); // Affiche : Données depuis la base de données
}
}
Explication : La dépendance est injectée via le constructeur.
b) Injection par setter
public class UserService {
private UserRepository repository;
public void setRepository(UserRepository repository) {
this.repository = repository;
}
public void displayUserData() {
System.out.println(repository.getUserData());
}
}
public class Main {
public static void main(String[] args) {
UserRepository repository = new DatabaseUserRepository();
UserService service = new UserService();
service.setRepository(repository);
service.displayUserData(); // Affiche : Données depuis la base de données
}
}
Explication : La dépendance est injectée via une méthode setter.
c) Avec Spring
L'annotation @Component
est utilisée dans Spring pour marquer une classe comme un bean géré. Elle appartient au package suivant :
import org.springframework.stereotype.Component;
Définition
Lorsqu'une classe est annotée avec @Component
, Spring l'ajoute automatiquement à son conteneur de beans :
import org.springframework.stereotype.Component;
@Component
public class MyService {
public void execute() {
System.out.println("Service exécuté !");
}
}
Détection Automatique des Composants
Spring utilise le Component Scanning pour détecter les classes annotées avec @Component
. Une configuration classique :
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}
Autres Annotations de Stéréotype
Spring propose d'autres annotations dérivées de @Component
:
Annotation | Description |
---|---|
@Component |
Annotation générique pour les composants Spring. |
@Service |
Indique un service métier. |
@Repository |
Spécialise un composant pour l'accès aux bases de données. |
@Controller |
Utilisé pour les contrôleurs Spring MVC. |
Injection de Dépendances
Une classe annotée avec @Component
peut être injectée dans une autre via @Autowired
:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyController {
private final MyService myService;
@Autowired
public MyController(MyService myService) {
this.myService = myService;
}
public void run() {
myService.execute();
}
}
Rôle de @Autowired
@Autowired
est utilisé pour injecter une dépendance dans une classe.
Ici, Spring injecte une instance de MyService
dans MyController
.
Processus de Fonctionnement
- Spring scanne les classes annotées avec
@Component
. - Spring détecte que
MyService
est un bean et le met dans le conteneur. - Quand
MyController
est instancié, Spring injecte automatiquementMyService
grâce à@Autowired
.
Différences entre @Component
et @Autowired
Annotation | Description |
---|---|
@Component |
Déclare une classe comme un bean Spring. |
@Autowired |
Injecte automatiquement une dépendance gérée par Spring. |
Pourquoi utiliser @Autowired
au lieu de new
?
- Couplage faible : Pas de dépendance directe avec une classe spécifique.
- Testabilité améliorée : Permet d'injecter des mocks lors des tests unitaires.
- Gestion automatique : Spring optimise l'utilisation des objets et leur cycle de vie.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
public interface UserRepository {
String getUserData();
}
@Component
public class DatabaseUserRepository implements UserRepository {
@Override
public String getUserData() {
return "Données depuis la base de données";
}
}
@Component
public class UserService {
private UserRepository repository;
@Autowired
public UserService(UserRepository repository) {
this.repository = repository;
}
public void displayUserData() {
System.out.println(repository.getUserData());
}
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext("com.example.package");
UserService service = context.getBean(UserService.class);
service.displayUserData(); // Affiche : Données depuis la base de données
context.close();
}
}
Explication : Spring gère automatiquement l'injection des dépendances.
Différence entre IoC et DI
@Autowired] C --> C3(Exemples dans Spring MVC) C3 --> C3a[Injection dans les contrôleurs] C3 --> C3b[Services injectés dans les beans] C --> C4(Annotations clés) C4 --> C4a[@Autowired] C4 --> C4b[@Qualifier] C4 --> C4c[@Component] B --> D(Lien entre IoC et DI) D --> D1[IoC est le concept global] D --> D2[DI est une implémentation de IoC]
- IoC : Principe général d'inversion du contrôle.
- DI : Mise en œuvre concrète de l'IoC via injection.
Avantages
- Faible couplage : Les classes ne dépendent pas d'implémentations spécifiques.
- Testabilité : Facilite l'utilisation de mocks.
- Flexibilité : Changement d'implémentation sans modifier le code principal.
1.2. Qu'est-ce que Spring MVC ?
- Spring MVC est une implémentation du modèle MVC pour créer des applications web.
- Le Modèle représente les données.
- La Vue affiche ces données à l’utilisateur.
- Le Contrôleur gère les interactions utilisateur.
- Le DispatcherServlet est au cœur de Spring MVC, il reçoit les requêtes HTTP et les redirige vers les contrôleurs appropriés.
Exemple de base :
// Un contrôleur simple
public class MonControleur {
public String direBonjour() {
return "Bonjour depuis Spring MVC !";
}
}
1.3. Différence entre Spring MVC et Spring Boot
- Spring MVC : Un framework manuel qui nécessite une configuration explicite, soit via des fichiers XML, soit par annotations.
- Spring Boot : Une surcouche à Spring MVC qui simplifie la configuration en automatisant les tâches, avec des conventions et des dépendances déjà préconfigurées.
- Objectif de ce cours : Nous utiliserons Spring MVC pur afin de mieux comprendre les mécanismes sous-jacents du framework et son fonctionnement détaillé.
Tableau comparatif :
Aspect | Spring MVC | Spring Boot |
---|---|---|
Configuration | Manuelle (XML/Java) | Automatique |
Dépendances | Définies manuellement | Gérées par starters |
Serveur | Externe (Tomcat) | Embarqué par défaut |
1.4. Architecture MVC (Modèle-Vue-Contrôleur)
- Modèle : Une classe Utilisateur pour stocker les données.
- Vue : Des pages JSP pour afficher les utilisateurs.
- Contrôleur : Une classe qui gère les opérations CRUD (Créer, Lire, Mettre à jour, Supprimer).
Exemple conceptuel :
// Modèle
public class Utilisateur {
private int id;
private String nom;
// Getters et setters
}
// Contrôleur
public class UtilisateurControleur {
public String listerUtilisateurs() {
return "listeUtilisateurs"; // Nom de la vue
}
}
Projet CRUD : Premiers pas
- Objectif : Créer une application web pour gérer une liste d’utilisateurs.
- Opérations principales :
- Create : Ajouter un utilisateur.
- Read : Afficher les informations des utilisateurs.
- Update : Modifier les informations des utilisateurs.
- Delete : Supprimer un utilisateur.
Structure initiale envisagée :
projet-crud/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.exemple.crud/
│ │ │ ├── model/
│ │ │ │ └── Utilisateur.java
│ │ │ ├── controller/
│ │ │ │ └── UtilisateurControleur.java
│ │ └── webapp/
│ │ ├── WEB-INF/
│ │ │ ├── web.xml
│ │ │ └── spring-config.xml
│ │ └── views/
│ │ └── listeUtilisateurs.jsp
├── pom.xml
2. Configuration de l’environnement
2.1. Prérequis (Java, Maven, Servlet Container comme Tomcat)
- Java : JDK 8 ou supérieur (nous utiliserons Java 17 ici pour modernité).
- Maven : Outil de gestion de dépendances et de construction.
- Servlet Container : Tomcat (exemple : version 9) pour exécuter l’application web.
Étapes :
- Installez Java https://www.oracle.com/java/technologies/downloads/(java -version pour vérifier).
- Installez Maven (installer eclipse ide for jee and web https://www.eclipse.org/downloads/packages/) (mvn -version pour vérifier).
- Téléchargez et configurez Apache Tomcat (ex. démarrer via startup.bat ou startup.sh).
2.2. Création d’un projet Spring MVC avec Maven
- Création du projet : Créer un projet Maven avec une structure standard pour une application web Spring MVC.
-
- Archetype: maven-archetype-webapp
- GroupId: com.example
- ArtifactId:projet-crud
- Fichier pom.xml : Le fichier pom.xml définit les dépendances nécessaires pour l’application.
Exemple - pom.xml :
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.exemple</groupId>
<artifactId>projet-crud</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<java.version>17</java.version>
<spring.version>6.0.0</spring.version>
</properties>
<dependencies>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Servlet API (fournie par Tomcat) -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<!-- JSP API -->
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>projet-crud</finalName>
<plugins>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
</project>
- <packaging>war</packaging> : Indique que le projet sera un fichier WAR déployable sur Tomcat.
- Dépendances :
- spring-webmvc : Module principal pour Spring MVC.
- jakarta.servlet-api et jsp-api : APIs pour les servlets et JSP, scope provided car fournies par Tomcat.
- Plugins Maven : Compilent le code en Java 17 et génèrent le WAR.
Commande Maven :
mvn clean package
Cela génère projet-crud.war dans le dossier target.
2.3. Configuration du fichier web.xml
- Fichier web.xml : Le fichier web.xml est le descripteur de déploiement pour les applications web Java.
- Configuration du DispatcherServlet : Il configure le DispatcherServlet, qui est le point d’entrée de Spring MVC.
Exemple - src/main/webapp/WEB-INF/web.xml :
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- Configuration du DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Mappage des URLs -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- Page d'accueil -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
- <servlet> : Définit le DispatcherServlet avec le nom dispatcher.
- <init-param> : Indique où trouver la configuration Spring (fichier spring-config.xml).
- <servlet-mapping> : Toutes les requêtes (/) sont redirigées vers le DispatcherServlet.
- <welcome-file> : Page par défaut affichée au démarrage.
2.4. Configuration du DispatcherServlet
- Configuration du DispatcherServlet : Le DispatcherServlet utilise un fichier de configuration Spring (XML ou Java).
- Approche classique : Dans ce cas, le fichier XML est utilisé pour rester dans une approche classique de Spring MVC.
Exemple - src/main/webapp/WEB-INF/spring-config.xml :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- Activer l'analyse des annotations -->
<context:component-scan base-package="com.exemple.crud"/>
<!-- Activer les fonctionnalités MVC -->
<mvc:annotation-driven/>
<!-- Configuration du View Resolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
- <context:component-scan> : Analyse le package com.exemple.crud pour trouver les contrôleurs, services, etc.
- <mvc:annotation-driven> : Active les annotations comme @Controller, @RequestMapping.
- InternalResourceViewResolver : Définit où trouver les vues (ex. /WEB-INF/views/listeUtilisateurs.jsp).
Projet CRUD : Configuration initiale
Structure actuelle :
projet-crud/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.exemple.crud/
│ │ └── webapp/
│ │ ├── WEB-INF/
│ │ │ ├── web.xml
│ │ │ └── spring-config.xml
│ │ └── index.jsp
├── pom.xml
Ajout d’une page d’accueil - src/main/webapp/index.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE >
<html>
<head>
<title>Bienvenue</title>
</head>
<body>
<h1>Bienvenue dans le projet CRUD avec Spring MVC !</h1>
<a href="utilisateurs">Voir la liste des utilisateurs</a>
</body>
</html>
Prochaines étapes :
Dans la section suivante (3. Les bases de Spring MVC), nous ajouterons un contrôleur pour gérer la requête /utilisateurs.
Déploiement :
- Exécutez mvn clean package.
- Déployez target/projet-crud.war dans le dossier webapps de Tomcat.
- Accédez à http://localhost:8080/projet-crud/ (si Tomcat est sur le port 8080).
3. Les bases de Spring MVC
3.1. Le rôle du DispatcherServlet
- DispatcherServlet : Le DispatcherServlet est le cœur de Spring MVC, servant de contrôleur frontal. Il reçoit toutes les requêtes HTTP, les analyse et les redirige vers les contrôleurs appropriés selon les mappages d’URL définis.
- Rôle de coordination : Il coordonne également le rendu des vues et la gestion des réponses, assurant le bon fonctionnement du flux de l'application.
Processus de gestion des requêtes :
- Une requête HTTP arrive, par exemple, /utilisateurs pour afficher la liste des utilisateurs.
- Le DispatcherServlet consulte les mappages définis dans les contrôleurs, recherchant une méthode associée à l’URL demandée.
- Une fois la méthode correspondante trouvée, il l'appelle et renvoie la vue appropriée pour afficher le résultat, comme une page JSP ou un modèle de données.
3.2. Définition d’un contrôleur avec @Controller
- Contrôleur : Un contrôleur dans Spring MVC est une classe annotée avec @Controller.
- Gestion des requêtes : Cette classe contient des méthodes qui gèrent les requêtes HTTP entrantes, en effectuant des actions comme la récupération de données ou la redirection vers une autre vue.
- Association d'URLs : Les méthodes du contrôleur sont associées à des URLs spécifiques via des annotations comme @RequestMapping ou des variantes comme @GetMapping, @PostMapping, etc.
Exemple - src/main/java/com/exemple/crud/controller/UtilisateurControleur.java :
package com.exemple.crud.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UtilisateurControleur {
@RequestMapping("/utilisateurs")
public String listerUtilisateurs() {
// Retourne le nom logique de la vue
return "listeUtilisateurs";
}
}
- @Controller : Indique que cette classe est un contrôleur Spring.
- @RequestMapping("/utilisateurs") : Associe la méthode à l’URL /utilisateurs.
- return "listeUtilisateurs" : Indique au ViewResolver de charger listeUtilisateurs.jsp.
Ajout de la vue - src/main/webapp/WEB-INF/views/listeUtilisateurs.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE >
<html>
<head>
<title>Liste des utilisateurs</title>
</head>
<body>
<h1>Liste des utilisateurs</h1>
<p>Pour l'instant, la liste est vide.</p>
<a href="/">Retour à l'accueil</a>
</body>
</html>
3.3. Mappage des requêtes avec @RequestMapping
- Annotation @RequestMapping : L'annotation @RequestMapping est utilisée pour associer des URLs et des méthodes HTTP (comme GET, POST, PUT, DELETE, etc.) à des méthodes dans un contrôleur Spring MVC.
- Utilisation au niveau de la classe : Elle peut être placée au niveau de la classe pour définir un préfixe d'URL commun à toutes les méthodes du contrôleur.
- Utilisation au niveau de la méthode : Elle peut aussi être utilisée sur une méthode pour associer une URL spécifique à cette méthode, en précisant le type de requête HTTP attendue (GET, POST, etc.).
Exemple - Ajout d’une méthode dans UtilisateurControleur :
package com.exemple.crud.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/utilisateurs") // Mappage au niveau classe
public class UtilisateurControleur {
@RequestMapping(method = RequestMethod.GET) // GET /utilisateurs
public String listerUtilisateurs() {
return "listeUtilisateurs";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.GET) // GET /utilisateurs/ajouter
public String afficherFormulaireAjout() {
return "ajouterUtilisateur";
}
}
- @RequestMapping("/utilisateurs") au niveau classe : Préfixe toutes les méthodes avec /utilisateurs.
- method = RequestMethod.GET : Spécifie que la méthode répond aux requêtes GET.
- /ajouter : Nouvelle URL pour afficher un formulaire d’ajout.
Ajout de la vue - src/main/webapp/WEB-INF/views/ajouterUtilisateur.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE >
<html>
<head>
<title>Ajouter un utilisateur</title>
</head>
<body>
<h1>Ajouter un utilisateur</h1>
<form method="post" action="/projet-crud/utilisateurs/ajouter">
<label>Nom : </label>
<input type="text" name="nom" />
<input type="submit" value="Ajouter" />
</form>
<a href="/projet-crud/utilisateurs">Retour à la liste</a>
</body>
</html>
3.4. Gestion des vues (View Resolver)
- ViewResolver : Le ViewResolver est responsable de la résolution des vues dans Spring MVC. Il traduit les noms logiques des vues (comme listeUtilisateurs) en chemins de fichiers physiques (par exemple, /WEB-INF/views/listeUtilisateurs.jsp).
- Configuration dans spring-config.xml : Le ViewResolver a été configuré dans le fichier spring-config.xml à l'aide de l'implémentation InternalResourceViewResolver.
- Fonctionnement : Lorsque Spring MVC doit afficher une vue, il demande au ViewResolver de résoudre le nom logique de la vue en un fichier physique (par exemple, convertir listeUtilisateurs en /WEB-INF/views/listeUtilisateurs.jsp).
Configuration existante (rappel) - spring-config.xml :
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
Explication :
- prefix : Chemin de base des fichiers JSP.
- suffix : Extension des fichiers.
- Résultat : listeUtilisateurs → /WEB-INF/views/listeUtilisateurs.jsp.
Projet CRUD : Progression
Structure actuelle :
projet-crud/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.exemple.crud/
│ │ │ └── controller/
│ │ │ └── UtilisateurControleur.java
│ │ └── webapp/
│ │ ├── WEB-INF/
│ │ │ ├── web.xml
│ │ │ ├── spring-config.xml
│ │ │ └── views/
│ │ │ ├── listeUtilisateurs.jsp
│ │ │ └── ajouterUtilisateur.jsp
│ │ └── index.jsp
├── pom.xml
État actuel :
- Page d’accueil (index.jsp) avec lien vers /utilisateurs.
- Contrôleur UtilisateurControleur gérant :
- GET /utilisateurs → Affiche la liste (vide pour l’instant).
- GET /utilisateurs/ajouter → Affiche un formulaire.
Prochaines étapes :
- Ajouter un modèle Utilisateur et gérer les données (section 4).
- Implémenter la logique CRUD complète.
Test :
- Recompilez avec mvn clean package.
- Déployez sur Tomcat.
- Accédez à http://localhost:8080/projet-crud/ et cliquez sur le lien pour voir la liste ou ajouter un utilisateur.
4. Gestion des requêtes HTTP
4.1. Méthodes HTTP : GET, POST, PUT, DELETE
Spring MVC prend en charge les principales méthodes HTTP pour implémenter un CRUD :- GET : Récupérer des données (Read).
- POST : Créer ou soumettre des données (Create).
- PUT : Mettre à jour des données (Update).
- DELETE : Supprimer des données (Delete).
- @RequestMapping : L'annotation @RequestMapping est utilisée pour associer des requêtes HTTP à des méthodes spécifiques dans un contrôleur.
- Attribut method : L'attribut method permet de spécifier le type de méthode HTTP (comme GET, POST, PUT, DELETE) à laquelle la méthode du contrôleur doit répondre.
- Exemple d'utilisation : Par exemple, @RequestMapping(method = RequestMethod.GET) permet de mapper une méthode à une requête GET spécifique.
4.2. Paramètres de requête avec @RequestParam
- @RequestParam : L'annotation @RequestParam est utilisée pour extraire les paramètres d'une requête HTTP.
- Paramètres de la requête : Elle permet de récupérer des paramètres passés dans l'URL, comme dans ?id=1, et de les lier directement aux méthodes du contrôleur.
- Exemple d'utilisation : Par exemple, une méthode de contrôleur peut être annotée comme suit : @RequestParam("id") pour récupérer la valeur du paramètre id de la requête.
Exemple - Ajout dans UtilisateurControleur :
package com.exemple.crud.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/utilisateurs")
public class UtilisateurControleur {
@RequestMapping(method = RequestMethod.GET)
public String listerUtilisateurs() {
return "listeUtilisateurs";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.GET)
public String afficherFormulaireAjout() {
return "ajouterUtilisateur";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.POST)
public String ajouterUtilisateur(@RequestParam("nom") String nom) {
// Logique temporaire : affiche le nom dans la console
System.out.println("Utilisateur ajouté : " + nom);
return "redirect:/utilisateurs"; // Redirige vers la liste
}
}
- @RequestParam("nom") : Récupère la valeur du champ nom envoyé par le formulaire POST.
- redirect:/utilisateurs : Redirige vers la liste après l’ajout.
4.3. Variables de chemin avec @PathVariable
- @PathVariable : L'annotation @PathVariable permet d'extraire des valeurs directement depuis l'URL de la requête.
- Exemple d'URL : Par exemple, dans l'URL /utilisateurs/1, le numéro 1 est extrait comme valeur pour l'ID.
- Utilité : Cette annotation est souvent utilisée pour des opérations spécifiques, comme la suppression ou la mise à jour d'un élément, où l'ID ou d'autres paramètres doivent être extraits de l'URL.
Exemple - Ajout dans UtilisateurControleur :
@RequestMapping(value = "/supprimer/{id}", method = RequestMethod.GET)
public String supprimerUtilisateur(@PathVariable("id") int id) {
// Logique temporaire : affiche l'ID dans la console
System.out.println("Utilisateur supprimé avec ID : " + id);
return "redirect:/utilisateurs";
}
- @PathVariable("id") : Récupère la valeur {id} dans l’URL /utilisateurs/supprimer/1.
- redirect:/utilisateurs : Retourne à la liste après suppression.
Mise à jour de listeUtilisateurs.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE >
<html>
<head>
<title>Liste des utilisateurs</title>
</head>
<body>
<h1>Liste des utilisateurs</h1>
<p>Pour l'instant, la liste est vide.</p>
<a href="/projet-crud/utilisateurs/ajouter">Ajouter un utilisateur</a>
<br/>
<a href="/projet-crud/utilisateurs/supprimer/1">Supprimer utilisateur 1 (exemple)</a>
<br/>
<a href="/projet-crud/">Retour à l'accueil</a>
</body>
</html>
4.4. Gestion des en-têtes HTTP
- @RequestHeader : L'annotation @RequestHeader permet de lire les en-têtes HTTP dans une requête.
- HttpServletResponse : On peut également utiliser HttpServletResponse pour définir les en-têtes HTTP dans la réponse.
- Exemple simple : Dans cet exemple, ces mécanismes seront utilisés pour illustrer la manipulation des en-têtes, bien que cela ne soit pas essentiel pour les opérations CRUD de base à ce stade.
Exemple - Ajout dans UtilisateurControleur :
import org.springframework.web.bind.annotation.RequestHeader;
@RequestMapping(value = "/info", method = RequestMethod.GET)
public String afficherInfo(@RequestHeader("User-Agent") String userAgent) {
System.out.println("Navigateur : " + userAgent);
return "listeUtilisateurs"; // Réutilise la vue existante
}
- @RequestHeader("User-Agent") : Récupère l’en-tête HTTP User-Agent envoyé par le navigateur.
Projet CRUD : Progression
Structure actuelle :
projet-crud/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/exemple/crud/
│ │ │ └── controller/
│ │ │ └── UtilisateurControleur.java
│ │ └── webapp/
│ │ ├── WEB-INF/
│ │ │ ├── web.xml
│ │ │ ├── spring-config.xml
│ │ │ └── views/
│ │ │ ├── listeUtilisateurs.jsp
│ │ │ └── ajouterUtilisateur.jsp
│ │ └── index.jsp
├── pom.xml
Modèle - src/main/java/com/exemple/crud/model/Utilisateur.java :
package com.exemple.crud.model;
public class Utilisateur {
private int id;
private String nom;
public Utilisateur() {}
public Utilisateur(int id, String nom) {
this.id = id;
this.nom = nom;
}
// Getters et setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
}
Mise à jour du contrôleur avec une liste temporaire :
import com.exemple.crud.model.Utilisateur;
import org.springframework.ui.Model;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("/utilisateurs")
public class UtilisateurControleur {
private List<Utilisateur> utilisateurs = new ArrayList<>();
@RequestMapping(method = RequestMethod.GET)
public String listerUtilisateurs(Model model) {
model.addAttribute("utilisateurs", utilisateurs);
return "listeUtilisateurs";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.GET)
public String afficherFormulaireAjout() {
return "ajouterUtilisateur";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.POST)
public String ajouterUtilisateur(@RequestParam("nom") String nom) {
int id = utilisateurs.size() + 1; // ID simple pour l'exemple
utilisateurs.add(new Utilisateur(id, nom));
return "redirect:/utilisateurs";
}
@RequestMapping(value = "/supprimer/{id}", method = RequestMethod.GET)
public String supprimerUtilisateur(@PathVariable("id") int id) {
utilisateurs.removeIf(u -> u.getId() == id);
return "redirect:/utilisateurs";
}
}
Mise à jour de listeUtilisateurs.jsp pour afficher la liste :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE >
<html>
<head>
<title>Liste des utilisateurs</title>
</head>
<body>
<h1>Liste des utilisateurs</h1>
<c:if test="${empty utilisateurs}">
<p>Aucun utilisateur pour l'instant.</p>
</c:if>
<c:forEach var="utilisateur" items="${utilisateurs}">
<p>ID: ${utilisateur.id} - Nom: ${utilisateur.nom}
<a href="/projet-crud/utilisateurs/supprimer/${utilisateur.id}">Supprimer</a></p>
</c:forEach>
<a href="/projet-crud/utilisateurs/ajouter">Ajouter un utilisateur</a>
<br/>
<a href="/projet-crud/">Retour à l'accueil</a>
</body>
</html>
Ajout de JSTL dans pom.xml :
- Définition : JSTL est une bibliothèque de balises standard pour JSP permettant d'écrire du code plus propre en remplaçant les scriptlets Java.
- Objectif : Simplifier le développement web dynamique avec des balises prédéfinies pour les boucles, conditions et manipulation des données.
- Principales catégories de balises :
- Core (c:) : Boucles (
<c:forEach>
), conditions (<c:if>
), affichage (<c:out>
). - Formatting (fmt:) : Formatage des dates, nombres et messages internationalisés.
- SQL (sql:) : Interaction avec les bases de données (moins utilisé aujourd’hui).
- XML (x:) : Traitement des documents XML.
- Functions (fn:) : Fonctions utilitaires pour manipuler les chaînes.
- Core (c:) : Boucles (
- Avantages : Réduction du code Java brut, meilleure maintenance et séparation de la logique métier et de la présentation.
- Exemple :
<c:forEach var="item" items="${maListe}">${item}</c:forEach>
. - Intégration avec Spring MVC : Utilisé dans les vues JSP pour afficher dynamiquement les données des contrôleurs.
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>3.0.1</version>
</dependency>
État actuel :
- Create : Formulaire POST pour ajouter un utilisateur.
- Read : Liste des utilisateurs affichée.
- Delete : Suppression via URL avec @PathVariable.
Prochaines étapes :
- Ajouter la mise à jour (Update) dans la section 5.
- Connecter une base de données dans la section 6.
Test :
- Recompilez avec mvn clean package.
- Déployez sur Tomcat.
- Ajoutez des utilisateurs et supprimez-les via les liens.
5. Modèles et vues
5.1. Utilisation de l’objet Model et ModelAndView
- Model : Dans Spring MVC, Model est utilisé pour transmettre des données du contrôleur vers la vue.
- ModelAndView : ModelAndView combine les données du modèle et le nom de la vue dans un seul objet, permettant ainsi une gestion plus souple des deux.
- Utilisation actuelle : Actuellement, Model est déjà utilisé dans le projet pour passer les données à la vue.
- Exploration de ModelAndView : Nous allons explorer l'utilisation de ModelAndView pour obtenir plus de flexibilité dans la gestion des vues et des données.
Exemple - Mise à jour dans UtilisateurControleur :
@RequestMapping(method = RequestMethod.GET)
public ModelAndView listerUtilisateurs() {
ModelAndView mav = new ModelAndView("listeUtilisateurs");
mav.addObject("utilisateurs", utilisateurs);
return mav;
}
- ModelAndView("listeUtilisateurs") : Définit la vue.
- addObject : Ajoute des données au modèle, accessible dans la JSP via ${utilisateurs}.
5.2. Intégration avec des moteurs de templates (JSP, Thymeleaf, Freemarker)
- Utilisation actuelle de JSP : Dans le projet, JSP est déjà utilisé comme moteur de templates pour le rendu des vues.
- Autres moteurs de templates supportés par Spring MVC : Spring MVC prend également en charge Thymeleaf et Freemarker pour le rendu des vues.
- Thymeleaf : Pour utiliser Thymeleaf, il est nécessaire d'ajouter une dépendance supplémentaire et de configurer un ViewResolver spécifique pour intégrer le moteur dans l'application.
Configuration JSP actuelle (rappel) - spring-config.xml :
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
5.3. Gestion des formulaires (Form Binding)
- Gestion des formulaires : Spring MVC permet de lier les données saisies dans un formulaire à un objet Java à l'aide du mécanisme de Form Binding.
- Fonctionnalité de mise à jour : Dans notre projet, une fonctionnalité de mise à jour (Update) sera ajoutée, permettant de pré-remplir un formulaire avec les données existantes d'un utilisateur pour faciliter les modifications.
Modèle - Utilisateur.java (inchangé pour l’instant) :
package com.exemple.crud.model;
public class Utilisateur {
private int id;
private String nom;
public Utilisateur() {}
public Utilisateur(int id, String nom) {
this.id = id;
this.nom = nom;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
}
Mise à jour du contrôleur - UtilisateurControleur.java :
package com.exemple.crud.controller;
import com.exemple.crud.model.Utilisateur;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("/utilisateurs")
public class UtilisateurControleur {
private List<Utilisateur> utilisateurs = new ArrayList<>();
@RequestMapping(method = RequestMethod.GET)
public String listerUtilisateurs(Model model) {
model.addAttribute("utilisateurs", utilisateurs);
return "listeUtilisateurs";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.GET)
public String afficherFormulaireAjout(Model model) {
model.addAttribute("utilisateur", new Utilisateur());
return "ajouterUtilisateur";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.POST)
public String ajouterUtilisateur(@ModelAttribute("utilisateur") Utilisateur utilisateur) {
int id = utilisateurs.size() + 1;
utilisateur.setId(id);
utilisateurs.add(utilisateur);
return "redirect:/utilisateurs";
}
@RequestMapping(value = "/supprimer/{id}", method = RequestMethod.GET)
public String supprimerUtilisateur(@PathVariable("id") int id) {
utilisateurs.removeIf(u -> u.getId() == id);
return "redirect:/utilisateurs";
}
@RequestMapping(value = "/modifier/{id}", method = RequestMethod.GET)
public String afficherFormulaireModification(@PathVariable("id") int id, Model model) {
Utilisateur utilisateur = utilisateurs.stream()
.filter(u -> u.getId() == id)
.findFirst()
.orElse(null);
model.addAttribute("utilisateur", utilisateur);
return "modifierUtilisateur";
}
@RequestMapping(value = "/modifier", method = RequestMethod.POST)
public String modifierUtilisateur(@ModelAttribute("utilisateur") Utilisateur utilisateur) {
utilisateurs.removeIf(u -> u.getId() == utilisateur.getId());
utilisateurs.add(utilisateur);
return "redirect:/utilisateurs";
}
}
- @ModelAttribute : Lie les données du formulaire à un objet Utilisateur.
- /modifier/{id} : Affiche le formulaire pré-rempli avec les données de l’utilisateur.
- /modifier (POST) : Met à jour la liste avec les nouvelles données.
Vue - src/main/webapp/WEB-INF/views/modifierUtilisateur.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!DOCTYPE >
<html>
<head>
<title>Modifier un utilisateur</title>
</head>
<body>
<h1>Modifier un utilisateur</h1>
<form:form method="post" action="/projet-crud/utilisateurs/modifier" modelAttribute="utilisateur">
<form:hidden path="id"/>
<label>Nom : </label>
<form:input path="nom"/>
<input type="submit" value="Modifier"/>
</form:form>
<a href="/projet-crud/utilisateurs">Retour à la liste</a>
</body>
</html>
Mise à jour de ajouterUtilisateur.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!DOCTYPE >
<html>
<head>
<title>Ajouter un utilisateur</title>
</head>
<body>
<h1>Ajouter un utilisateur</h1>
<form:form method="post" action="/projet-crud/utilisateurs/ajouter" modelAttribute="utilisateur">
<label>Nom : </label>
<form:input path="nom"/>
<input type="submit" value="Ajouter"/>
</form:form>
<a href="/projet-crud/utilisateurs">Retour à la liste</a>
</body>
</html>
Mise à jour de listeUtilisateurs.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE >
<html>
<head>
<title>Liste des utilisateurs</title>
</head>
<body>
<h1>Liste des utilisateurs</h1>
<c:if test="${empty utilisateurs}">
<p>Aucun utilisateur pour l'instant.</p>
</c:if>
<c:forEach var="utilisateur" items="${utilisateurs}">
<p>ID: ${utilisateur.id} - Nom: ${utilisateur.nom}
<a href="/projet-crud/utilisateurs/modifier/${utilisateur.id}">Modifier</a>
<a href="/projet-crud/utilisateurs/supprimer/${utilisateur.id}">Supprimer</a></p>
</c:forEach>
<a href="/projet-crud/utilisateurs/ajouter">Ajouter un utilisateur</a>
<br/>
<a href="/projet-crud/">Retour à l'accueil</a>
</body>
</html>
5.4. Validation des données avec Hibernate Validator
- Validation du nom : Une validation sera ajoutée pour garantir que le nom de l'utilisateur n'est pas vide et qu'il respecte une longueur minimale. Cette étape est essentielle pour assurer la qualité des données dans l'application.
Ajout de la dépendance dans pom.xml :
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>5.0.1</version>
</dependency>
Mise à jour de Utilisateur.java :
package com.exemple.crud.model;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class Utilisateur {
private int id;
@NotBlank(message = "Le nom ne peut pas être vide")
@Size(min = 2, message = "Le nom doit avoir au moins 2 caractères")
private String nom;
public Utilisateur() {}
public Utilisateur(int id, String nom) {
this.id = id;
this.nom = nom;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
}
Mise à jour du contrôleur :
@RequestMapping(value = "/ajouter", method = RequestMethod.POST)
public String ajouterUtilisateur(@Valid @ModelAttribute("utilisateur") Utilisateur utilisateur, BindingResult result) {
if (result.hasErrors()) {
return "ajouterUtilisateur";
}
int id = utilisateurs.size() + 1;
utilisateur.setId(id);
utilisateurs.add(utilisateur);
return "redirect:/utilisateurs";
}
@RequestMapping(value = "/modifier", method = RequestMethod.POST)
public String modifierUtilisateur(@Valid @ModelAttribute("utilisateur") Utilisateur utilisateur, BindingResult result) {
if (result.hasErrors()) {
return "modifierUtilisateur";
}
utilisateurs.removeIf(u -> u.getId() == utilisateur.getId());
utilisateurs.add(utilisateur);
return "redirect:/utilisateurs";
}
Ajout de @Valid et BindingResult dans les imports :
import jakarta.validation.Valid;
import org.springframework.validation.BindingResult;
Mise à jour de ajouterUtilisateur.jsp pour afficher les erreurs :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!DOCTYPE >
<html>
<head>
<title>Ajouter un utilisateur</title>
</head>
<body>
<h1>Ajouter un utilisateur</h1>
<form:form method="post" action="/projet-crud/utilisateurs/ajouter" modelAttribute="utilisateur">
<label>Nom : </label>
<form:input path="nom"/>
<form:errors path="nom" cssClass="error"/>
<input type="submit" value="Ajouter"/>
</form:form>
<a href="/projet-crud/utilisateurs">Retour à la liste</a>
</body>
<style>.error { color: red; }</style>
</html>
Mise à jour de modifierUtilisateur.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<!DOCTYPE >
<html>
<head>
<title>Modifier un utilisateur</title>
</head>
<body>
<h1>Modifier un utilisateur</h1>
<form:form method="post" action="/projet-crud/utilisateurs/modifier" modelAttribute="utilisateur">
<form:hidden path="id"/>
<label>Nom : </label>
<form:input path="nom"/>
<form:errors path="nom" cssClass="error"/>
<input type="submit" value="Modifier"/>
</form:form>
<a href="/projet-crud/utilisateurs">Retour à la liste</a>
</body>
<style>.error { color: red; }</style>
</html>
Projet CRUD : Progression
État actuel :
- Create : Ajout avec validation.
- Read : Liste des utilisateurs.
- Update : Modification avec formulaire pré-rempli et validation.
- Delete : Suppression via URL.
Prochaines étapes :
- Connecter une base de données (section 6) pour remplacer la liste temporaire.
Test :
- Recompilez avec mvn clean package.
- Déployez sur Tomcat.
- Essayez d’ajouter ou de modifier un utilisateur avec un nom vide ou trop court pour voir les erreurs.
6. Gestion des données
6.1. Injection de dépendances avec Spring IoC
- Inversion de Contrôle (IoC) : Spring utilise le principe d'IoC pour gérer les dépendances à travers un conteneur. Ce mécanisme permet une gestion plus flexible des composants et des services au sein de l'application.
- Injection de service : Au lieu de manipuler directement une liste de données dans le contrôleur, un service sera injecté pour gérer ces opérations, ce qui permet de séparer la logique métier et de maintenir un code plus propre et modulable.
6.2. Connexion à une base de données (JDBC, Hibernate)
Nous utiliserons Hibernate (JPA) pour connecter notre application à une base de données H2. Cela nécessite :- Une entité persistante (Utilisateur).
- Un repository JPA.
- Une configuration dans spring-config.xml.
Dépendances dans pom.xml :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.exemple</groupId>
<artifactId>projectcruds</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>projectcruds Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<java.version>17</java.version>
<spring.version>6.0.0</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Servlet API (fournie par Tomcat) -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<!-- JSP API -->
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Hibernate et JPA -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.1.7.Final</version>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<finalName>projectcruds</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
</project>
6.3. Couche Service et DAO dans Spring MVC
Nous allons structurer le projet en couches :- Entité : Utilisateur mappé à une table.
- Repository : Interface JPA pour les opérations CRUD.
- Service : Logique métier.
- Contrôleur : Utilise le service.
Entité - src/main/java/com/exemple/crud/model/Utilisateur.java :
package com.exemple.crud.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Entity
public class Utilisateur {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@NotBlank(message = "Le nom ne peut pas être vide")
@Size(min = 2, message = "Le nom doit avoir au moins 2 caractères")
private String nom;
public Utilisateur() {}
public Utilisateur(int id, String nom) {
this.id = id;
this.nom = nom;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
}
Repository - src/main/java/com/exemple/crud/repository/UtilisateurRepository.java :
package com.exemple.crud.repository;
import com.exemple.crud.model.Utilisateur;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UtilisateurRepository extends JpaRepository<Utilisateur, Integer> {
}
Service - src/main/java/com/exemple/crud/service/UtilisateurService.java :
package com.exemple.crud.service;
import com.exemple.crud.model.Utilisateur;
import com.exemple.crud.repository.UtilisateurRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UtilisateurService {
@Autowired
private UtilisateurRepository utilisateurRepository;
public List<Utilisateur> listerTous() {
return utilisateurRepository.findAll();
}
public Utilisateur trouverParId(int id) {
Optional<Utilisateur> utilisateur = utilisateurRepository.findById(id);
return utilisateur.orElse(null);
}
public void ajouter(Utilisateur utilisateur) {
utilisateurRepository.save(utilisateur);
}
public void supprimer(int id) {
utilisateurRepository.deleteById(id);
}
}
Mise à jour du contrôleur - UtilisateurControleur.java :
package com.exemple.crud.controller;
import com.exemple.crud.model.Utilisateur;
import com.exemple.crud.service.UtilisateurService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import jakarta.validation.Valid;
@Controller
@RequestMapping("/utilisateurs")
public class UtilisateurControleur {
@Autowired
private UtilisateurService utilisateurService;
@RequestMapping(method = RequestMethod.GET)
public String listerUtilisateurs(Model model) {
model.addAttribute("utilisateurs", utilisateurService.listerTous());
return "listeUtilisateurs";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.GET)
public String afficherFormulaireAjout(Model model) {
model.addAttribute("utilisateur", new Utilisateur());
return "ajouterUtilisateur";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.POST)
public String ajouterUtilisateur(@Valid @ModelAttribute("utilisateur") Utilisateur utilisateur, BindingResult result) {
if (result.hasErrors()) {
return "ajouterUtilisateur";
}
utilisateurService.ajouter(utilisateur);
return "redirect:/utilisateurs";
}
@RequestMapping(value = "/supprimer/{id}", method = RequestMethod.GET)
public String supprimerUtilisateur(@PathVariable("id") int id) {
utilisateurService.supprimer(id);
return "redirect:/utilisateurs";
}
@RequestMapping(value = "/modifier/{id}", method = RequestMethod.GET)
public String afficherFormulaireModification(@PathVariable("id") int id, Model model) {
Utilisateur utilisateur = utilisateurService.trouverParId(id);
model.addAttribute("utilisateur", utilisateur);
return "modifierUtilisateur";
}
@RequestMapping(value = "/modifier", method = RequestMethod.POST)
public String modifierUtilisateur(@Valid @ModelAttribute("utilisateur") Utilisateur utilisateur, BindingResult result) {
if (result.hasErrors()) {
return "modifierUtilisateur";
}
utilisateurService.ajouter(utilisateur); // "Ajouter" fonctionne aussi pour la mise à jour avec JPA
return "redirect:/utilisateurs";
}
}
Configuration - Mise à jour de spring-config.xml :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!-- Analyse des composants -->
<context:component-scan base-package="com.exemple.crud"/>
<!-- Activer MVC -->
<mvc:annotation-driven/>
<!-- Configuration View Resolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- DataSource pour H2 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<!-- EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.exemple.crud.model"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!-- Activer JPA Repositories -->
<jpa:repositories base-package="com.exemple.crud.repository"/>
</beans>
6.4. Transactions avec @Transactional
- Les opérations sur la base de données doivent être transactionnelles pour garantir l’intégrité des données.
- Pour ce faire, nous ajoutons l'annotation @Transactional au service. Cela permet de gérer les transactions de manière transparente.
- L'annotation garantit que toutes les opérations effectuées au sein d'une méthode seront exécutées dans une seule transaction. En cas d’erreur, toutes les modifications sont annulées pour préserver l'intégrité des données.
Mise à jour de UtilisateurService.java :
package com.exemple.crud.service;
import com.exemple.crud.model.Utilisateur;
import com.exemple.crud.repository.UtilisateurRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Service
@Transactional
public class UtilisateurService {
@Autowired
private UtilisateurRepository utilisateurRepository;
public List<Utilisateur> listerTous() {
return utilisateurRepository.findAll();
}
public Utilisateur trouverParId(int id) {
Optional<Utilisateur> utilisateur = utilisateurRepository.findById(id);
return utilisateur.orElse(null);
}
public void ajouter(Utilisateur utilisateur) {
utilisateurRepository.save(utilisateur);
}
public void supprimer(int id) {
utilisateurRepository.deleteById(id);
}
}
Ajout dans spring-config.xml pour activer les transactions :
<tx:annotation-driven transaction-manager="transactionManager"/>
(Ajoutez l’espace de noms xmlns:tx et son schemaLocation au début de spring-config.xml) :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"
xmlns:tx="http://www.springframework.org/schema/tx">
<!-- Analyse des composants -->
<context:component-scan base-package="com.exemple.crud"/>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- Activer MVC -->
<mvc:annotation-driven/>
<!-- Configuration View Resolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- DataSource pour H2 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<!-- EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.exemple.crud.model"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!-- Activer JPA Repositories -->
<jpa:repositories base-package="com.exemple.crud.repository"/>
</beans>
Projet CRUD : Progression
Structure actuelle :
projet-crud/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.exemple.crud/
│ │ │ ├── controller/
│ │ │ │ └── UtilisateurControleur.java
│ │ │ ├── model/
│ │ │ │ └── Utilisateur.java
│ │ │ ├── repository/
│ │ │ │ └── UtilisateurRepository.java
│ │ │ └── service/
│ │ │ └── UtilisateurService.java
│ │ └── webapp/
│ │ ├── WEB-INF/
│ │ │ ├── web.xml
│ │ │ ├── spring-config.xml
│ │ │ └── views/
│ │ │ ├── listeUtilisateurs.jsp
│ │ │ ├── ajouterUtilisateur.jsp
│ │ │ └── modifierUtilisateur.jsp
│ │ └── index.jsp
├── pom.xml
État actuel :
- CRUD complet connecté à une base de données H2 (in-memory).
- Les données persistent pendant l’exécution de l’application.
Test :
- Recompilez avec mvn clean package.
- Déployez sur Tomcat.
- Ajoutez, modifiez, supprimez des utilisateurs : les données sont maintenant dans la base H2.
Prochaines étapes :
- Gérer les exceptions et ajouter des fonctionnalités avancées (section 7).
7. Fonctionnalités avancées
7.1. Gestion des exceptions avec @ExceptionHandler
- Spring MVC gère les exceptions centralisées avec @ExceptionHandler.
- Cette annotation permet de capturer les erreurs dans le contrôleur et de gérer des exceptions spécifiques.
- Par exemple, nous gérons les tentatives de modification ou suppression d'un ID inexistant avec une réponse personnalisée.
Mise à jour de UtilisateurControleur.java :
package com.exemple.crud.controller;
import com.exemple.crud.model.Utilisateur;
import com.exemple.crud.service.UtilisateurService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import jakarta.validation.Valid;
@Controller
@RequestMapping("/utilisateurs")
public class UtilisateurControleur {
@Autowired
private UtilisateurService utilisateurService;
@RequestMapping(method = RequestMethod.GET)
public String listerUtilisateurs(Model model) {
model.addAttribute("utilisateurs", utilisateurService.listerTous());
return "listeUtilisateurs";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.GET)
public String afficherFormulaireAjout(Model model) {
model.addAttribute("utilisateur", new Utilisateur());
return "ajouterUtilisateur";
}
@RequestMapping(value = "/ajouter", method = RequestMethod.POST)
public String ajouterUtilisateur(@Valid @ModelAttribute("utilisateur") Utilisateur utilisateur, BindingResult result) {
if (result.hasErrors()) {
return "ajouterUtilisateur";
}
utilisateurService.ajouter(utilisateur);
return "redirect:/utilisateurs";
}
@RequestMapping(value = "/supprimer/{id}", method = RequestMethod.GET)
public String supprimerUtilisateur(@PathVariable("id") int id) {
if (utilisateurService.trouverParId(id) == null) {
throw new IllegalArgumentException("Utilisateur avec ID " + id + " non trouvé.");
}
utilisateurService.supprimer(id);
return "redirect:/utilisateurs";
}
@RequestMapping(value = "/modifier/{id}", method = RequestMethod.GET)
public String afficherFormulaireModification(@PathVariable("id") int id, Model model) {
Utilisateur utilisateur = utilisateurService.trouverParId(id);
if (utilisateur == null) {
throw new IllegalArgumentException("Utilisateur avec ID " + id + " non trouvé.");
}
model.addAttribute("utilisateur", utilisateur);
return "modifierUtilisateur";
}
@RequestMapping(value = "/modifier", method = RequestMethod.POST)
public String modifierUtilisateur(@Valid @ModelAttribute("utilisateur") Utilisateur utilisateur, BindingResult result) {
if (result.hasErrors()) {
return "modifierUtilisateur";
}
utilisateurService.ajouter(utilisateur);
return "redirect:/utilisateurs";
}
@ExceptionHandler(IllegalArgumentException.class)
public ModelAndView gererException(IllegalArgumentException ex) {
ModelAndView mav = new ModelAndView("erreur");
mav.addObject("message", ex.getMessage());
return mav;
}
}
Vue d’erreur - src/main/webapp/WEB-INF/views/erreur.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE >
<html>
<head>
<title>Erreur</title>
</head>
<body>
<h1>Une erreur est survenue</h1>
<p>${message}</p>
<a href="/projet-crud/utilisateurs">Retour à la liste</a>
</body>
</html>
- @ExceptionHandler : Capture les exceptions IllegalArgumentException lancées dans ce contrôleur.
- ModelAndView : Renvoie une vue personnalisée avec le message d’erreur.
7.2. Intercepteurs (Interceptors) pour pré/post-traitement
- Les intercepteurs permettent d'exécuter du code avant ou après le traitement d'une requête.
- Ils sont utiles pour des tâches comme la journalisation, la gestion de la sécurité, ou la modification de la réponse.
- Dans ce cas, nous allons ajouter un intercepteur pour journaliser les requêtes entrantes.
Intercepteur - src/main/java/com/exemple/crud/interceptor/LogInterceptor.java :
package com.exemple.crud.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Requête reçue : " + request.getRequestURI());
return true; // Continue le traitement
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Vue rendue pour : " + request.getRequestURI());
}
}
Configuration dans spring-config.xml :
<mvc:interceptors>
<bean class="com.exemple.crud.interceptor.LogInterceptor"/>
</mvc:interceptors>
Explication :
- preHandle : Exécuté avant le contrôleur.
- postHandle : Exécuté après le contrôleur mais avant le rendu de la vue.
7.3. Internationalisation (i18n)
L’internationalisation permet d’afficher des messages dans différentes langues. Nous allons configurer des messages en français et anglais.Fichier de messages - src/main/resources/messages.properties (par défaut, anglais) :
titre.liste=User List
titre.ajouter=Add User
titre.modifier=Edit User
aucun.utilisateur=No users yet
Fichier de messages - src/main/resources/messages_fr.properties (français) :
titre.liste=Liste des utilisateurs
titre.ajouter=Ajouter un utilisateur
titre.modifier=Modifier un utilisateur
aucun.utilisateur=Aucun utilisateur pour l'instant
Configuration dans spring-config.xml :
<!-- Définir un LocaleResolver pour gérer les changements de langue -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="en"/> <!-- Langue par défaut -->
</bean>
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="useCodeAsDefaultMessage" value="false"/> <!-- Si true, affiche le code si le message n'est pas trouvé -->
</bean>
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang"/>
</bean>
</mvc:interceptors>
Mise à jour des vues :
- listeUtilisateurs.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<!DOCTYPE >
<html>
<head>
<title><spring:message code="titre.liste"/></title>
</head>
<body>
<h1><spring:message code="titre.liste"/></h1>
<c:if test="${empty utilisateurs}">
<p><spring:message code="aucun.utilisateur"/></p>
</c:if>
<c:forEach var="utilisateur" items="${utilisateurs}">
<p>ID: ${utilisateur.id} - Nom: ${utilisateur.nom}
<a href="/projet-crud/utilisateurs/modifier/${utilisateur.id}">Modifier</a>
<a href="/projet-crud/utilisateurs/supprimer/${utilisateur.id}">Supprimer</a></p>
</c:forEach>
<a href="/projet-crud/utilisateurs/ajouter"><spring:message code="titre.ajouter"/></a>
<br/>
<a href="/projet-crud/">Retour à l'accueil</a>
</body>
</html>
- ajouterUtilisateur.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<!DOCTYPE >
<html>
<head>
<title><spring:message code="titre.ajouter"/></title>
</head>
<body>
<h1><spring:message code="titre.ajouter"/></h1>
<form:form method="post" action="/projet-crud/utilisateurs/ajouter" modelAttribute="utilisateur">
<label>Nom : </label>
<form:input path="nom"/>
<form:errors path="nom" cssClass="error"/>
<input type="submit" value="Ajouter"/>
</form:form>
<a href="/projet-crud/utilisateurs">Retour à la liste</a>
</body>
<style>.error { color: red; }</style>
</html>
- modifierUtilisateur.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<!DOCTYPE >
<html>
<head>
<title><spring:message code="titre.modifier"/></title>
</head>
<body>
<h1><spring:message code="titre.modifier"/></h1>
<form:form method="post" action="/projet-crud/utilisateurs/modifier" modelAttribute="utilisateur">
<form:hidden path="id"/>
<label>Nom : </label>
<form:input path="nom"/>
<form:errors path="nom" cssClass="error"/>
<input type="submit" value="Modifier"/>
</form:form>
<a href="/projet-crud/utilisateurs">Retour à la liste</a>
</body>
<style>.error { color: red; }</style>
</html>
Test i18n :
- Ajoutez ?lang=fr ou ?lang=en à l’URL (ex. http://localhost:8080/projet-crud/utilisateurs?lang=fr).
7.4. Gestion des sessions et cookies
Nous allons ajouter un compteur de visites dans la session pour chaque utilisateur.Mise à jour de UtilisateurControleur.java :
import jakarta.servlet.http.HttpSession;
@RequestMapping(method = RequestMethod.GET)
public String listerUtilisateurs(Model model, HttpSession session) {
Integer visites = (Integer) session.getAttribute("visites");
if (visites == null) {
visites = 0;
}
visites++;
session.setAttribute("visites", visites);
model.addAttribute("visites", visites);
model.addAttribute("utilisateurs", utilisateurService.listerTous());
return "listeUtilisateurs";
}
Mise à jour de listeUtilisateurs.jsp :
<p>Nombre de visites : ${visites}</p>
<!-- Ajouter après le <h1> -->
Projet CRUD : Progression
État actuel :
- Gestion des exceptions avec page d’erreur.
- Journalisation des requêtes via intercepteur.
- Internationalisation (français/anglais).
- Compteur de visites dans la session.
Test :
- Recompilez avec mvn clean package.
- Déployez sur Tomcat.
- Testez une URL invalide (ex. /utilisateurs/modifier/999), changez la langue avec ?lang=fr, et observez le compteur de visites.
Prochaines étapes :
- Ajouter des notions de sécurité (section 8).
8. Sécurité de base avec Spring MVC
8.1. Introduction à Spring Security (optionnel)
Spring Security est un module puissant pour sécuriser les applications Spring, offrant authentification, autorisation, et protection contre les attaques courantes (ex. CSRF). Cependant, comme nous utilisons Spring MVC sans Spring Boot, nous allons d’abord implémenter une sécurité manuelle simple avant de montrer une configuration de base avec Spring Security en option.8.2. Sécurisation des contrôleurs
Nous allons implémenter une sécurité manuelle avec un filtre de servlet pour vérifier si un utilisateur est "connecté" avant d’accéder aux pages du CRUD. Nous utiliserons une session pour simuler une authentification.Filtre - src/main/java/com/exemple/crud/filter/AuthFilter.java :
package com.exemple.crud.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
String uri = httpRequest.getRequestURI();
if (uri.endsWith("/login") || uri.endsWith("/doLogin")) {
chain.doFilter(request, response); // Autorise les pages de login
return;
}
if (session == null || session.getAttribute("utilisateurConnecte") == null) {
httpResponse.sendRedirect(httpRequest.getContextPath() + "/login");
} else {
chain.doFilter(request, response); // Continue si connecté
}
}
@Override
public void destroy() {}
}
Configuration dans web.xml :
<filter>
<filter-name>authFilter</filter-name>
<filter-class>com.exemple.crud.filter.AuthFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>authFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Contrôleur de login - src/main/java/com/exemple/crud/controller/LoginControleur.java :
package com.exemple.crud.controller;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class LoginControleur {
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String afficherLogin() {
return "login";
}
@RequestMapping(value = "/doLogin", method = RequestMethod.POST)
public String faireLogin(@RequestParam("username") String username,
@RequestParam("password") String password,
HttpSession session) {
// Simulation d'authentification (username: admin, password: 1234)
if ("admin".equals(username) && "1234".equals(password)) {
session.setAttribute("utilisateurConnecte", username);
return "redirect:/utilisateurs";
} else {
return "redirect:/login?erreur=1";
}
}
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/login";
}
}
Vue - src/main/webapp/WEB-INF/views/login.jsp :
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE >
<html>
<head>
<title>Connexion</title>
</head>
<body>
<h1>Connexion</h1>
<% if (request.getParameter("erreur") != null) { %>
<p style="color: red;">Nom d'utilisateur ou mot de passe incorrect.</p>
<% } %>
<form method="post" action="/projet-crud/doLogin">
<label>Nom d'utilisateur : </label>
<input type="text" name="username"/>
<br/>
<label>Mot de passe : </label>
<input type="password" name="password"/>
<br/>
<input type="submit" value="Se connecter"/>
</form>
</body>
</html>
Mise à jour de listeUtilisateurs.jsp pour ajouter un lien de déconnexion :
<a href="/projet-crud/logout">Déconnexion</a>
<!-- Ajouter après "Retour à l'accueil" -->
Explication :
- Le filtre AuthFilter vérifie la présence d’un attribut utilisateurConnecte dans la session.
- Une page de login simple simule une authentification (admin/1234).
- La déconnexion invalide la session.
8.3. Authentification et autorisation simples
Pour une sécurité plus robuste, nous pourrions utiliser Spring Security. Voici une configuration optionnelle rapide (non implémentée ici pour rester dans Spring MVC pur, mais montrée comme exemple).Dépendance Spring Security (optionnel) dans pom.xml :
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>6.0.0</version>
</dependency>
Configuration Spring Security (optionnel) dans spring-security.xml :
<http auto-config="true">
<intercept-url pattern="/utilisateurs/**" access="hasRole('USER')" />
<form-login login-page="/login" default-target-url="/utilisateurs" />
<logout logout-url="/logout" logout-success-url="/login" />
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="admin" password="{noop}1234" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
Ajout dans web.xml (optionnel) :
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-security.xml</param-value>
</context-param>
Note : Nous n’utilisons pas cette configuration ici, mais elle montre comment Spring Security pourrait remplacer notre filtre manuel.
Projet CRUD : Progression
Structure actuelle :
projet-crud/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.exemple.crud/
│ │ │ ├── controller/
│ │ │ │ ├── UtilisateurControleur.java
│ │ │ │ └── LoginControleur.java
│ │ │ ├── filter/
│ │ │ │ └── AuthFilter.java
│ │ │ ├── interceptor/
│ │ │ │ └── LogInterceptor.java
│ │ │ ├── model/
│ │ │ │ └── Utilisateur.java
│ │ │ ├── repository/
│ │ │ │ └── UtilisateurRepository.java
│ │ │ └── service/
│ │ │ └── UtilisateurService.java
│ │ ├── resources/
│ │ │ ├── messages.properties
│ │ │ └── messages_fr.properties
│ │ └── webapp/
│ │ ├── WEB-INF/
│ │ │ ├── web.xml
│ │ │ ├── spring-config.xml
│ │ │ └── views/
│ │ │ ├── listeUtilisateurs.jsp
│ │ │ ├── ajouterUtilisateur.jsp
│ │ │ ├── modifierUtilisateur.jsp
│ │ │ ├── erreur.jsp
│ │ │ └── login.jsp
│ │ └── index.jsp
├── pom.xml
État actuel :
- Sécurité manuelle avec filtre et page de login.
- CRUD accessible uniquement après connexion.
Test :
- Recompilez avec mvn clean package.
- Déployez sur Tomcat.
- Accédez à http://localhost:8080/projet-crud/utilisateurs : vous serez redirigé vers /login.
- Connectez-vous avec admin/1234, puis testez le CRUD et la déconnexion.
Prochaines étapes :
- Déploiement et tests (section 9).
9. Déploiement
9.1. Construction du fichier WAR
Puisque notre projet utilise Spring MVC sans Spring Boot, nous générons un fichier WAR (Web Application Archive) pour le déployer sur un conteneur de servlets comme Tomcat. Maven s’occupe de cette tâche grâce au plugin maven-war-plugin déjà configuré.Configuration existante dans pom.xml (rappel) :
<packaging>war</packaging>
<build>
<finalName>projet-crud</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>
Commande pour générer le WAR :
mvn clean package
Explication :
- mvn clean : Supprime les fichiers générés précédemment.
- mvn package : Compile le projet et crée target/projet-crud.war.
9.2. Déploiement sur un serveur (Tomcat, Jetty)
Nous allons déployer notre application sur Apache Tomcat, un conteneur de servlets populaire. Les étapes sont similaires pour Jetty.Étapes de déploiement sur Tomcat :
- Téléchargez et installez Tomcat :
- Téléchargez Tomcat 10 (compatible avec Jakarta EE 9+) depuis tomcat.apache.org.
- Décompressez-le et démarrez-le avec bin/startup.sh (Linux/Mac) ou bin/startup.bat (Windows).
- Déployez le WAR :
- Copiez target/projet-crud.war dans le dossier webapps de Tomcat.
- Tomcat déploie automatiquement le WAR au démarrage ou détecte les modifications.
- Accédez à l’application :
- Ouvrez un navigateur et allez à http://localhost:8080/projet-crud/.
- Vous serez redirigé vers la page de login.
Commandes utiles pour Tomcat :
- Arrêter : bin/shutdown.sh ou bin/shutdown.bat.
- Vérifier les logs : Consultez logs/catalina.out pour les erreurs.
9.3. Tests unitaires et d’intégration
Nous allons ajouter des tests unitaires pour le service et des tests d’intégration pour le contrôleur afin de valider le fonctionnement de l’application.Dépendances pour les tests dans pom.xml :
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
Test unitaire - src/test/java/com/exemple/crud/service/UtilisateurServiceTest.java :
package com.exemple.crud.service;
import com.exemple.crud.model.Utilisateur;
import com.exemple.crud.repository.UtilisateurRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
public class UtilisateurServiceTest {
@Mock
private UtilisateurRepository utilisateurRepository;
@InjectMocks
private UtilisateurService utilisateurService;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testListerTous() {
List<Utilisateur> utilisateurs = Arrays.asList(new Utilisateur(1, "Alice"), new Utilisateur(2, "Bob"));
when(utilisateurRepository.findAll()).thenReturn(utilisateurs);
List<Utilisateur> result = utilisateurService.listerTous();
assertEquals(2, result.size());
assertEquals("Alice", result.get(0).getNom());
verify(utilisateurRepository, times(1)).findAll();
}
@Test
public void testTrouverParId() {
Utilisateur utilisateur = new Utilisateur(1, "Alice");
when(utilisateurRepository.findById(1)).thenReturn(Optional.of(utilisateur));
Utilisateur result = utilisateurService.trouverParId(1);
assertEquals("Alice", result.getNom());
verify(utilisateurRepository, times(1)).findById(1);
}
}
Test d’intégration - src/test/java/com/exemple/crud/controller/UtilisateurControleurTest.java :
package com.exemple.crud.controller;
import com.exemple.crud.model.Utilisateur;
import com.exemple.crud.service.UtilisateurService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
public class UtilisateurControleurTest {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@Autowired
private UtilisateurService utilisateurService;
@Test
public void testListerUtilisateurs() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
mockMvc.perform(get("/utilisateurs"))
.andExpect(status().isOk())
.andExpect(view().name("listeUtilisateurs"));
}
}
Configuration pour les tests - src/test/resources/applicationContext-test.xml :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="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">
<context:component-scan base-package="com.exemple.crud"/>
</beans>
Exécution des tests :
mvn test
Explication :
- Test unitaire : Utilise Mockito pour simuler le repository et tester le service.
- Test d’intégration : Utilise MockMvc pour simuler des requêtes HTTP (nécessite un contexte Spring complet, ici simplifié).
Projet CRUD : Progression
État actuel :
- Projet prêt à être déployé sous forme de WAR.
- Tests unitaires et d’intégration basiques ajoutés.
Structure finale :
projet-crud/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.exemple.crud/
│ │ │ ├── controller/
│ │ │ │ ├── UtilisateurControleur.java
│ │ │ │ └── LoginControleur.java
│ │ │ ├── filter/
│ │ │ │ └── AuthFilter.java
│ │ │ ├── interceptor/
│ │ │ │ └── LogInterceptor.java
│ │ │ ├── model/
│ │ │ │ └── Utilisateur.java
│ │ │ ├── repository/
│ │ │ │ └── UtilisateurRepository.java
│ │ │ └── service/
│ │ │ └── UtilisateurService.java
│ │ ├── resources/
│ │ │ ├── messages.properties
│ │ │ └── messages_fr.properties
│ │ └── webapp/
│ │ ├── WEB-INF/
│ │ │ ├── web.xml
│ │ │ ├── spring-config.xml
│ │ │ └── views/
│ │ │ ├── listeUtilisateurs.jsp
│ │ │ ├── ajouterUtilisateur.jsp
│ │ │ ├── modifierUtilisateur.jsp
│ │ │ ├── erreur.jsp
│ │ │ └── login.jsp
│ │ └── index.jsp
│ ├── test/
│ │ ├── java/
│ │ │ └── com.exemple.crud/
│ │ │ ├── controller/
│ │ │ │ └── UtilisateurControleurTest.java
│ │ │ └── service/
│ │ │ └── UtilisateurServiceTest.java
│ │ └── resources/
│ │ └── applicationContext-test.xml
├── pom.xml
Test final :
- Exécutez mvn clean package.
- Déployez target/projet-crud.war dans Tomcat.
- Accédez à http://localhost:8080/projet-crud/ et testez toutes les fonctionnalités.
Prochaines étapes :
- Bonnes pratiques et conclusion (section 10).
10. Bonnes pratiques et conclusion
10.1. Structuration du code
Une bonne structuration améliore la lisibilité et la maintenabilité. Voici les pratiques appliquées dans notre projet :- Séparation des responsabilités : Contrôleurs, services, repositories, et modèles sont séparés.
- Packages logiques : Organisation par fonctionnalité (ex. controller, service, model).
- Nommage clair : Classes et méthodes reflètent leur rôle (ex. UtilisateurService, listerTous).
Exemple de structure (rappel) :
projet-crud/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.exemple.crud/
│ │ │ ├── controller/
│ │ │ ├── filter/
│ │ │ ├── interceptor/
│ │ │ ├── model/
│ │ │ ├── repository/
│ │ │ └── service/
│ │ ├── resources/
│ │ └── webapp/
│ ├── test/
├── pom.xml
graph TD
A[Structuration] --> B(Séparation Responsabilités)
A --> C[Packages Logiques]
A --> D[Nommage Clair]
B --> E[Contrôleurs]
B --> F[Services]
B --> G[Repositories]
C --> H[com.exemple.crud.*]
10.2. Optimisation des performances
Pour optimiser notre application Spring MVC :- Réduire les requêtes inutiles : Utiliser une base de données en mémoire (H2) pour les tests, mais envisager une base persistante (MySQL, PostgreSQL) en production.
- Cache : Ajouter un cache pour les données fréquemment accédées (non implémenté ici, mais possible avec Ehcache ou Spring Cache).
- Requêtes efficaces : Minimiser les appels à la base avec des requêtes batch ou des index.
Exemple d’ajout d’un index dans Utilisateur.java (pour une base réelle) :
@Entity
@Table(indexes = @Index(columnList = "nom"))
public class Utilisateur {
// Champs existants
}
10.3. Ressources pour approfondir Spring MVC
Pour aller plus loin :- Documentation officielle : Spring MVC Reference.
- Livres : "Spring in Action" (Craig Walls).
- Tutoriels : Baeldung (baeldung.com/spring-mvc-tutorial).
- Projets open-source : Explorez des dépôts GitHub comme ceux de Spring Samples.
Conclusion du projet CRUD
Résumé :
Nous avons construit une application CRUD complète avec Spring MVC (sans Spring Boot) qui :
- Gère une liste d’utilisateurs avec création, lecture, mise à jour, et suppression.
- Utilise une base de données H2 via Hibernate/JPA.
- Inclut des fonctionnalités avancées : gestion des exceptions, intercepteurs, internationalisation, sessions, et sécurité manuelle.
- Est déployable sur Tomcat avec un fichier WAR.
- Est testée avec des tests unitaires et d’intégration.
Points forts :
- Structure modulaire et respect des principes MVC.
- Configuration manuelle pour une compréhension approfondie.
- Fonctionnalités pratiques pour une application web réelle.
Améliorations possibles :
- Passer à Spring Security pour une sécurité robuste.
- Ajouter un frontend moderne (ex. Angular, React) via une API REST.
- Utiliser une base de données persistante en production.
Test final :
- Compilez avec mvn clean package.
- Déployez projet-crud.war sur Tomcat.
- Accédez à http://localhost:8080/projet-crud/, connectez-vous (admin/1234), et testez toutes les fonctionnalités.