Skip to main content

Spring MVC

Documentation

A l'origine un design pattern pour les applications avec interface graphique qui a été décliné pour le web.

  • Controller : reçoit les requêtes HTTP et construit une réponse pour le navigateur ou le client HTTP
  • View : La vue (HTML/JSON/XML ...) retournée suite à la requête
  • Model : les objets qui permettent de structurer la vue (par exemple un objet Article avec un titre, auteur, contenu, etc.)

On adopte ici un modèle par couche

modèle en couches

Pour créer un controller avec Spring il existe l'annotation @RestController à placer sur une classe Java. Cette annotation indique que cette classe va traiter des requêtes HTTP et accepter/renvoyer des données au format JSON.

Pour indiquer sur quelle URL va répondre notre controller, on utilise l'annotation @RequestMapping pour préciser le chemin. Cette annotation peut se mettre au niveau de la classe, ou au niveau de la méthode.

On doit ensuite annoter chaque méthode de la classe en indiquant à quel type de requête (GET, POST, etc.) la méthode doit répondre.

Dans cet exemple : on souhaite envoyer la chaine de caractère "Hello World !" quand on fait une requête GET vers le chemin /helloworld.

@RestController // classe qui répond à des requêtes HTTP
@RequestMapping("/helloworld") // sur le chemin /helloworld
public class HelloWorldController {

@GetMapping // cette méthode répondra aux requêtes HTTP GET
public String helloWorld() { // en revoyant une chaine
return "Hello World !";
}

}

TP2 - Création d'un controller

On souhaite ici créer une classe avec l'annotation @RestController qui répond sur l'URL /test et qui renvoie un document JSON avec un attribut titre et la valeur Hello World !.

La classe doit s'appeler IndexController, le nom de la méthode à l'intérieur de la classe est libre.

Pour tester votre application, lancez là et ouvrez votre navigateur à l'adresse :

http://localhost:8080/test

Evidemment, écrire manuellement des chaines JSON n'est pas une bonne pratique, on souhaite plutôt que les objets Java que notre application va manipuler soit automatiquement sérialisés dans le format JSON.

Quand une classe porte l'annotation @RestController, toute méthode qui renvoie un objet Java le verra automatiquement sérialisé en JSON dans la réponse HTTP.

@RestController
@RequestMapping("/article")
public class ArticleController {

@GetMapping
public Article article() {
Article article = new Article();
article.setTitre("Titre de mon article");
article.setContenu("Corps de l'article");
article.setAuteur("Edouard Pellerin");
return article;
}

}

TP3 - Création d'un controller qui renvoie un article en JSON

Dans votre projet Spring, créez une nouvelle classe ArticleController qui permet de recevoir un article au format JSON quand on appelle l'URL suivante :

http://localhost:8080/article

Votre classe article doit contenir les attributs suivants :

  • titre (String)
  • contenu (String)
  • auteur (String)
  • statut (Enum avec les valeurs BROUILLON, PUBLIE, ARCHIVE)
  • date de publication (LocalDateTime)

Pour lire les paramètres envoyés dans l'URL, on peut utiliser @RequestParam, par exemple pour l'URL :

http://localhost:8080/article/recherche?auteur=Edouard&contenu=test

@RestController
@RequestMapping("/article")
public class ArticleController {

@GetMapping("/recherche") // accessible sur le chemin /article/recherche
public Article rechercherArticle(
@RequestParam("auteur") String auteur, // la variable contient "Edouard"
@RequestParam String contenu) // la variable contient "test"
{
// faire une recherche de l'article
}
}

Il est également possible de récupérer un morceau de chemin variable. Par exemple, vous voulez récupérer un article selon son ID. Vous souhaitez une URL de la forme : /article/2 pour récupérer l'article avec l'ID 2.

@RestController
@RequestMapping("/article")
public class ArticleController {

@GetMapping("/{id}")
public Article getById(@PathVariable("id") Integer id)
{
// si on appelle /article/2, la variable id contiendra l'entier 2
}
}

