Guide Complet de Spring MVC : Architecture, Flux de Requêtes et Implémentations Avancées

Architecture et Flux de Traitement

Spring MVC est un module intrinsèque du framework Spring, conçu selon le modèle Modèle-Vue-Contrôleur (MVC) pour orchestrer les interatcions entre le front-end et le back-end. Grâce à son intégration native, aucune couche intermédiaire n'est nécessaire pour le lier au conteneur IoC de Spring.

Le Cycle de Vie d'une Requête

Le traitement d'une requête HTTP dans Spring MVC suit un pipeline précis orchestré par le contrôleur frontal :

  1. La requête HTTP entrante est capturée par le DispatcherServlet.
  2. Ce dernier interroge le HandlerMapping pour identifier le contrôleur (Handler) correspondant à l'URL.
  3. Le HandlerMapping retourne la chaîne d'exécution au DispatcherServlet.
  4. Le DispatcherServlet délègue l'exécution au HandlerAdapter.
  5. Le HandlerAdapter invoque la méthode spécifique du contrôleur.
  6. Le contrôleur exécute la logique métier et retourne un objet ModelAndView à l'adaptateur.
  7. Le HandlerAdapter transmet ce ModelAndView au DispatcherServlet.
  8. Le DispatcherServlet sollicite le ViewResolver pour traduire le nom de vue logique en vue physique (ex: JSP, Thymeleaf).
  9. Le ViewResolver renvoie l'objet View résolu.
  10. Le DispatcherServlet procède au rendu de la vue en injectant les données du modèle dans le contexte de la requête.
  11. La réponse HTTP finale est envoyée au client.

Configuration Initiale du Projet

Dépendances Maven

Intégrez les modules essentiels de Spring dans votre fichier pom.xml. Nous utilisons ici une version récente de la branche 5.x :

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.27</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.27</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.3.27</version>
    </dependency>
</dependencies>

Configuration du Descripteur de Déploiement (web.xml)

Le DispatcherServlet doit être déclaré pour intercepter les requêtes entrantes. Ici, nous interceptons tous les appels sous le préfixe /api/.

