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 :
- La requête HTTP entrante est capturée par le
DispatcherServlet. - Ce dernier interroge le
HandlerMappingpour identifier le contrôleur (Handler) correspondant à l'URL. - Le
HandlerMappingretourne la chaîne d'exécution auDispatcherServlet. - Le
DispatcherServletdélègue l'exécution auHandlerAdapter. - Le
HandlerAdapterinvoque la méthode spécifique du contrôleur. - Le contrôleur exécute la logique métier et retourne un objet
ModelAndViewà l'adaptateur. - Le
HandlerAdaptertransmet ceModelAndViewauDispatcherServlet. - Le
DispatcherServletsollicite leViewResolverpour traduire le nom de vue logique en vue physique (ex: JSP, Thymeleaf). - Le
ViewResolverrenvoie l'objetViewrésolu. - Le
DispatcherServletprocède au rendu de la vue en injectant les données du modèle dans le contexte de la requête. - 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>