By:Hakiki

Spring MVC

  1. Introduction
    1. Présentation du framework
    2. Spring MVC ?
    3. Spring MVC et Spring Boot
    4. Architecture MVC
    5. Prérequis
    6. Création d'un projet
    7. Configuration de web.xml
    8. Configuration du DispatcherServlet
  2. Base Spring MVC
    1. Le rôle du DispatcherServlet
    2. Définition d'un contrôleur
    3. Mappage des requêtes
    4. Gestion des vues
  3. Requêtes et Modèles
    1. Méthodes HTTP
    2. Paramètres de requête
    3. Variables de chemin
    4. En-têtes HTTP
    5. Utilisation de Model et ModelAndView
    6. Intégration avec JSP, Thymeleaf, Freemarker
    7. Form Binding et Validation
  4. Données et Avancées
    1. Injection de dépendances
    2. Connexion à la base de données
    3. Couches Service et DAO
    4. Transactions avec @Transactional
    5. Gestion des exceptions
    6. Intercepteurs et i18n
    7. Gestion des sessions
  5. Sécurité et Déploiement
    1. Spring Security
    2. Sécurisation des contrôleurs
    3. Authentification simple
    4. Construction du fichier WAR
    5. Déploiement sur Tomcat, Jetty
    6. Tests unitaires et d'intégration
  6. Bonnes pratiques
    1. Structuration du code
    2. Optimisation des performances




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).
graph TD A[Spring Framework] --> B(Inversion de Contrôle) A --> C(Injection de Dépendances) A --> D[Modules] D --> E(Spring MVC) D --> F(Spring Core) D --> G(Spring Security) E --> H[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 automatiquement MyService 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

graph TD A[IoC et DI dans Spring MVC] --> B(Inversion de Contrôle - IoC) A --> C(Injection de Dépendances - DI) B --> B1(Définition) B1 --> B1a[Contrôle donné au conteneur] B1 --> B1b[Opposé à l'instanciation manuelle] B --> B2(Objectif) B2 --> B2a[Découplage des composants] B2 --> B2b[Flexibilité et modularité] B --> B3(Conteneur IoC) B3 --> B3a[Spring Bean Container] B3 --> B3b[Gère le cycle de vie des beans] C --> C1(Définition) C1 --> C1a[Injection des dépendances dans les objets] C1 --> C1b[Réduit les dépendances explicites] C --> C2(Méthodes d'injection) C2 --> C2a[Par constructeur] C2 --> C2b[Par setters] C2 --> C2c[Par annotations
@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 !"; } }
graph TD A[Spring] --> B(Spring MVC) A --> C(Spring Boot) B --> D[Configuration manuelle] C --> E[Configuration automatique] B --> F[Modèle MVC] C --> G[Starters]

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 :

AspectSpring MVCSpring Boot
ConfigurationManuelle (XML/Java)Automatique
DépendancesDéfinies manuellementGérées par starters
ServeurExterne (Tomcat)Embarqué par défaut
graph TD A[Spring] --> B(Spring MVC) A --> C(Spring Boot) B --> D[Configuration manuelle] C --> E[Configuration automatique] B --> F[Modèle MVC] C --> G[Starters]

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 } }
graph TD A[MVC] --> B(Modèle) A --> C(Vue) A --> D(Contrôleur) D --> B[Données] B --> C[Affichage] D --> E[Requête HTTP]



classDiagram class Model { +Langages: Java +Bibliothèques: Spring Core, Spring Data JPA, Hibernate, Lombok, Jackson +Responsabilité: Gérer les données et la logique métier } class View { +Langages: JSP, HTML, CSS, JavaScript +Bibliothèques: Spring MVC, JSTL, Thymeleaf, Bootstrap, jQuery +Responsabilité: Afficher les données à l'utilisateur } class Controller { +Langages: Java +Bibliothèques: Spring MVC, Spring Boot, Jackson, Servlet API +Responsabilité: Gérer les requêtes HTTP et coordonner Model/View } Controller --> Model : Utilise Controller --> View : Renvoie Model --> Controller : Fournit données

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.
Étape actuelle : Comprendre les bases de Spring MVC avant de configurer le projet.

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)

