Spring : IoC & DI
Dans cette quête, tu vas explorer des concepts fondamentaux du framework Spring : l'inversion de contrôle et l'injection de dépendances.
Tu vas découvrir différentes manières de contrôler une (petite) partie du framework.
Objectifs
- Comprendre les notions de couplage fort et couplage faible
- Comprendre la notion d'inversion de contrôle
- Savoir appliquer l'injection de dépendances avec Spring Boot
Couplage fort / couplage faible
On parle de couplage quand il y a interaction entre des composants logiciels. Si une classe appelle une instance d'une autre classe, il y a "couplage". Parfois le couplage est plus ou moins fort, par exemple : si tu crées une instance d'une autre classe, le couplage est très fort.
Pour mieux comprendre cette notion, voici un exemple abstrait :
1. Couplage fort
Tu es le patron d'un site de tests de jeux vidéos et tu cherches des pigistes.
Dans un premier temps, tu décides d'embaucher des salariés pigistes sur ton besoin actuel, qui est de réaliser des tests sous la forme d’articles.
Après un certain temps, le marché des sites de tests de jeux vidéos évolue et tu voudrais déplacer ton activité vers une plate-forme de test au format vidéo.
Tes salariés ne sont, à juste titre, pas content : ils sont compétents dans l’écriture d’articles, pas pour tourner des vidéos !
Dans cet exemple, les tests réalisés sont fortement liés aux testeurs : on parle de couplage fort.
2. Couplage faible
Maintenant, imaginons que tu es toujours le patron d'un site de tests de jeux vidéos, sauf que cette fois, tu vas passer par un chasseur de tête pour trouver des testeurs en freelances.
Le chasseur de tête va te demander de rédiger un contrat sur les comportements attendus d'un testeur :
-
la rédaction envoie le jeu
-
le testeur réalise le test
-
la rédaction reçoit le test
Dans un premier temps, tu vas demander au chasseur de tête de te trouver des pigistes correspondants aux critères : ils vont réaliser des articles.
Quelques temps plus tard, tu décides que les tests sont réalisés sous format vidéo : tant que le contrat prévu est respecté, le chasseur de tête trouvera quelqu'un correspondant aux critères.
Dans cet exemple, les tests réalisés sont faiblement liés aux testeurs : on parle de couplage faible.
3. En conclusion
Dans le cas d'un couplage fort, tu rencontreras les problèmes suivants :
-
le code peut être difficile à lire, surtout lorsqu'il y a beaucoup d'interdépendances : on parle de code spaghetti
-
les composants seront difficilement réutilisables, car liés les uns aux autres
-
les composant seront difficilement testables, car il sera nécessaire de définir en amont toutes les dépendances (qui elles mêmes pourront apporter leurs lots d'erreurs)
Lors de la réalisation d'un couplage faible, tous les inconvénients soulevés plus tôt sont ainsi gommés.
Explication de la notion de polymorphisme
Inversion de contrôle
Dans le cadre d'un projet informatique, l'inversion de contrôle (IoC) est un design pattern qui consiste à déléguer des tâches au framework, par exemple la création d'instances. Le but de l’IoC est de réduire les dépendances entre les composants et donc pousser au maximum le design pattern de couplage faible.
L'IoC est parfois surnommée the Hollywood Principle, car elle illustre une phrase célèbre dans le milieu du cinéma américain : « Don't call us, we'll call you ».

Pour mettre en place l'IoC, il faut réaliser des contrats (interfaces), afin de réaliser une abstraction du comportement de chaque composant.
Le comportement réel sera quand à lui réalisé dans l'implémentation de ces interfaces.
Il devient alors possible de déléguer au framework la mise en relation de ces composants.
Injection de dépendances
Dans un framework, l'implémentation de l'IoC est nommé injection de dépendances (DI). Il existe deux méthodes pour mettre en place ce processus :
- par annotation
- par constructeur
1. Autowired
L'injection de dépendance par l'annoation @Autowired est la plus simple à mettre en place.
Reprenons le code de notre contrôleur :
@Controller
public class NewsroomController {
private ArticleReviewRepository repository = new ArticleReviewRepository();
@GetMapping("/review")
@ResponseBody
public Review showReview(@RequestParam String game) {
repository.sendGame(game);
repository.review();
return repository.getReview();
}
}
Ici, la classe ArticleReviewRepository implémente l'interface ReviewDao, par polymorphisme d'héritage il est possible de créer une référence du type de l'interface ReviewDao et d'y stocker une instance de ArticleReviewRepository.
Cela donne le code suivant :
@Controller
public class NewsroomController {
private ReviewDao repository = new ArticleReviewRepository();
// code omitted...
}
Maintenant, tu vas demander à Spring de te fournir une instance de l'implémentation existante de l'interface ReviewDao. Cela passe par l'annotation @Autowired :
@Controller
public class NewsroomController {
@Autowired
private ReviewDao repository;
// code omitted...
}
Enfin, il va falloir préciser à ArticleReviewRepository qu'il peut être injecté par Spring : il faut lui ajouter une annotation.
L'annotation principale dans le cas d'une injection est @Component. Cependant, d'autres annotations fonctionneront, par exemple : @Service, @Entity ou encore @Repository. Ces annotations servent à détailler le rôle de la classe, ici il semble approprié d'utiliser @Repository :
@Repository
public class ArticleReviewRepository implements ReviewDao {
// code omitted...
}
Et... c'est tout ! Comme l'interface ReviewDao ne possède qu'une seule implémentation ArticleReviewRepository, Spring en va créer une instance et la fournir à NewsroomController.
2. Injection par constructeur
L'inconvénient de l'injection de dépendance par annotation @Autowired, c'est qu'elle rend le code plus difficile à tester. Ce n'est pas impossible, mais plus compliqué de mettre en place un mock (une version simulée de l'élément injecté par dépendance).
Spring conseille de faire une injection par constructeur à la place :
@Controller
public class NewsroomController {
private final ReviewDao repository;
public NewsroomController(ReviewDao repositoryInjected) {
this.repository = repositoryInjected;
}
// code omitted...
}
Qualifier
Imaginons que tu aies deux implémentations de ReviewDao : ArticleReviewRepository, mais aussi VideoReviewRepository. Comment Spring va deviner laquelle injecter ? Par défaut, il ne va pas y arriver et il va donc planter lamentablement à l'exécution du serveur :
Field repository in com.wildcodeschool.gamenews.controller.NewsroomController required a single bean, but 2 were found:
- articleReviewRepository: defined in file [ArticleReviewRepository.class]
- videoReviewRepository: defined in file [VideoReviewRepository.class]
Ici Spring utilise le terme de bean comme on parle généralement d'objet : il s'attendait à récupérer l'instance d'un objet, mais deux étaient disponibles. En l'absence d'un objet qualificatif (qualifying bean), il ne peut pas prendre de décision.
Pour définir l'instance que tu désires injecter, il faut ajouter l'annotation @Qualifier, avec entre parenthèses le nom de la classe commençant par une minuscule. Si tu veux injecter l'instance de VideoReviewRepository :
@Controller
public class NewsroomController {
private final ReviewDao repository;
public NewsroomController(
@Qualifier("videoReviewRepository") ReviewDao repositoryInjected
) {
this.repository = repositoryInjected;
}
// code omitted...
}
Dépôt d'exemple
Tu vas pouvoir lire et tester le code vu dans la quête sur le dépôt suivant : https://github.com/WildCodeSchool/quest-spring-injection-sample.
Prend le temps de parcourir toutes les classes : tu verras que les Repository ont aussi une injection de dépendances des instances de ArticleReview et VideoReview (qui ont maintenant l'annotation @Component).
Tu peux tester sur le lien http://localhost:8080/review : pense à renseigner dans l’url un paramètre "game", avec le nom d’un jeu à tester.
Ensuite, modifie la valeur du qualifier contenu dans NewsroomController afin de réaliser des tests au format vidéo, puis relance le serveur afin de tester les changements.
En résumé
Les concepts d'inversion de contrôle et d'injection de dépendances sont au cœur du fonctionnement de Spring.
Comme c'est souvent le cas pour les frameworks modernes, il est tout à fait possible d'utiliser Spring d'une façon « magique », sans connaître ces deux concepts. Néanmoins, étant donné qu'il s'agit de concepts fondateurs, ces deux notions constituent souvent des sujets de choix pour des questions d'entretiens techniques (voir ressources).
Entre autres bénéfices, l'inversion de contrôle et l'injection de dépendances permettent de :
-
Minimiser la quantité de code à écrire
-
Rendre une application plus simple à tester grâce à l'utilisation de bouchons (mocks).
-
Rendre une application plus flexible. Par exemple, en cas de migration d'une base de données utilisée par ton application Java vers une autre (R : une base de données SQL vers une base de données NoSQL), il n'y aura pas besoin de modifier le code lui-même - seulement la configuration liée à l'inversion de contrôle et à l'injection de dépendances.
Tout cela peut te paraître très théorique pour le moment... N'hésite pas à revenir consulter les ressources de cette partie pour approfondir ces notions. Cela s'éclairera aussi lorsque tu pratiqueras Spring plus avant.
Pour voir comment l'inversion de contrôle et l'injection de dépendances peuvent être traités dans le cadre d'un entretien de recrutement technique
Challenge
C’est pas sorcier
Fait un fork du dépôt suivant : https://github.com/WildCodeSchool/quest-spring-injection.
Tu reprends le développement d'un site de fan d'Harry Potter afin de l’améliorer. En l'état, le site permet l'administration complète des personnages de l'univers de la série.
La base de données est fictive et utilise une ArrayList, pas besoin de configurer quoi que ce soit. Les données ne sont donc pas persistantes : c’est normal qu’après un redémarrage du serveur, la liste revienne à sa valeur par défaut.
Tu vas commencer par tester le site et voir comment tout cela fonctionne.
Attention à bien regarder toutes les classes du dépôt avant de commencer le challenge !
- Teste que le site fonctionne correctement
- Modifie la classe
WizardControllerafin de récupérer l’instance du repository par injection de dépendance - Ajoute l’annotation nécessaire à
WizardRepositoryafin que Spring Boot autorise cette injection - Compile et teste le site afin de t’assurer que tout fonctionne encore normalement
Critères de validation
- Dans la classe
WizardController, aucune instance deWizardRepositoryn’est créée : elle doit être injectée - Dans la classe
WizardController, c’est l’interfaceWizardDaoqui doit être injectée, pasWizardRepository - Dans la classe
WizardRepository, l’annotation nécessaire à la prise en compte par Spring Boot est renseignée - L’application fonctionne correctement