Architecture interne de Spring MVC : du DispatcherServlet au rendu de vue

Point d'entrée : DispatcherServlet

Lorsqu'une requête HTTP parvient à l'application (par exemple GET /api/produits/42), le conteneur web (Tomcat, Jetty, etc.) la dirige vers le DispatcherServlet. Ce dernier hérite de HttpServlet et effectue tout son travail dans la méthode doDispatch(), appelée par doService().

Le rôle de doDispatch() consiste à orchestrer le pipeline complet : localiser le bon gestionnaire, l'exécuter, puis produire la réponse finale. Le DispatcherServlet ne contient aucune logique métier — il agit comme un chef d'orchestre qui coordonne plusieurs composants qu'il maintient sous forme de listes internes.

Localisation du gestionnaire : HandlerMapping

Une fois la requête reçue, le DispatcherServlet doit déterminer quel bloc de code doit la traiter. C'est la mission du HandlerMapping.

Au démarrage de l'application, le conteneur Spring scanne tous les beans et repère les méthodes annotées avec @RequestMapping ou ses dérivés (@GetMapping, @PostMapping, etc.). L'implémentation principale RequestMappingHandlerMapping construit alors une table de correspondance entre les motifs d'URL/méthodes HTTP et les objets HandlerMethod, puis la conserve en mémoire.

@RestController
@RequestMapping("/api/produits")
public class CatalogueController {

    private final ProduitService produitService;

    public CatalogueController(ProduitService produitService) {
        this.produitService = produitService;
    }

    @GetMapping("/{reference}")
    public ResponseEntity<ProduitDTO> recupererProduit(@PathVariable String reference) {
        ProduitDTO produit = produitService.trouverParReference(reference);
        return ResponseEntity.ok(produit);
    }
}

Quand une requête arrive, le DispatcherServlet parcourt les HandlerMapping enregistrés. Dès qu'un d'eux identifie une correspondance, il ne renvoie pas uniquement le HandlerMethod : il retourne un HandlerExecutionChain, un objet qui enveloppe à la fois le gestionnaire cible et la liste des HandlerInterceptor applicables. Cette encapsulation permet d'exécuter les intercepteurs avant et après l'appel de la méthode métier.

Préparation de l'exécution : HandlerAdapter

Le DispatcherServlet sait désormais quelle méthode invoquer, mais reste confronté à un problème : comment appeler de façon générique une méthode Java dont la signature est arbitraire ? Les paramètres peuvent inclure @PathVariable, @RequestBody, @RequestParam, et le type de retour peut varier (String, objet, ResponseEntity, etc.).

Spring résout cette difficulté via le pattern Adapter avec l'interface HandlerAdapter. Le DispatcherServlet interroge chaque HandlerAdapter enregistré pour savoir s'il supporte le type de handler trouvé. L'implémentation RequestMappingHandlerAdapter prend en charge les HandlerMethod.

RequestMappingHandlerAdapter est sans doute le composant le plus sophistiqué de Spring MVC. Son fonctionnement se décompose en deux phases :

  • Résolution des paramètres : il examine la signature de la méthode cible et, pour chaque paramètre, sollicite un HandlerMethodArgumentResolver adapté. Par exemple, un paramètre annoté @PathVariable déclenche PathVariableMethodArgumentResolver, qui extrait la valeur depuis l'URL, effectue la conversion de type, puis fournit l'argument prêt à l'emploi. Chaque annotation dispose de son propre résolveur.
  • Invocation par réflexion : une fois tous les paramètres assemblés, la méthode est invoquée via l'API Reflection de Java.

Exécution et résultat : ModelAndView

Après l'appel réflexif de la méthode du contrôleur, le HandlerAdapter récupère la valeur retournée et l'enveloppe dans un objet ModelAndView, qui combine :

  • Model : un Map contenant les données métier à transmettre à la vue.
  • View : soit un nom de vue logique (une simple chaîne), soit un objet View concret.

