Analyse approfondie des annotations @RestController et @Controller dans Spring MVC

Distinction entre @Controller et @RestController

Dans l'écosystème Spring Boot, la gestion des points de terminaison (endpoints) repose principalement sur deux annotations : @Controller et @RestController. Bien qu'elles servent toutes deux à déclarer des composants traitant les requêtes HTTP, leur comportement par défaut en matière de réponse diffère sensiblement.

Points communs

Les deux annotations identifient une classe comme étant un Bean Spring capable de recevoir des requêtes web. Elles sont détectées lors du scan de composants et enregistrées dans le contexte d'application en tant que gestionnaires de requêtes.

Différences fondamentales

  • @Controller : Il s'agit de l'annotation traditionnelle de Spring MVC. Elle est conçue pour renvoyer des vues (fichiers HTML, JSP, FreeMarker). Pour retourner des données sérialisées (JSON/XML) depuis une méthode, il est nécessaire d'ajouter explicitement l'annotation @ResponseBody.
  • @RestController : Introduite avec Spring 4.0, cette annotation est une "annotation composée" qui regroupe @Controller et @ResponseBody. Chaque méthode de la classe est traitée comme si elle portait l'annotation @ResponseBody, ce qui signifie que la valeur de retour est directement écrite dans le corps de la réponse HTTP.

Si vous souhaitez renvoyer une vue depuis un @RestController, vous devez utiliser l'objet ModelAndView, car un simple retour de chaîne de caractères serait interprété comme du texte brut et non comme un nom de vue.

@RestController
@RequestMapping("/api/v1")
public class AppAccountController {

    @GetMapping("/dashboard")
    public ModelAndView showDashboard() {
        // Redirection vers une vue même dans un RestController
        ModelAndView viewContainer = new ModelAndView("dashboard-page");
        return viewContainer;
    }

    @GetMapping("/status")
    public String checkStatus() {
        // Retourne directement la chaîne "UP" dans le corps de la réponse
        return "UP";
    }
}

Analyse de la structure des annotations

L'examen du code source de Spring révèle la hiérarchie de ces annotations. Voici comment @RestController hérite des propriétés de @Controller :

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    /**
     * Nom du bean suggéré.
     */
    String value() default "";
}

Cette structure garantit que toute classe annotée avec @RestController bénéficie automatiquement de la sémantique de @ResponseBody.

Mécanisme interne de traitement de @ResponseBody

Le traitement des réponses dans Spring MVC est orchestré par le HandlerAdapter, plus précisément la classe RequestMappingHandlerAdapter. Une fois que la méthode du contrôleur a été invoquée, Spring doit décider comment transformer l'objet retourné en une réponse HTTP valide.

Dans la méthode invokeAndHandle de ServletInvocableHandlerMethod, Spring injecte une liste de processeurs de valeurs de retour (HandlerMethodReturnValueHandler) :

// Extrait simplifié du processus d'invocation
ServletInvocableHandlerMethod methodExecutor = createInvocableHandlerMethod(handlerMethod);
methodExecutor.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
methodExecutor.invokeAndHandle(webRequest, mavContainer);

Initialisasion des processeurs

Au démarrage de l'application, RequestMappingHandlerAdapter initialise par défaut une collection de gestionnaires. C'est ici que RequestResponseBodyMethodProcessor est enregistré. C'est ce processeur spécifique qui est responsable de la gestion de l'annotation @ResponseBody.

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
    
    // Processeurs pour ModelAndView, View, etc.
    handlers.add(new ModelAndViewMethodReturnValueHandler());
    
    // Le processeur clé pour @ResponseBody et @RestController
    handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
          this.contentNegotiationManager, this.requestResponseBodyAdvice));
    
    // Autres processeurs...
    return handlers;
}

Sélection et exécution du processeur

Lorsqu'une requête est terminée, Spring parcourt la liste des gestionnaires disponibles et appelle la méthode supportsReturnType pour identifier celui qui convient. Pour RequestResponseBodyMethodProcessor, la logique de sélection est la suivante :

@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
          returnType.hasMethodAnnotation(ResponseBody.class));
}

Comme @RestController contient l'annotation @ResponseBody au niveau de la classe (getContainingClass), ce processeur est systématiquement sélectionné. Il utilise ensuite les convertisseurs de messages (comme Jackson pour JSON) pour transformer l'objet Java en flux binaire envoyé au cleint.

Étiquettes: spring-mvc spring-boot Java Annotations RestController

Publié le 9 juillet à 22h53