Spring 02 : Sur la route
Dans cette quête, tu vas découvrir comment exploiter des mécanismes avancés sur les routes avec Spring.
Tu vas notamment voir comment, à partir d'une même route, renvoyer différents résultats.
Géronimo !
Objectifs
- Gérer des urls variables grâce à Spring
- Adapter le statut HTTP retourné
- Renvoyer plus qu'une chaîne de caractères
Afficher un JSON
Le JSON (JavaScript Object Notation) est un format permettant de transcrire des données sous forme d'arborescence. Tu verras dans une quête à quoi sert le JSON, pour le moment sache que ça ressemble à ça :
{
"login": "defunkt",
"id": 2,
"followers": 20936,
"following": 210,
"created_at": "2007-10-20T05:24:19Z",
"updated_at": "2019-11-01T21:56:00Z"
}
Dans la quête précédente, tu as vu comment afficher du texte en passant par un contrôleur :
@GetMapping("/greetings")
@ResponseBody
public String index() {
return "Greetings from Spring Boot!";
}
Il t'est aussi possible d'afficher du JSON très facilement : Spring va convertir n'importe quel objet (et collection d'objets) en fichier JSON !
Par exemple, tu as la classe suivante :
class Person {
private String name;
private int age;
// constructor, getters and setters omitted for better readability
}
Tu créeras alors la route suivante dans un contrôleur :
@GetMapping("/person")
@ResponseBody
public Person personSample() {
return new Person("John Doe", 30);
}
Le type de retour de la méthode est bien un objet
Person, pas une chaîne de caractères !
Lors de l'appel de l'url http://localhost:8080/login, tu obtiendras l'affichage d'un fichier JSON suivant :
{
name: "John Doe",
age: 30
}
Note que l'annotation @ResponseBody est nécessaire pour exprimer que la route va afficher un contenu (et non charger une page web).
Appel d'une page statique
Dans la quête précédente, tu as découvert que déposer un fichier html dans le dossier des ressources statiques te permettait d'y accéder à partir du serveur web, comme par exemple une page index.html.
Imagine que tu aies créé une page sign-in.html, mais que tu aimerais y accéder de l'url suivante :
Il va te falloir créer la route correspondante dans un contrôleur :
@GetMapping("/login")
public String signIn() {
return "sign-in.html";
}
Tu as peut-être remarqué l'absence de l'annotation @ResponseBody : c'est normal, la route ainsi crée va afficher une vue (le fichier sign-in.html) et non renvoyer un contenu.
Si tu laisse l'annotation
@ResponseBody, tu verras juste le texte "sign-in.html" apparaître à l'écran !
Variables de chemin
Tu es sûrement tombé plusieurs fois sur le site StackOverflow. Le site contient des questions et des réponses sur des points de code, toutes taguées par langage. Tu peux voir les questions avec les tags Java, JavaScript ou CSS respectivement aux urls suivantes :
Ces trois liens pointent vers le domaine stackoverflow.com, et sur ce domaine vers le chemin /questions/tagged. Avec tes conaissances actuelles, tu pourrais écrire ces trois routes de la façon suivante :
@GetMapping("/questions/tagged/java")
@ResponseBody
List<Question> getQuestionWithTagJava() {
return Questions.getByTag("Java");
}
@GetMapping("/questions/tagged/javascript")
@ResponseBody
List<Question> getQuestionWithTagJavaScript() {
return Questions.getByTag("JavaScript");
}
@GetMapping("/questions/tagged/css")
@ResponseBody
List<Question> getQuestionWithTagCSS() {
return Questions.getByTag("CSS");
}
La liste de résultat varie en fonction du langage. Quelque chose qui varie, est-ce qu'on ne pourrait pas la gérer comme... une variable ?
Dans Spring, c'est possible : tu peux utiliser l'annotation @PathVariable. Plutôt que de déclarer 3 routes comme dans le code ci-dessus, tu peux écrire :
@GetMapping("/questions/tagged/{tag}")
@ResponseBody
List<Question> getQuestionWithTag(@PathVariable String tag) {
return Questions.getByTag(tag);
}
Dans le mapping, tu noteras la syntaxe {tag} : tu expliques ici à Spring que ton mapping s'applique pour toutes les urls qui commencent par /questions/tagged/ suivi d'une valeur pour le tag -- dans notre exemple java, javascript ou css. La méthode prend en paramètre une chaîne de caractères appelée tag : c'est l'annotation @PathVariable qui fait le lien entre les deux.
Note le rapport entre le nom de la variable dans la route et le nom de la variable en paramètre : il faut qu'il soit identique pour que le lien soit fait entre les deux.
Voyons un autre exemple, que tu ajouteras dans le contrôleur de ton choix d'un nouveau projet :
@GetMapping("/hello/{name}")
@ResponseBody
public String hello(@PathVariable String name) {
return "Hello " + name;
}
Une fois l'application lancée avec mvn spring-boot:run, ouvre l'url :
Amuse-toi à changer le nom dans l'url. Quand tu auras fait le tour des prénoms de ta promo, reviens lire la suite ;)
L'article fil rouge que tu as vu dans la première quête Spring contient un paragraphe sur @PathVariable
Paramètres de requêtes
Il existe un autre moyen de passer une donnée à une route. Tu as peut-être déjà entendu parler des paramètres d'url : ces options que tu trouves après un ?. Par exemple :
L'url t'affichera les questions avec le tag Java sur StackOverflow avec deux options :
-
sort=newestte permet d'afficher les questions les plus récentes en premier. -
pageSize=50limite le nombre de questions affichées à 50.
Trouve sur la page des questions Java les boutons "Frequent" et "Votes", et vois ce qu'il se passe dans l'url lorsque tu cliques dessus.
Tu peux récupérer un paramètre de requête avec l'annotation @RequestParam, qui préfixera le paramètre.
Ajoute le code suivant dans le contrôleur de ton projet :
@GetMapping("/movie")
@ResponseBody
public String showTitle(@RequestParam String title) {
return "The movie's title is " + title;
}
N'oublie pas d'ajouter l'import pour l'annotation
@RequestParam.
Relance l'application, et ouvre l'url suivante :
Si le titre ne s'affiche pas : as-tu pensé à relancer le serveur avec
mvn spring-boot:run?
Là encore, prends le temps de t'amuser avec différentes valeurs avant de passer à la suite :)
Note que tu peux rendre le paramètre optionnel avec @RequestParam(required = false). Une autre option est de fournir une valeur par défaut avec @RequestParam(defaultValue = ""). Tu devras alors gérer le fait que la valeur soit remplie ou non :
@GetMapping("/movie")
@ResponseBody
public String showTitle(@RequestParam String title, @RequestParam(defaultValue = "", required = false) String year) {
if (!year.isEmpty()) {
return "The movie's title is " + title + " (" + year + ")";
}
return "The movie's title is " + title;
}
Une bonne présentation de @RequestParam -- à la fin en bonus, une comparaison avec @PathVariable.
Variables ou paramètres ?
Tu as deux outils à ta disposition, dans quel cas les utiliser ?
Paramètres de requêtes :
-
lors du tri ou du filtrage de données (ex:
?sort=title&page=2) -
lors de l'envoie de formulaire (avec
@PostMapping)
Variables de chemin
-
le reste du temps !
-
notamment pour créer des API
Combiner les deux ?
C'est possible ! Par exemple, tu peux afficher les films d'une catégorie spécifique avec une variable de chemin et de trier les résultats par un attribut (réalisateur ou années par exemple) :
@GetMapping("/movie/{category}/")
@ResponseBody
public List<Movie> loadMovies(@PathVariable String category,
@RequestParam(defaultValue = "") String attribute) {
return Movies.getByCategory(category).sortBy(attribute):
}
Renvoie le statut HTTP adapté aux valeurs reçues
Maintenant que tu as vu ce que les variables et les paramètres peuvent faire, je peux te parler du deuxième effet Kiss cool : tu vas devoir gérer dans ton code que les valeurs reçues sont parfois incorrectes.
Si tu regardes comment est composée l'url d'une question sur StackOverflow, par exemple celle-ci :
le nom de domaine est toujours stackoverflow.com. Vient ensuite le chemin /questions suivi d'un identifiant et d'un titre. Le titre n'est là que pour "faire joli". Tu peux le vérifier en cliquant sur l'url suivante :
Tu arrives quand même à destination : ce qui compte, c'est l'identifiant, qui est une variable du chemin. Que se passe-t-il si tu passes un identifiant incorrect dans l'url ? Fais le test :
Tu arrives sur une page 404 : la page demandée n'existe pas. D'une manière ou d'une autre, le code côté serveur doit ressembler au pseudo-code suivant :
Si id = 0
lever une erreur 404
Fin Si
retourner question correspondante à id
Pour l'implémenter en Java avec Spring, tu peux t'appuyer -- depuis Spring 5 -- sur un type d'exception bien particulier : ResponseStatusException. Pour l'utiliser, tu dois préciser à la construction le statut HTTP que tu veux renvoyer -- et optionnellement un message et une exception source. Par exemple, pour notre question StackOverflow, cela pourrait donner :
@GetMapping("/questions/{id}")
Question getQuestion(@PathVariable int id) {
if (id == 0) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Question Not Found");
}
return Questions.getById(id);
}
Les différentes valeurs sont listées dans la doc officielle.
D'autres options sont décrites dans cet article.
Challenge
Doctor who ?