Pour développer une application Spring MVC, vous avez besoin de :

  • 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 :

  1. Installez Java https://www.oracle.com/java/technologies/downloads/(java -version pour vérifier).
  2. Installez Maven (installer eclipse ide for jee and web https://www.eclipse.org/downloads/packages/) (mvn -version pour vérifier).
  3. Téléchargez et configurez Apache Tomcat (ex. démarrer via startup.bat ou startup.sh).
graph TD A[Prérequis] --> B(Java JDK) A --> C(Maven) A --> D[Servlet Container] D --> E(Tomcat) D --> F(Jetty) B --> G[Version 8+] C --> H[Gestion dépendances]

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.

graph TD A[Projet Maven] --> B(pom.xml) B --> C[Dépendances] B --> D[Build] C --> E(spring-webmvc) C --> F(jakarta.servlet-api) D --> G[WAR] D --> H[Java 17]

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.
graph TD A[web.xml] --> B(DispatcherServlet) B --> C[Nom: dispatcher] B --> D[Classe: org.springframework.web.servlet.DispatcherServlet] B --> E[Config: spring-config.xml] A --> F[Servlet Mapping] F --> G[URL: /]

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).
graph TD A[spring-config.xml] --> B(Component Scan) A --> C[Annotation Driven] A --> D[View Resolver] B --> E[Package: com.exemple.crud] D --> F[Prefix: /WEB-INF/views/] D --> G[Suffix: .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> <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 :

  1. Exécutez mvn clean package.
  2. Déployez target/projet-crud.war dans le dossier webapps de Tomcat.
  3. 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 :

  1. Une requête HTTP arrive, par exemple, /utilisateurs pour afficher la liste des utilisateurs.
  2. Le DispatcherServlet consulte les mappages définis dans les contrôleurs, recherchant une méthode associée à l’URL demandée.
  3. 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.
graph TD A[DispatcherServlet] --> B(Reçoit Requête HTTP) B --> C[Analyse URL] C --> D[Appelle Contrôleur] D --> E[Gère Modèle] E --> F[Rendu Vue] F --> G[Réponse HTTP]

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> <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>
graph TD A[Contrôleur] --> B(@Controller) A --> C[@RequestMapping] C --> D[URL: /utilisateurs] A --> E[Retourne Vue] E --> F[listeUtilisateurs.jsp]

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> <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>
graph TD A[@RequestMapping] --> B(Niveau Classe) A --> C[Niveau Méthode] B --> D[utilisateurs] C --> E[GET /utilisateurs] C --> F[GET /utilisateurs/ajouter] E --> G[listeUtilisateurs.jsp] F --> H[ajouterUtilisateur.jsp]

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.
graph TD A[View Resolver] --> B(InternalResourceViewResolver) B --> C[Prefix: /WEB-INF/views/] B --> D[Suffix: .jsp] A --> E[Nom logique] E --> F[Fichier physique] E -->|Exemple| G[listeUtilisateurs] G --> H[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 :

  1. Recompilez avec mvn clean package.
  2. Déployez sur Tomcat.
  3. 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.
graph TD A[Méthodes HTTP] --> B(GET) A --> C(POST) A --> D(PUT) A --> E(DELETE) B --> F[Lecture] C --> G[Création] D --> H[Mise à jour] E --> I[Suppression]

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.
graph TD A[@RequestParam] --> B[Récupère Paramètre] B --> C[Nom: nom] A --> D[Utilisation] D --> E[Ajout Utilisateur] E --> F[Redirection]

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> <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>
graph TD A[@PathVariable] --> B[Récupère Variable URL] B --> C[Ex: /supprimer/id] C --> D[id = 1] A --> E[Utilisation] E --> F[Suppression] F --> G[Redirection]

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.
graph TD A[Gestion En-têtes] --> B(@RequestHeader) B --> C[Ex: User-Agent] A --> D[HttpServletResponse] D --> E[Définir En-têtes] B --> F[Utilisation]

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> <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.
  • 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 :

  1. Recompilez avec mvn clean package.
  2. Déployez sur Tomcat.
  3. 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}.
graph TD A[Modèles] --> B(Model) A --> C(ModelAndView) B --> D[Ajoute Données] C --> E[Vue + Données] D --> F[Ex: utilisateurs] E --> G[listeUtilisateurs.jsp]

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>
graph TD A[Moteurs de Templates] --> B(JSP) A --> C(Thymeleaf) A --> D(Freemarker) B --> E[Utilisé ici] E --> F[InternalResourceViewResolver] F --> G[Prefix: /WEB-INF/views/] F --> H[Suffix: .jsp]

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> <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> <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> <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>
graph TD A[Form Binding] --> B(@ModelAttribute) B --> C[Lie Formulaire] C --> D[Objet: Utilisateur] A --> E[GET /modifier/id] E --> F[Formulaire Pré-rempli] A --> G[POST /modifier] G --> H[Mise à jour]

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> <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> <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>
graph TD A[Validation] --> B(Hibernate Validator) B --> C[@NotBlank] B --> D[@Size] A --> E[@Valid] E --> F[BindingResult] F --> G[Affiche Erreurs] G --> H[Formulaire]

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 :

  1. Recompilez avec mvn clean package.
  2. Déployez sur Tomcat.
  3. 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.
graph TD A[Spring IoC] --> B(Inversion de Contrôle) B --> C[Conteneur] C --> D[Injection Dépendances] D --> E[@Autowired] D --> F[Configuration XML]

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>
graph TD A[Gestion Données] --> B(Entité) A --> C(Repository) A --> D(Service) A --> E(Contrôleur) B --> F[Utilisateur] C --> G[JpaRepository] D --> H[UtilisateurService] E --> I[UtilisateurControleur] H --> J[@Autowired Repository] I --> K[@Autowired Service]

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>
graph TD A[Transactions] --> B(@Transactional) B --> C[Service] A --> D[Transaction Manager] D --> E[JpaTransactionManager] A --> F[Annotation Driven]

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 :

  1. Recompilez avec mvn clean package.
  2. Déployez sur Tomcat.
  3. 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> <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.
graph TD A[Gestion Exceptions] --> B(@ExceptionHandler) B --> C[Type: IllegalArgumentException] B --> D[Retourne Vue] D --> E[erreur.jsp] C --> F[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.
graph TD A[Intercepteurs] --> B(HandlerInterceptor) B --> C[preHandle] B --> D[postHandle] C --> E[Avant Contrôleur] D --> F[Après Contrôleur] A --> G[mvc:interceptors]

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> <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> <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> <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).
graph TD A[Internationalisation] --> B(MessageSource) B --> C[messages.properties] B --> D[messages_fr.properties] A --> E[spring:message] E --> F[Affiche Texte]

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> -->
graph TD A[Gestion Sessions] --> B(HttpSession) B --> C[getAttribute] B --> D[setAttribute] A --> E[Ex: Compteur Visites] E --> F[Affiche dans Vue]

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 :

  1. Recompilez avec mvn clean package.
  2. Déployez sur Tomcat.
  3. 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.

graph TD A[Sécurité] --> B(Spring Security) A --> C[Sécurité Manuelle] B --> D[Authentification] B --> E[Autorisation] C --> F[Filtre Simple]

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> <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.
graph TD A[Sécurisation Contrôleurs] --> B(Filtre) B --> C[AuthFilter] C --> D[Vérifie Session] D --> E[Redirige /login] A --> F[LoginControleur] F --> G[GET /login] F --> H[POST /doLogin] F --> I[GET /logout]

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.

graph TD A[Authentification] --> B(Session Manuelle) A --> C[Spring Security] B --> D[UtilisateurConnecte] C --> E[Roles] C --> F[Formulaire Login] C --> G[Logout]

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 :

  1. Recompilez avec mvn clean package.
  2. Déployez sur Tomcat.
  3. Accédez à http://localhost:8080/projet-crud/utilisateurs : vous serez redirigé vers /login.
  4. 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.
graph TD A[Construction WAR] --> B(Maven) B --> C[mvn clean package] B --> D[maven-war-plugin] C --> 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 :

  1. 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).
  2. 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.
  3. 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.
graph TD A[Déploiement] --> B(Tomcat) B --> C[Copier WAR] C --> D[webapps/projet-crud.war] B --> E[Démarrer] E --> F[http://localhost:8080/projet-crud] A --> G(Jetty) G --> H[Processus similaire]

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é).
graph TD A[Tests] --> B(Unitaires) A --> C[Intégration] B --> D[JUnit + Mockito] D --> E[UtilisateurService] C --> F[Spring Test + MockMvc] F --> G[UtilisateurControleur]

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 :

  1. Exécutez mvn clean package.
  2. Déployez target/projet-crud.war dans Tomcat.
  3. 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 }
graph TD A[Optimisation] --> B(Réduire Requêtes) A --> C[Cache] A --> D[Requêtes Efficaces] B --> E[Base Mémoire/Production] C --> F[Ehcache] D --> G[Index]

10.3. Ressources pour approfondir Spring MVC

Pour aller plus loin :

graph TD A[Ressources] --> B(Documentation) A --> C[Livres] A --> D[Tutoriels] A --> E[Projets Open-Source] B --> F[Spring.io] C --> G[Spring in Action] D --> H[Baeldung] E --> I[GitHub]

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.