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");