En interne, le HandlerAdapter s'appuie sur une collection de HandlerMethodReturnValueHandler pour traiter le retour selon sa nature. Un String est interprété comme un nom de vue logique. Si la méthode ou la classe porte l'annotation @ResponseBody (ou @RestController), le RequestResponseBodyMethodProcessor entre en jeu : il sérialise directement l'objet de retour via un HttpMessageConverter (par exemple Jackson pour produire du JSON) et l'écrit dans le corps de la réponse. Dans ce cas, aucune vue n'est résolue — l'étape suivante est court-circuitée.

Rendu : ViewResolver et View

Lorsque le DispatcherServlet reçoit un ModelAndView contenant un nom de vue logique comme "detail_produit", il doit transformer cette chaîne en une réponse concrète (HTML, etc.). Cette responsabilité incombe au ViewResolver.

Le DispatcherServlet itère sur les ViewResolver enregistrés. Par exemple, un InternalResourceViewResolver configuré avec le préfixe /WEB-INF/vues/ et le suffixe .jsp transformera "detail_produit" en chemin physique /WEB-INF/vues/detail_produit.jsp, puis instanciera un objet JstlView. Avec Thymeleaf, le ThymeleafViewResolver produira un ThymeleafView.

L'objet View obtenu expose une méthode render() qui fusionne les données du Model avec le template pour générer le contenu final. Le résultat est écrit dans le flux de sortie de la HttpServletResponse et renvoyé au navigateur. Le cycle requête-réponse s'achève ici.

Vue d'ensemble du flux


Requête HTTP
     │
     ▼
DispatcherServlet
     │
     ├─► (1) HandlerMapping ──► HandlerExecutionChain
     │                              (HandlerMethod + Interceptors)
     │                                    │
     ├─► (2) HandlerAdapter ── supporte ce handler ?
     │         │
     │         ├─ (3) Résolution des arguments
     │         ├─ (4) Invocation du Controller
     │         └─ (5) Construction du ModelAndView
     │                                    │
     ├─► (6) ViewResolver ──► View concrète
     │
     └─► (7) View.render() ──► Réponse HTTP

Spring Boot et l'auto-configuration de Spring MVC

En pratique, lorsque vous développez avec Spring Boot, vous ne configurez jamais manuellement ces composants. C'est l'auto-configuration qui s'en charge.

Spring Boot et Spring MVC entretiennent une relation de complémentarité : Spring MVC définit l'architecture et les contrats (DispatcherServlet, HandlerMapping, HandlerAdapter, etc.), tandis que Spring Boot assure l'assemblage et la simplification via la classe WebMvcAutoConfiguration.

Avec la dépendance spring-boot-starter-web, plusieurs mécanismes se mettent en place automatiquement :

  1. Suppression de web.xml : Spring Boot embarque un serveur web (Tomcat par défaut) et enregistre le DispatcherServlet de manière programmatique via l'API Servlet 3.0+ (ServletContainerInitializer), de façon totalement transparente.
  2. Instanciation des composants clés : WebMvcAutoConfiguration crée et configure les beans essentiels — RequestMappingHandlerMapping, RequestMappingHandlerAdapter (avec son arsenal de résolveurs d'arguments, de gestionnaires de retour et de convertisseurs comme MappingJackson2HttpMessageConverter), plusieurs ViewResolver (dont ContentNegotiatingViewResolver), ainsi que des HandlerExceptionResolver pour la gestion uniforme des erreurs.
  3. Convention sur configuration : les ressources statiques placées sous classpath:/static/ ou classpath:/public/ sont automatiquement servies. Un fichier index.html à la racine sert de page d'accueil. Toute personnalisation peut s'effectuer via les propriétés spring.mvc.* et server.* dans application.properties ou application.yml, sans nécessiter de classe de configuration Java explicite.

En résumé, Spring MVC spécifie ce qu'il faut faire et comment (les composants et leur séquence d'invocation), alors que Spring Boot détermine comment les assembler et comment simplifier leur configuration. Vous n'avez qu'à annoter vos contrôleurs avec @RestController et à implémenter votre logique métier — toute la machinerie Spring MVC est déjà câblée et opérationnelle en arrière-plan.

Étiquettes: Spring MVC DispatcherServlet HandlerMapping HandlerAdapter Spring Boot

Publié le 2 juillet à 08h43