Pour revenir au principe MVC de départ, dans l'exemple suivant :

  • Le controller est la classe ArticleController et sa méthode article()
  • Le modèle est l'article (variable article1)
  • La vue est le JSON créé automatiquement par Spring
@RestController
@RequestMapping("/article")
public class ArticleController {

@GetMapping
public Article article() {
Article article1 = new Article();
article1.setTitre("Titre de mon article");
article1.setContenu("Corps de l'article");
article1.setAuteur("Edouard Pellerin");
return article1;
}

}

Spring MVC

Jusqu'ici, nous avons vu comment récupérer des informations depuis l'application. Mais comment faire pour envoyer des informations ? par exemple, si mon application Angular propose un formulaire à soumettre, comment récupérer ce qu'a tapé l'utilisateur dans Spring ?

Nous allons voir ici comment traiter les requêtes qui permettent d'envoyer des données : POST, PUT, PATCH

Par formalisme, POST sert à créer de nouvelles entités (création d'un nouvel article), PUT permet de mettre à jour une entité existante en reprécisant l'intégralité des champs (mise à jour d'un article en renvoyant le titre, le contenu, l'auteur etc. peu importe ce qui a changé), PATCH permet de mettre à jour une entité en envoyant uniquement les champs qui ont changé (envoi du titre uniquement).

Techniquement, tout peut être fait avec POST.

Pour lire un objet JSON envoyé dans le corps d'une requête POST/PUT, utiliser @RequestBody dans la signature des paramètres de la méthode pour que Spring crée un objet Java correspondant au contenu JSON :

@RestController
@RequestMapping("/article")
public class ArticleController {

@PostMapping
public Article creerArticle(@RequestBody Article article) {
// sauvegarder le nouvel article en base
}
}

Gestions des logs avec Spring Boot (et Logback)

Il est important pour une application de lister dans un fichier ou dans la console les différentes opérations effectuées, pour qu'on puisse comprendre ce qui est exécuté. Avec Spring Boot, pour obtenir un logger dans n'importe quelle classe :

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// ...

@RestController
@RequestMapping("/")
public class ArticleController {

private Logger logger = LoggerFactory.getLogger(ArticleController.class);

@PostMapping
public Article creerArticle(@RequestBody Article article) {
if(article.getTitre() == null) {
logger.error("L'article envoyé n'a pas de titre");
return null;
}
logger.info("Titre {} et auteur {} de mon article", article.getTitre(), article.getAuteur());
return article;
}
}

TP4 - Développer deux web service et les tester

  1. Téléchargez le logiciel insomnia : https://insomnia.rest/download
  2. Créez une classe avec l'annotation @RestController et deux méthodes, une méthode @GetMapping et une méthode @PostMapping
  3. Créez une classe Article avec un champ Auteur, un champ Date de publication et une liste de Labels
  4. L'appel à la méthode GET doit vous retournez un Article au format JSON dans Insomnia. L'article est à créer directement dans le code de la méthode avec les données que vous voulez.
  5. L'appel à la méthode POST avec un Article au format JSON doit vous permettre d'afficher dans la console le titre et l'auteur.

Il peut être utile de contrôler précisément la réponse envoyée par votre controller, notamment le code de statut HTTP.

Plutôt que de renvoyer diretement un objet, Spring propose une classe "wrapper" qui permet aussi de contrôler le reste de la réponse :ResponseEntity<T>.

Par exemple, sur une recherche d'un article sur un ID qui n'existe pas :

// dans une classe de controller
public ResponseEntity<Article> getArticleById(@PathVariable Integer id) {
// chercher l'article en base
Article article = articleRepository.findById(id);
if(article == null) {
return ResponseEntity.notFound().build(); // renvoie une réponse HTTP 404
} else {
return ResponseEntity.ok(article); // renvoie 200 et l'article en JSON
}
}

La classe ResponseEntity permet également de préciser les entête HTTP, le corps de la réponse, le statut ...

ResponseEntity.status(HttpStatus.FORBIDDEN)
.header("X-entete-custom", "valeur")
.body("erreur");