Injection de dépendances et configuration
Cours · Spring Boot : écrire le serveur (école d'ingénieur), chapitre 2 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun
Au chapitre précédent, les six livres de la Librairie étaient écrits dans le contrôleur. Ce n'est pas sa place : un contrôleur traduit des requêtes HTTP, il ne devrait savoir ni où sont rangés les livres, ni comment calculer des frais de livraison. Il faut donc découper l'application en objets aux rôles distincts (un contrôleur, un service, un dépôt de données) et les relier. Et certains réglages, comme le nom de la Librairie ou le montant des frais de livraison sur le campus, ne devraient pas être écrits dans le code : on doit pouvoir les changer sans recompiler.
Spring résout ces deux problèmes par deux mécanismes que ce chapitre démystifie. Le conteneur crée les objets de l'application et les relie entre eux : c'est l'injection de dépendances. La configuration externe fournit les réglages depuis des fichiers, des variables d'environnement ou la ligne de commande. On verra aussi ce que fait vraiment l'auto-configuration de Spring Boot, et comment la lire.
Objectifs du chapitre
- Expliquer l'inversion de contrôle et le rôle du conteneur de Spring.
- Déclarer des beans par annotation (
@Component,@Service,@Repository) ou par méthode@Bean. - Injecter les dépendances par le constructeur, et choisir entre plusieurs implémentations.
- Lire des réglages avec
@Valueet surtout@ConfigurationProperties. - Utiliser les profils et connaître l'ordre de priorité des sources de configuration.
- Lire le rapport d'auto-configuration.
Les démonstrations sont dans le projet de démonstration du chapitre, code/ch2/demo, et la Librairie dans code/ch2/librairie. Comme au chapitre précédent, les sorties montrées viennent de l'exécution de ces projets.
2.1 Des objets qui fabriquent leurs dépendances
Une caisse calcule le total d'une commande ; elle a besoin d'un calcul des frais de livraison, décrit par une interface :
/** Le calcul des frais de livraison d’une commande. */
public interface CalculFrais {
double frais(String retrait);
}
Sans framework, la caisse fabrique elle-même l'implémentation qu'elle utilise :
/** Sans Spring : la classe fabrique elle-même sa dépendance. */
public class CaisseManuelle {
private final CalculFrais calculFrais = new FraisCampus();
public double total(double montantLivres, String retrait) {
return montantLivres + calculFrais.frais(retrait);
}
}
Ce code marche, mais la caisse est soudée à FraisCampus. Pour offrir la livraison pendant une promotion, il faut modifier la caisse. Pour la tester avec des frais connus d'avance, impossible de lui en donner d'autres. Et si FraisCampus avait elle-même besoin d'autres objets (les réglages de la Librairie, une horloge), la caisse devrait les fabriquer aussi, et ainsi de suite.
La solution consiste à ne plus fabriquer ses dépendances, mais à les recevoir : la caisse déclare dans son constructeur ce dont elle a besoin, et quelqu'un d'autre le lui fournit. Ce « quelqu'un », c'est le conteneur de Spring.
Une dépendance d'un objet est un autre objet dont il a besoin pour travailler. On parle d'injection de dépendances quand l'objet reçoit ses dépendances de l'extérieur (par son constructeur) au lieu de les créer. C'est un cas d'inversion de contrôle : ce n'est plus notre code qui crée et relie les objets, c'est le framework.
2.2 Le conteneur et ses beans
Un bean est un objet créé, relié et gardé par Spring. L'ensemble des beans forme le contexte d'application (ApplicationContext), que SpringApplication.run construit au démarrage.
Pour que Spring crée un bean à partir d'une de nos classes, on l'annote. Toutes les annotations suivantes ont le même effet de base, « fais de cette classe un bean » ; elles ne diffèrent que par l'intention qu'elles affichent et par quelques traitements propres :
| Annotation | Rôle de la classe |
|---|---|
@Component | composant quelconque |
@Service | logique métier (calculs, règles) |
@Repository | accès aux données |
@RestController | réponse aux requêtes HTTP |
@Configuration | classe de déclarations @Bean |
Notre calcul des frais devient un composant, et la caisse un service :
@Component
public class FraisCampus implements CalculFrais {
@Override
public double frais(String retrait) {
return "campus".equals(retrait) ? 2.0 : 0.0;
}
}
@Service
public class CaisseService {
private final CalculFrais calculFrais;
// Un seul constructeur : Spring l’appelle et lui fournit un CalculFrais.
public CaisseService(CalculFrais calculFrais) {
this.calculFrais = calculFrais;
}
public double total(double montantLivres, String retrait) {
return montantLivres + calculFrais.frais(retrait);
}
}
Au démarrage, Spring parcourt les paquets (chapitre 1), trouve ces deux classes, crée d'abord un FraisCampus puis appelle le constructeur de CaisseService en lui passant ce FraisCampus, le seul bean qui soit un CalculFrais. Pour voir le résultat, le projet de démonstration contient un bean Inventaire qui, au démarrage, liste les beans de nos paquets et affiche quelques valeurs. C'est un CommandLineRunner, une interface dont Spring Boot appelle la méthode run une fois l'application prête :
@Override
public void run(String... args) {
System.out.println("--- Beans de fr.librairie.demo ---");
Arrays.stream(contexte.getBeanDefinitionNames())
.filter(nom -> estDeLaDemo(contexte.getType(nom)))
.sorted()
.forEach(nom -> System.out.println(
nom + " : " + contexte.getType(nom).getSimpleName()));
System.out.println("--- Valeurs ---");
System.out.println("total campus : " + caisse.total(40.0, "campus"));
System.out.println(accueil.message());
System.out.println("frais campus : " + proprietes.fraisCampus());
System.out.println("même instance : " + (contexte.getBean(CaisseService.class) == caisse));
}
private static boolean estDeLaDemo(Class<?> type) {
return type != null && type.getPackageName().startsWith("fr.librairie.demo");
}
Voici ce qu'il affiche :
--- Beans de fr.librairie.demo ---
accueil : Accueil
caisseService : CaisseService
demoApplication : DemoApplication
fraisCampus : FraisCampus
horlogeConfiguration : HorlogeConfiguration$$SpringCGLIB$$0
inventaire : Inventaire
librairie-fr.librairie.demo.LibrairieProperties : LibrairieProperties
ouvertureService : OuvertureService
--- Valeurs ---
total campus : 42.0
Bienvenue à La Librairie du Campus
frais campus : 2.0
même instance : true
La suite du cours est sur Adloun
Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.
Lire la suite du coursEn accès libre, sans inscription.