Introduction à Spring Web MVC

Spring Web MVC est le module Spring consacré au développement d’application Web et d’API Web. Le nom de ce module renvoie directement au modèle MVC (Modèle Vue Contrôleur). Le modèle MVC n’est pas réservé au développement Web et même, son application n’a pas vraiment de sens pour le développement d’API Web. Quoi qu’il en soit, les notions de modèle, de contrôleur et de vue sont centrales pour Spring Web MVC. Dans ce chapitre, nous rappellerons le principe du modèle MVC et nous verrons comment intégrer Spring Web MVC dans une application Spring Boot et dans une application sans Spring Boot.

Le modèle MVC

Le modèle MVC (Modèle Vue Contrôleur) est un modèle d’architecture pour guider la conception d’applications qui nécessitent une interaction de l’utilisateur avec le système. Il définit trois grandes catégories de responsabilité :

Le modèle

Les classes appartenant à cette catégorie définissent les données applicatives échangées entre l’utilisateur et le système.

La vue

Les classes appartenant à cette catégorie gèrent la représentation graphique des données et l’interface utilisateur

Le contrôleur

Les classes appartenant à cette catégorie gèrent les interactions de l’utilisateur et la mise à jour des vues après la modification des données. Les contrôleurs assurent la cohérence entre le modèle et la vue.

Dans la logique du modèle MVC, un utilisateur interagit avec un contrôleur. Pour une application Web, une interaction avec un serveur correspond à l’envoi d’une requête HTTP. Donc les requêtes doivent être prises en charge par des contrôleurs. Ce sont eux qui alimentent le modèle avec les objets qui seront nécessaires aux vues.

  • le modèle est constitué par un ensemble d’objets Java qui représentent les données envoyées au serveur ou les données à afficher à l’utilisateur dans une page Web.

  • la vue est constituée par des objets capables de générer des pages Web, de mettre en forme les données utilisateurs… Spring Web MVC nous permet d’utiliser différentes technologies pour la prise en charge des vues. Par exemple, nous pouvons utiliser des Java Server Pages (JSP) qui est la technologie disponible depuis J2EE. Pour nos exemples, nous utiliserons Thymeleaf qui est le moteur de génération de vue recommandé par le Spring Framework (mais absolument pas imposé). Pour le développeur, la création de vue consiste principalement à écrire des modèles de vues sous la forme de fichiers HTML.

  • le contrôleur est un composant chargé de valider les paramètres de la requête avant de les transmettre à la couche de service pour traitement. Une fois ce traitement terminé, c’est le contrôleur qui met à jour le modèle et le transmet à une vue. Nous verrons au prochain chapitre que Spring Web MVC fournit un jeu d’annotations particulier pour nous permettre de développer des contrôleurs.

Intégration de Spring Web MVC

Spring Web MVC est un module pour le développement d’application Web ou d’API Web. Donc, cela suppose le recours à un serveur pour traiter les requêtes. Le Spring Framework ne fournit pas de serveur Web. Pour exécuter une application Spring Web MVC nous avons donc besoin de déployer notre application dans un serveur.

Comme il a été dit dans l’introduction générale au Spring Framework, ce dernier a été conçu comme une approche différente en terme d’architecture par rapport à J2EE (puis Java EE et Jakarta EE). La différence fondamentale est que chaque application Spring embarque son propre conteneur avec les services dont elle a besoin. Donc une application Spring Web MVC n’a pas besoin d’un serveur d’application Java EE complet comme Wildfly, GlassFish ou TomEE. Elle peut s’exécuter dans un conteneur Web plus léger comme Tomcat ou Jetty qui offre le service minimal dont une application Spring Web MVC à besoin : le lancement d’un serveur HTTP et la possibilité de déléguer le traitement des requêtes au code de l’application.

Le choix d’utiliser ou non Spring Boot pour configurer votre application va être déterminant pour l’intégration de Spring Web MVC et le déploiement de votre application.

Intégration dans une application avec Spring Boot

