Découvrez Spring Initializr
Avez-vous déjà commandé une salade à composer au restaurant ? Eh bien, Spring Initializr fait plus ou moins la même chose : il vous permet de composer votre application selon vos besoins.
Pour débuter, rendez-vous sur https://start.spring.io :
Vous pourrez sélectionner :
le gestionnaire de projet (Maven ou Gradle) ;
le langage Java (Kotlin ou Groovy) ;
et la version de SpringBoot.
Et vous pourrez initialiser :
les métadonnées de notre projet ;
le packaging et la version de Java ;
et surtout, les dépendances grâce au bouton “Add dependencies” en haut à droite.
Nous allons, dans les prochains chapitres, développer un mini-système d'e-commerce fondé sur l'architecture Microservices avec Spring Boot. Nous allons commencer par un premier microservice qui gère les produits que nous allons proposer à la vente. Il doit pouvoir ajouter, supprimer, mettre à jour et afficher les produits.
Dans une première étape, nous allons créer une version très simplifiée de ce microservice. Nous enrichirons ce microservice au fur et à mesure de notre découverte des différents concepts à assimiler.
Créez et importez à partir de Spring Initializr
Retournez sur Spring Initializr et renseignez les Metadata du projet comme suit :
Group :
com.ecommerce
Artifact :
microcommerce
Name :
microcommerce
Packaging :
jar
Java Version :
11
Voici le formulaire renseigné avec ces informations :
Ensuite, suivez les étapes suivantes :
Sélectionnez en bas le starter Web :
Cliquez sur "Generate Project" et téléchargez l'application générée.
Procédez à l'extraction de l'application téléchargée. Si vous avez utilisé les mêmes noms que moi, elle devrait s'appeler microcommerce.zip.
Une fois sur la page d'accueil d'IntelliJ, cliquez sur Fichier puis Importer un projet depuis des sources existantes.
Sélectionnez le dossier de l'application microcommerce, puis sélectionnez le pom.xml :
Analysez le code obtenu
Si tout s'est déroulé correctement, vous devriez avoir cette arborescence :
Étudions maintenant en détail les différents éléments de cette arborescence :
pom.xml
Comme nous l'avons vu dans le chapitre précédent, ce pom.xml hérite du parent spring-boot-starter-parent qui nous permet de ne plus nous soucier des versions des dépendances ni de leur compatibilité :
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Rappelez-vous : vous avez la possibilité de choisir la version de Java à utiliser. Comme nous avons sélectionné Java 11 dans l'interface de Spring Initializr, une balise XML <java.version> a été ajoutée dans la partie Properties du fichier pom.xml, comme nous pouvons le voir dans le code qui suit :
<properties>
<java.version>11</java.version>
</properties>
Le pom.xml définit ensuite une dépendance vers le starter qui va ajouter à notre microservice toutes les dépendances de base nécessaires pour démarrer rapidement :
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Pour voir la liste des dépendances importées, rendez-vous à gauche dans "External Libraries" :
Vous avez dans cette liste principalement :
Jackson : permet de parser JSON et faire le lien entre les classes Java et le contenu JSON.
Tomcat : intégré, va nous permettre de lancer notre application en exécutant tout simplement le JAR sans avoir à le déployer dans un serveur d'application.
Hibernate : facilite la gestion des données.
Logging : remonte ce qui se passe dans l'application grâce à logback et autres.
MicrocommerceApplication.java
Cette classe, générée automatiquement par Spring Boot, est le point de démarrage de l'application :
package com.ecommerce.micrommerce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MicrommerceApplication {
public static void main(String[] args) {
SpringApplication.run(MicrommerceApplication.class, args);
}
}
Elle lance, entre autres, la classe SpringApplication, responsable du démarrage de l'application Spring. Cette classe va créer le fameux ApplicationContext dans lequel iront toutes les configurations générées automatiquement ou ajoutées par vos soins.
Mais le plus important ici, c'est bien sûr l'annotation @SpringBootApplication, qui est une simple encapsulation de trois annotations :
@Configuration : donne à la classe actuelle la possibilité de définir des configurations qui iront remplacer les traditionnels fichiers XML. Ces configurations se font via des Beans.
@EnableAutoConfiguration : l'annotation vue précédemment qui permet, au démarrage de Spring, de générer automatiquement les configurations nécessaires en fonction des dépendances situées dans notre classpath.
@ComponentScan : indique qu'il faut scanner les classes de ce package afin de trouver des Beans de configuration.
Nous reviendrons sur la configuration dans une prochaine section. Si vous souhaitez personnaliser finement le comportement de Spring Boot, il vous suffit de remplacer @SpringBootApplication par ces 3 annotations :
...
...
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class MicrocommerceApplication {
...
...
}
Cette modification permet notamment de paramétrer l'annotation @ComponentScan, par exemple pour cibler des fichiers à scanner.
application.properties
Ce fichier va vous permettre de modifier très simplement un nombre impressionnant de configurations liées à Spring Boot et à ses dépendances. Par exemple : changer le port d'écoute de Tomcat, l'emplacement des fichiers de log, les paramètres d'envoi d'e-mails, etc. Je vous invite à jeter un œil sur la liste complète ici. Nous reviendrons sur ce fichier dans une prochaine section.
MicrocommerceApplicationTests.java
Ce fichier vous permet d'écrire vos tests.
Exécutez l'application
Nous n'avons rien ajouté dans notre application pour l'instant, mais nous pouvons déjà l'exécuter. Si vous n'avez pas le panneau Maven à droite, rendez-vous en bas à gauche d'IntelliJ pour l'activer :
Double-cliquez ensuite sur "Install" sous "LifeCycle" de Maven dans le panneau de droite :
L'application sera compilée, et vous retrouverez le JAR sous le nouveau dossier "Target" créé pour l'occasion par Maven :
Exécutez enfin l'application depuis un terminal comme n'importe quel JAR, grâce à la commande :
java -jar Chemin/vers/microcommerce/target/microcommerce-0.0.1-SNAPSHOT.jar
Vous devriez alors avoir un retour de ce type :
Dans les dernières lignes, vous remarquerez cette phrase : " Tomcat started on port(s): 8080 (http)". Ce qui vous indique que votre application tourne, et qu'elle est en écoute grâce à Tomcat sur le port 8080.
Eh bien, rendez-vous dans votre navigateur à l'adresse http://localhost:8080. Vous obtenez alors cette magnifique erreur, car nous n'avons pas encore fourni d'éléments à afficher !
L'application se lance, et vous pouvez avoir le retour dans la console intégrée en bas. Cette opération est à faire uniquement la première fois. Pour les fois suivantes, il suffira d'appuyer sur le bouton Play en haut à droite pour la démarrer, et l'arrêter avec le bouton rouge :
Vous pouvez en profiter pour essayer la personnalisation de l'autoconfiguration de Spring Boot via application.properties. Vous pouvez ainsi changer un nombre impressionnant de paramètres grâce à une simple ligne dans le fichier application.properties.
Changeons par exemple le port du serveur. Ajoutez tout simplement cette ligne :
Exécutez maintenant l'application. Spring vous indique alors que l'application tourne désormais sur le port 9090 :
Vous pouvez le vérifier en vous rendant également à l'URL http://localhost:9090/Produits.
Créez l'API REST
Nous arrivons maintenant au cœur du microservice que nous voulons développer. Ce microservice va devoir être RESTful, et donc pouvoir communiquer de cette manière.
Définissez les besoins
Nous avons besoin d'un microservice capable de gérer les produits. Pour cela, il doit pouvoir exposer une API REST qui propose toutes les opérations CRUD (Create, Read, Update, Delete).
Nous allons donc avoir :
une classe Produit qui représente les caractéristiques d'un produit (nom, prix, etc.) ;
un contrôleur qui s'occupera de répondre aux requêtes CRUD et de faire les opérations nécessaires.
Nous voulons donc pouvoir appeler notre microservice sur les URL suivantes :
Requête GET à /Produits : affiche la liste de tous les produits.
Requête GET à /Produits/{id} : affiche un produit par son Id.
Requête PUT à /Produits/{id} : met à jour un produit par son Id.
Requête POST à /Produits : ajoute un produit.
Requête DELETE à /Produits/{id} : supprime un produit par son Id.
Passons au code !
Créez le contrôleur REST
Nous allons créer un contrôleur et le placer dans un package "controller", lui-même situé dans un package "web". Pour ce faire, faites un clic droit sur le package principal, puis New -> Java Class :
Pour créer les packages nécessaires et placer la classe directement à l'intérieur, nous allons utiliser une notation raccourcie fournie par IntelliJ. Dans la boîte de dialogue, écrivez web.controller.ProductController, comme illustré dans la figure suivante :
Lorsque vous cliquez sur OK, IntelliJ crée un package web, puis crée à l'intérieur de celui-ci un package controller. Enfin, la classe ProductController est créée à l'intérieur de ce dernier package. Vous obtenez l'arborescence illustrée ci-après :
Nous allons commencer par indiquer à Spring que ce contrôleur est un contrôleur REST. Saisissez le code suivant dans la classe ProductController :
package com.ecommerce.microcommerce.web.controller;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
}
Vous connaissez sans doute l'annotation @Controller de Spring qui permet de désigner une classe comme contrôleur, lui conférant la capacité de traiter les requêtes de type GET, POST, etc. Vous ajoutez ensuite @ResponseBody aux méthodes qui devront répondre directement sans passer par une vue.
@RestController est simplement la combinaison des deux annotations précédentes. Une fois ajouté, il indique que cette classe va pouvoir traiter les requêtes que nous allons définir. Il indique aussi que chaque méthode va renvoyer directement la réponse JSON à l'utilisateur, donc pas de vue dans le circuit.
Méthode pour GET /Produits
Commençons par créer une méthode listeProduits, très simple, qui retourne une string. Comme nous n'avons pas encore de produits, on retourne une simple phrase pour tester.
package com.ecommerce.micrommerce.web.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
@GetMapping("/Produits")
public String listeProduits() {
return "Un exemple de produit";
}
}
Dans les versions plus anciennes de Spring, nous aurions utilisé @RequestMapping(value = "/Produits", method = RequestMethod.GET)
.
@RequestMapping prend deux paramètres :
value qui sert à définir l’URL sur laquelle on peut atteindre la méthode.
Et method qui définit le verbe HTTP pour interroger l’URL.
De nouvelles annotations sont dorénavant disponibles, telles que @GetMapping, @PostMapping, @PutMapping, @DeleteMapping qui permettent de ne spécifier que l’URL, tout en utilisant le verbe HTTP lié, présent juste avant le mapping.
Dans ce code, c'est l'annotation @GetMapping qui permet de faire le lien entre l'URI "/Produits", invoquée via GET, et la méthode listeProduits.
Cette annotation accepte plusieurs paramètres, dont voici les principaux :
value : C'est ici que vous indiquez l'URI à laquelle cette méthode doit répondre. Vous pouvez également indiquer des paramètres, nous y reviendrons.
produces : Dans certains cas d'utilisations avancées, vous aurez besoin de préciser, par exemple, que votre méthode est capable de répondre en XML et en JSON. Cela entre aussi dans le choix de la méthode qui correspond le mieux à la requête. Si la requête contient du XML et que vous avez 2 méthodes identiques, dont une capable de produire du XML, c'est celle-ci qui sera appelée. Il en va de même pour consumes qui précise les formats acceptés. Dans la plupart des cas, vous n'avez pas besoin de renseigner ces paramètres.
Très bien! Il ne reste plus qu'à lancer l'application et à se rendre à http://localhost:9090/Produits.
Méthode pour GET /Produits/{id}
Créons maintenant une autre méthode capable d'accepter un Id de produit en paramètre :
@GetMapping("/Produits/{id}")
public String afficherUnProduit(@PathVariable int id) {
return "Vous avez demandé un produit avec l'id " + id;
}
La première différence dans cette méthode est l'ajout de {id} à l'URI. Cette notation permet d'indiquer que cette méthode doit répondre uniquement aux requêtes avec une URI de type /Produits/25, par exemple. Comme nous avons indiqué que Id doit être un int (dans @PathVariable int id), vous pouvez vous amuser à passer une chaîne de caractères à la place, vous verrez que Spring vous renverra une erreur.
Tout cela est très bien, mais nous aimerions bien renvoyer une liste de vrais produits au format JSON, et non plus seulement des phrases inutiles. C'est ce que nous allons voir dans la prochaine section.
Renvoyez une réponse JSON
Nous allons commencer par créer une classe qui représente un produit. Cette classe est souvent appelée Model (ou plus anciennement Bean, ou POJO, pour Plain Old Java Object). Un Model est une classe classique qui doit être "sérialisable" et avoir au minimum :
un constructeur public sans argument ;
des getters et setters pour toutes les propriétés de la classe.
Commencez par créer une nouvelle classe Product que vous allez placer dans un package "model" sous le package microcommerce :
Créez ensuite les propriétés de base de la classe :
package com.ecommerce.micrommerce.model;
public class Product {
private int id;
private String nom;
private int prix;
}
Vous allez maintenant générer le constructeur et les getters et setters. Faites Alt + insert ou clic droit puis Générer, afin d'afficher la fenêtre de génération de code :
Générez le constructeur sans argument puis les getters et setters pour toutes les propriétés, ainsi que la méthode toString. Pour nos tests, nous allons ajouter un constructeur afin d'obtenir des instances de produits préremplies avec des informations de tests. Vous obtenez ceci :
package com.ecommerce.micrommerce.model;
public class Product {
private int id;
private String nom;
private int prix;
public Product() {
}
public Product(int id, String nom, int prix) {
this.id = id;
this.nom = nom;
this.prix = prix;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public int getPrix() {
return prix;
}
public void setPrix(int prix) {
this.prix = prix;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", nom='" + nom + '\'' +
", prix=" + prix +
'}';
}
}
}
Très bien ! Maintenant, à chaque fois que quelqu'un appelle notre URI "/Produits/{id}", nous voudrions renvoyer un produit au format JSON qui correspond à notre classe Product.
Retournez sur le code précédent et remplacez la méthode afficherUnProduit par celle-ci :
//Récupérer un produit par son Id
@GetMapping(value = "/Produits/{id}")
public Product afficherUnProduit(@PathVariable int id) {
Product product = new Product(id, new String("Aspirateur"), 100);
return product;
}
Intellij vous surlignera product en jaune, car selon le linter (un outil intégré dans Intellij qui analyse le code), la variable est redondante ; mais pour la compréhension du code, je laisserai passer ces corrections.
Pour votre information, la syntaxe “parfaite” serait :
return product = new Product(id, new String("Aspirateur"), 100);
Explications
Tout d'abord, nous avons indiqué que notre méthode va retourner un Product au lieu de String .
Normalement, nous sommes censés aller chercher le produit par l'Id que nous avons reçu dans la base de données, et le retourner à l'utilisateur. Comme nous n'avons pas encore de base de données, nous avons tout simplement instancié un objet Product grâce au constructeur que nous avons défini plus tôt. Nous simulons donc la récupération d'un produit dans la base de données.
Une fois notre instance de Product prête, nous la retournons.
Voici le contrôleur complet :
package com.ecommerce.micrommerce.web.controller;
import com.ecommerce.micrommerce.model.Product;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
@GetMapping("/Produits")
public String listeProduits() {
return "Un exemple de produit";
}
//Récupérer un produit par son Id
@GetMapping(value = "/Produits/{id}")
public Product afficherUnProduit(@PathVariable int id) {
Product product = new Product(id, new String("Aspirateur"), 100);
return product;
}
}
Lancez de nouveau le projet puis rendez-vous par exemple sur http://localhost:9090/Produits/27 pour regarder ce qui se passe.
Vous obtenez une belle réponse formatée en JSON comme par magie.
Comment est-ce possible ?
Vous avez indiqué au début que cette classe est un contrôleur REST grâce à l'annotation @RestController. Spring sait alors que les réponses aux requêtes qu'il vous passe devront être très probablement en format JSON.
L'autoconfigurateur va alors chercher si vous avez dans votre classpath une dépendance capable de transformer un objet Java en JSON, et inversement. Bingo ! Il y a justement Jackson qui a été importé avec le starter que nous avons utilisé. Le Bean Product que nous renvoyons est donc transformé en JSON, puis servi en réponse.
Vous venez donc de créer un premier microservice REST sans avoir à manipuler JSON, ni à parser les requêtes HTTP.
Créez le DAO
Nous allons nous rapprocher un peu plus d'un cas d'utilisation réelle, en créant la DAO nécessaire pour communiquer avec une base de données. Nous allons simuler celle-ci grâce à des données statiques.
Créez un package et nommez-le dao, puis créez dedans une interface nommée ProductDao, dans laquelle vous allez déclarer les opérations que nous allons implémenter.
package com.ecommerce.micrommerce.web.dao;
import com.ecommerce.micrommerce.web.model.Product;
import org.springframework.stereotype.Repository;
import java.util.List;
public interface ProductDao {
List<Product> findAll();
Product findById(int id);
Product save(Product product);
}
Nous indiquons dans cette interface que les opérations suivantes sont possibles :
findAll : renvoie la liste complète de tous les produits ;
findById : renvoie un produit par son Id ;
save : ajoute un produit.
Les noms des méthodes ne sont pas choisis au hasard. En effet, il faut suivre les conventions afin de bénéficier plus tard de certaines fonctionnalités qui vous feront gagner beaucoup de temps.
Maintenant que notre interface est prête, nous allons pouvoir créer son implémentation. Créez une classe ProductDaoImpl qui implémente l'interface que nous venons de créer. Vous obtenez alors ceci :
package com.ecommerce.micrommerce.web.dao;
import com.ecommerce.micrommerce.web.model.Product;
import java.util.List;
public class ProductDaoImpl implements ProductDao{
@Override
public List<Product> findAll() {
return null;
}
@Override
public Product findById(int id) {
return null;
}
@Override
public Product save(Product product) {
return null;
}
}
Normalement , cette classe est censée communiquer avec la base de données pour récupérer les produits ou en ajouter. Nous allons simuler ce comportement en créant des produits en dur dans le code.
Nous obtenons donc ceci :
package com.ecommerce.micrommerce.web.dao;
import com.ecommerce.micrommerce.web.model.Product;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
@Repository
public class ProductDaoImpl implements ProductDao{
public static List<Product> products = new ArrayList<>();
static {
products.add(new Product(1, "Ordinateur portable", 350));
products.add(new Product(2, "Aspirateur Robot", 500));
products.add(new Product(3, "Table de Ping Pong", 750));
}
@Override
public List<Product> findAll() {
return products;
}
@Override
public Product findById(int id) {
for (Product product : products){
if (product.getId() == id){
return product;
}
}
return null;
}
@Override
public Product save(Product product) {
products.add(product);
return product;
}
}
@Repository : cette annotation est appliquée à la classe afin d'indiquer à Spring qu'il s'agit d'une classe qui gère les données, ce qui nous permettra de profiter de certaines fonctionnalités, comme les translations des erreurs. Nous y reviendrons.
Pour tester avec des données statiques, vu que nous n'en sommes pas encore à la base de données, nous définissons ici un tableau de Products dans lequel nous ajoutons 3 produits statiques. Les méthodes suivantes sont ensuite redéfinies afin de renvoyer les données adéquates :
findAll : renvoie tous les produits que nous avons créés.
findById : vérifie s'il y a un produit avec l'Id donné dans notre liste de produits, et le renvoie en cas de correspondance.
save : ajoute le produit reçu à notre liste.
Très bien, votre couche DAO est prête ! Vous allez pouvoir l'utiliser pour simuler la récupération et l'ajout de produits depuis une base de données.
Interagissez avec les données
Nous allons maintenant modifier notre contrôleur afin qu'il utilise notre couche DAO pour manipuler les produits.
Voici la classe modifiée :
package com.ecommerce.micrommerce.web.controller;
import com.ecommerce.micrommerce.web.dao.ProductDao;
import com.ecommerce.micrommerce.web.model.Product;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class ProductController {
private final ProductDao productDao;
public ProductController(ProductDao productDao){
this.productDao = productDao;
}
@GetMapping("/Produits")
public List<Product> listeProduits() {
return productDao.findAll();
}
@GetMapping(value = "/Produits/{id}")
public Product afficherUnProduit(@PathVariable int id) {
return productDao.findById(id);
}
}
Tout d'abord, nous avons créé une variable de type ProductDao, que nous avons définie en private final afin que Spring se charge d'en fabriquer une instance que nous injectons dans le constructeur. ProductDao a désormais accès à toutes les méthodes que nous avons définies.
Pour information, s’il n’y a qu’un seul constructeur, Spring s’occupe d’ajouter l’annotation automatiquement à la compilation ; mais pour des raisons de lisibilité, nous avons choisi de le rendre explicite.
Nous avons changé listeProduits afin qu'elle nous retourne une liste de produits List. Ensuite, il a suffi d'invoquer la méthode findAll créée précédemment pour qu'elle nous retourne tous les produits.
De même pour afficherUnProduit qui fait appel à findById.
Testez !
Lancez le microservice et rendez-vous à http://localhost:9090/Produits/, vous obtiendrez ceci :
Bingo ! Vous avez la liste des produits que vous avez définis au format JSON, prête à être consommée par n'importe quel microservice REST.
Rendez-vous ensuite à http://localhost:9090/Produits/1 pour afficher produit par produit.
Le processus pour obtenir le résultat est le suivant :
L'utilisateur envoie une requête GET vers /Produits/2.
Le dispatcheur cherche dans votre contrôleur la méthode qui répond au pattern "/Produits/{id}" et l'exécute.
La méthode (dans ce cas listeProduits) fait appel au DAO pour qu'il communique avec la base de données. Il récupère les informations sur le produit puis il crée une instance de Product qu'il renvoie ensuite à votre méthode.
Votre méthode retourne l'instance reçue, qui est transformée à la volée en JSON grâce à Jackson.
En résumé
Spring est utilisé pour instancier nos objets.
Nous avons un microservice fonctionnel.
Vous pouvez retrouver le code de ce chapitre sur github.
Maintenant que les bases de notre microservice sont prêtes, nous allons pouvoir tester celui-ci avec Postman. On y va !