<servlet>
    <servlet-name>frontController</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>frontController</servlet-name>
    <url-pattern>/api/*</url-pattern>
</servlet-mapping>

Configuration du Contexte Spring (spring-mvc-config.xml)

Ce fichier définit le résolveur de vues, active l'annotation-driven et configure le scan des composants.

<!-- Résolveur de vues pour les templates JSP -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<!-- Scan des contrôleurs -->
<context:component-scan base-package="com.enterprise.billing.controller"/>

<!-- Activation des annotations MVC -->
<mvc:annotation-driven/>

Mapping des Requêtes et Annotations

L'annotation @RequestMapping permet de router les URL vers des méthodes spécifiques. Elle peut être appliquée au niveau de la classe (préfixe) et de la méthode (chemin spécifique).

Attributs de @RequestMapping

  • value / path : Définit l'URI cible.
  • method : Restreint le verbe HTTP (GET, POST, PUT, etc.).
  • params : Filtre les requêtes en fonction de la présence ou de la valeur de paramètres spécifiques.
@Controller
@RequestMapping("/invoices")
public class InvoiceController {

    @RequestMapping(value = "/generate", method = RequestMethod.POST, params = "priority")
    public String generateInvoice() {
        // Logique de génération
        return "invoice-success";
    }
}

Liaison des Paramètres de Requête

Paramètres Primitifs

Spring MVC lie automatiquement les paramètres de la requête aux arguments de la méthode si les noms correspondent.

<!-- Front-end -->
<a href="/api/invoices/details?invoiceId=INV-992">Voir Détails</a>
// Back-end
@RequestMapping("/details")
public String getInvoiceDetails(String invoiceId, Model model) {
    model.addAttribute("id", invoiceId);
    return "invoice-details";
}

Objets JavaBean (POJO)

Les paramètres complexes sont automatiquement mappés aux propriétés d'un objet Java.

<!-- Front-end -->
<form action="/api/invoices/create" method="POST">
    Client: <input type="text" name="clientName" />
    Montant: <input type="number" name="billingDetails.amount" />
    <input type="submit" value="Facturer"/>
</form>
// Back-end
@RequestMapping("/create")
public String createInvoice(Invoice invoice) {
    // L'objet invoice et ses propriétés imbriquées sont peuplés automatiquement
    return "redirect:/invoices/list";
}

Annotations Spécifiques de Paramétrage

@RequestParam

Permet de lier un paramètre de requête à un arguement de méthode lorsque les noms diffèrent.

@RequestMapping("/search")
public String searchInvoice(@RequestParam("ref") String referenceCode) {
    // Traite referenceCode
    return "search-result";
}

@RequestBody

Utilisé pour extraire le corps de la requête HTTP (généralement du JSON) et le désérialiser dans un objet Java. Nécessite une méthode POST ou PUT.

@RequestMapping(value = "/update", method = RequestMethod.POST)
public String updateInvoice(@RequestBody InvoicePayload payload) {
    // Traite le corps JSON
    return "success";
}

@PathVariable

Extrait les variables définies directement dans le template de l'URI.

<!-- Front-end -->
<a href="/api/invoices/view/INV-102">Voir</a>
// Back-end
@RequestMapping("/view/{code}")
public String viewInvoice(@PathVariable("code") String invoiceCode) {
    // Utilise invoiceCode
    return "invoice-view";
}

Gestion des Réponses et Retours

Retour de type String

La chaîne retournée est interprétée comme un nom de vue logique par le ViewResolver.

Retour de type void

Le développeur prend le contrôle total de la réponse via les objets HttpServletRequest et HttpServletResponse.

@RequestMapping("/export")
public void exportData(HttpServletResponse response) throws IOException {
    response.setContentType("application/pdf");
    response.setHeader("Content-Disposition", "attachment; filename=report.pdf");
    
    // Écriture directe dans le flux de sortie
    response.getOutputStream().write(generatePdfBytes());
}

Objet ModelAndView

Permet de retourner simultanément les données du modèle et le nom de la vue.

@RequestMapping("/dashboard")
public ModelAndView loadDashboard() {
    ModelAndView mv = new ModelAndView();
    mv.addObject("stats", dashboardService.getStats());
    mv.setViewName("dashboard-view");
    return mv;
}

Redirection et Forwarding

Utilisation des préfixes forward: et redirect: pour contourner le résolveur de vues standard.

@RequestMapping("/migrate")
public String migrateData() {
    // Redirection côté client (nouvelle requête)
    return "redirect:/api/dashboard"; 
}

Communication Asynchrone (JSON / AJAX)

Pour les échanges de données JSON, la bibliothèque Jackson est requise :

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>
// Requête AJAX côté client
$.ajax({
    url: "/api/invoices/calculate",
    type: "POST",
    contentType: "application/json",
    data: JSON.stringify({ items: [{id: 1, qty: 2}] }),
    success: function(response) {
        console.log("Total: " + response.totalAmount);
    }
});
// Contrôleurs Spring
@ResponseBody
@RequestMapping("/calculate")
public CalculationResult calculateTotal(@RequestBody CartRequest cart) {
    return billingService.computeTotal(cart);
}

Téléchargement de Fichiers

Upload Local

<form action="/api/documents/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="attachment" />
    <button type="submit">Envoyer</button>
</form>
@RequestMapping("/upload")
public String handleLocalUpload(@RequestParam("attachment") MultipartFile file, HttpServletRequest request) throws IOException {
    String uploadDir = request.getServletContext().getRealPath("/storage/");
    File dir = new File(uploadDir);
    if (!dir.exists()) dir.mkdirs();

    String uniqueName = UUID.randomUUID() + "_" + file.getOriginalFilename();
    file.transferTo(new File(dir, uniqueName));
    
    return "redirect:/documents";
}

Upload vers un Serveur Distant

Utilisation de RestTemplate pour transférer le fichier vers un microservice de stockage.

@RequestMapping("/remote-upload")
public String handleRemoteUpload(@RequestParam("attachment") MultipartFile file) {
    String remoteApi = "http://storage-service:8080/api/files";
    
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);
    
    MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
    body.add("fileResource", file.getResource());
    
    HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
    
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.postForObject(remoteApi, requestEntity, String.class);
    
    return "redirect:/documents";
}

Gestion Centralisée des Exceptions

Spring MVC permet de capturer les exceptions via l'interface HandlerExceptionResolver.

// Exception métier personnalisée
public class BillingException extends Exception {
    public BillingException(String msg) { super(msg); }
}
// Résolveur global
public class GlobalExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView("error-template");
        if (ex instanceof BillingException) {
            mv.addObject("errorDetail", ex.getMessage());
        } else {
            mv.addObject("errorDetail", "Erreur système critique.");
        }
        return mv;
    }
}
<!-- Enregistrement dans spring-mvc-config.xml -->
<bean id="globalExceptionHandler" class="com.enterprise.billing.exception.GlobalExceptionHandler" />

Intercepteurs de Requêtes

Contrairement aux filtres Servlet qui interceptent tout le trafic, les intercepteurs Spring MVC ciblent spécifiquement les méthodes des contrôleurs.

Implémentation d'un Intercepteur

public class SecurityAuditInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = request.getHeader("X-Auth-Token");
        if (token == null || token.isEmpty()) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false; // Bloque la requête
        }
        return true; // Autorise la requête
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        // Exécuté après le contrôleur, avant le rendu de la vue
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // Exécuté après le rendu complet de la vue
    }
}

Configuration de l'Intercepteur

<mvc:interceptors>
    <mvc:interceptor>
        <!-- Appliquer à tous les endpoints sous /api/invoices -->
        <mvc:mapping path="/api/invoices/**"/>
        <!-- Exclure les endpoints publics -->
        <mvc:exclude-mapping path="/api/invoices/public/**"/>
        
        <bean class="com.enterprise.billing.interceptor.SecurityAuditInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

Étiquettes: spring-mvc Java dispatcher-servlet request-mapping handler-interceptor

Publié le 23 août à 22h09