Spring Boot est un projet conçu pour simplifier considérablement la configuration des applications basées sur le Spring Framework. On peut donc s’attendre à ce que l’intégration de Spring Web MVC se fasse facilement… ce qui est effectivement le cas.

Pour intégrer Spring Web MVC, vous devez rajouter une dépendance au module spring-boot-starter-web.

Déclaration de la dépendance dans le fichier pom.xml pour un projet Maven
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Il est fortement recommandé d’ajouter également une dépendance au module spring-boot-devtools. Ce dernier est très pratique pour la phase de développement. Il permet notamment le redémarrage à chaud du serveur lorsqu’on effectue une modification dans le code source.

Déclaration de la dépendance dans le fichier pom.xml pour un projet Maven
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
  <scope>runtime</scope>
  <optional>true</optional>
</dependency>

Lorsque la dépendance à spring-boot-starter-web est présente, le comportement de Spring Boot change au lancement de votre application. Il va automatiquement lancer un serveur HTTP (Tomcat par défaut) et déployer votre application dans ce serveur. Du point de vue de l’application, cela change radicalement le choix d’architecture. Plutôt que de disposer d’un serveur central Java EE dans lequel nous déployons nos applications, c’est chaque application qui dispose de son serveur embarqué.

Le serveur est configurable à travers les nombreux paramètres fournis par Spring Boot. Le plus utile pour démarrer est sans doute le paramètre server.port qui permet de donner le port d’écoute du serveur (8080 par défaut). Donc, si vous voulez que votre serveur écoute sur le port 9090, il suffit d’ajouter dans le fichier application.properties :

server.port = 9090

Astuce

Même si vous développez une application Spring Boot avec un serveur embarqué, il est très facile de la transformer en application Web Java EE prête à être déployée dans un serveur central.

Pour cela, vous devez générer une application War. Si vous utilisez Maven pour gérer votre projet, il vous suffit de changer ou d’ajouter la balise <packaging> dans votre fichier pom.xml pour indiquer un packaging de type war :

Configuration du projet Maven pour produire une application Web
<groupId>dev.gayerie</groupId>
<artifactId>monapplication</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

Vous devez également fournir dans votre code une classe qui hérite de la classe SpringBootServletInitializer et qui va lancer votre application :

Classe de lancement de votre application dans le serveur
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package dev.gayerie;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

public class MyWebApplication extends SpringBootServletInitializer {

  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(MyApplication.class);
  }

}

Dans l’exemple ci-dessus, la classe MyApplication référencée à la ligne 10 est tout simplement la classe principale de votre application Spring Boot, celle qui déclare la méthode main.

Pour que le déploiement fonctionne, il faut que le serveur supporte l’API Servlet version 3.0 ou plus (ce qui est le cas de tous les serveurs d’application Java EE récents).

Intégration dans une application sans Spring Boot

Une application Spring Web MVC suppose d’être déployée dans un conteneur Web Java EE. Tomcat ou Jetty peuvent suffire pour cela. Vous devez bien évidemment générer une application War. Si vous utilisez Maven pour gérer votre projet, il vous suffit de changer ou d’ajouter la balise <packaging> dans votre fichier pom.xml pour indiquer un packaging de type war :

Configuration du projet Maven pour produire une application Web
<groupId>dev.gayerie</groupId>
<artifactId>monapplication</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

Vous devez également déclarer une dépendance au module spring-mvc ainsi qu’à l’API Servlet :

Déclaration des dépendances dans le fichier pom.xml pour un projet Maven
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.0.1</version>
  <scope>provided</scope>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.3.1</version>
</dependency>

Vous allez avoir besoin d’une classe pour initialiser la Servlet de votre application et le contexte d’application Spring. Depuis la version 3.0 de l’API Servlet, il est possible de réaliser cette phase d’initialisation dans une classe Java implémentant l’interface WebApplicationInitializer.

La classe d’initialisation de la Servlet et du contexte Spring
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package dev.gayerie;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class MainWebAppInitializer implements WebApplicationInitializer {
  @Override
  public void onStartup(final ServletContext sc) throws ServletException {
    // Chargement du contexte d'application
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.register(WebAppConfiguration.class);

    // Création de la servlet
    DispatcherServlet servlet = new DispatcherServlet(context);
    ServletRegistration.Dynamic registration = sc.addServlet("app", servlet);
    registration.setLoadOnStartup(1);
    registration.addMapping("/");
  }
}