Le docteur veut créer son site web. Mais comme il est toujours pressé, il n'a pas lu le manuel de Spring : reprend son code pour lui montrer comme faire !
Pour le moment, ton site possède deux route :
/qui affiche une page d'accueil, avec des liens (qui, pour le moment, ne marchent pas)/doctor/qui affiche une page vide
Pour démarrer, fais unForkdu dépôt suivant puis clone-le en local.
Tu vas devoir modifier une route dans un contrôleur DoctorController, contenu dans le package controller :
- Avec l'aide de l'annotation
@PathVariable, modifie la route/doctor/pour accepter la variable{number}. - Pour le numéro 13, renvoie les informations sur l'incarnation du Docteur au format JSON, sous la forme
{"number": 13, "name": "Jodie Whittaker"}. Pour se faire, retourne une instance deDoctordont tu trouveras la classe dans le packagemodel. - Pour les autres numéros valides -- de 1 à 12 -- renvoie un statut 303.
- Si le numéro n'est pas valide (par exemple, le lien vers
/doctor/42), renvoie un statut 404 avec comme information une chaîne de caractères contenantImpossible de récupérer l'incarnation {number}." - Pousse le contenu de ton projet dans un dépôt GitHub et poste le lien de ton dépôt en guise de solution.
Critères de validation
- Les liens de la page d'accueil peuvent tous être testés
- Il n'y a qu'une seule route dans le contrôleur
- La route renvoie un JSON pour le numéro 13, un statut 303 pour les numéro 1 à 12, sinon elle renvoie un statut 404 avec un message adapté.
- Le code est disponible sur GitHub.
Ressources partagées sur cette quête
- pour mieux comprendre Spring @PathVariable Annotationhttps://www.baeldung.com/spring-pathvariable