Les lignes 18 à 21 créent et initialisent une servlet qui est de type DispatcherServlet. Cette classe est fournie par Spring Web MVC. Notez qu’à la ligne 21, on configure la servlet pour répondre aux requêtes concernant les chemins à partir de /, c’est à dire toutes les requêtes concernant notre application.

Les lignes 15 et 16 créent un contexte d’application spécifique aux applications Web et enregistrent une classe WebAppConfiguration. Cette classe doit aussi être fournie par notre application et correspond au point d’entrée pour la configuration du contexte d’application.

La classe d’initialisation du contexte d’application
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package dev.gayerie;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@EnableWebMvc
@Configuration
@ComponentScan
public class WebAppConfiguration implements WebMvcConfigurer {
}

Cette classe est identifiée comme une classe de configuration Spring grâce à l’annotation @Configuration. L’annotation @EnableWebMvc sert (comme son nom l’indique explicitement) à activer le support de Spring Web MVC, comme par exemple, activer la prise en charge des classes de contrôleurs.

L’encodage des paramètres de requêtes

L’encodage des paramètres des requêtes est source d’erreur dans le développement d’applications Web. En effet, même si l’encodage le plus couramment utilisé actuellement est UTF-8, il ne faut pas oublier que l’encodage par défaut sur le Web est Latin-1 (ISO-8859-1). Selon les navigateurs (et notamment suivant les versions des navigateurs), il peut y avoir des comportements légèrement différents. Ce n’est pas parce qu’une page HTML est encodée en UTF-8 que le formulaire qu’elle contient soumettra nécessairement des données en UTF-8. Pour contrôler au mieux le comportement des navigateurs, il est conseillé d’utiliser systématiquement l’attribut accept-charset sur la balise <form>. Cet attribut permet justement de spécifier l’encodage à utiliser pour envoyer les données au serveur :

Utilisation de accept-charset pour un formulaire
<form action="..." method="post" accept-charset="utf-8">
  <input type="text" name="nom">
  <input type="submit">Envoyer</input>
</form>

Côté serveur, vous devez vous assurer que l’encodage utilisé pour traiter les paramètres des requêtes est correctement positionné.

Pour une application avec Spring Boot, l’encodage par défaut pour les paramètres des requêtes est UTF-8. Si vous voulez le modifier, il faut déclarer la propriété server.tomcat.uri-encoding dans le fichier application.properties :

Changement de l’encodage par défaut pour une application Spring Boot
server.tomcat.uri-encoding = iso-8859-1

Pour une application sans Spring Boot, l’encodage par défaut sera celui du serveur sur lequel vous allez déployer votre application. Pour un serveur Tomcat par exemple, l’encodage du serveur est Latin-1 (ISO-8859-1). Si vous voulez utiliser l’encodage UTF-8, plutôt que de modifier la configuration de votre serveur, vous pouvez ajouter un filtre de requêtes dans votre application pour positionner l’encodage à UTF-8 à l’arrivée de chaque nouvelle requête.

Pour cela, ajoutez la ligne de code suivante dans la classe d’initialisation de votre application :

Ajout d’un filtre pour le passage à UTF-8
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package dev.gayerie;

import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.DispatcherServlet;

public class MainWebAppInitializer implements WebApplicationInitializer {

  @Override
  public void onStartup(ServletContext sc) throws ServletException {
    // Chargement du contexte d'application
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.register(WebAppConfiguration.class);

    // Création de la servlet
    DispatcherServlet servlet = new DispatcherServlet(context);
    ServletRegistration.Dynamic registration = sc.addServlet("app", servlet);
    registration.setLoadOnStartup(1);
    registration.addMapping("/");

    // Ajout du filtre UTF-8 pour les paramètres des requêtes
    sc.addFilter("characterEncoding", new CharacterEncodingFilter("UTF-8"))
        .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");

  }
}