

Introduction
Lorsqu’on débute avec Spring, deux concepts essentiels peuvent prêter à confusion : l’injection de dépendances (Dependency Injection ou DI) et l’inversion de contrôle (Inversion of Control ou IoC). Qui n'a jamais eu de "No qualifying bean of type found for dependency" comme erreur ?
Dans cet article, nous allons aborder les concepts d'injection de dépendances et d'inversion de contrôle sous Spring. Nous verrons les différentes manières d'y parvenir, ainsi que certains cas plus avancés.
Définitions
Avant toute chose, commençons par définir ces deux termes :
Le principe d'inversion de contrôle consiste à déléguer la gestion des objets à un conteneur.
Un conteneur gère le cycle de vie de nos objets (Bean lifecycle en anglais) de la création à la destruction. Sous Spring ce conteneur s'appelle simplement conteneur IoC.
C'est d'ailleurs la différence entre une bibliothèque et un framework. Votre code appelle une bibliothèque, un framework appelle votre code.
Spring n'a cependant pas "inventé" ces 2 concepts. L'injection de dépendances a été formalisée en 2004 par Martin Fowler. Et on trouve déjà à la fin des années 1980 des traces du concept d'inversion de contrôle.
On peut choisir quels objets seront gérés par l'IoC container et ce de différentes manières : annotations(@Service, @Controller, etc), fichier XML de configuration (de plus en plus rare) ou encore via certaines classes.
L'injection de dépendances est un design pattern qui permet de mettre en oeuvre l'inversion de contrôle, il consiste à créer dynamiquement les objets dont dépend un autre objet, plutôt que de les déclarer de manière "statique".
Par exemple prenons une classe SomeService, sa déclaration est la suivante :
public class SomeService {
SomeOtherService otherService;
public SomeService(){
this.otherService = new SomeOtherService();
}
}On voit assez facilement le fort couplage entre <span class="css-span">SomeService</span> et <span class="css-span">SomeOtherService</span>. On voit donc très vite les limites ici : si <span class="css-span">SomeOtherService</span> a aussi une ou plusieurs dépendances, le développeur devra gérer lui-même ces dépendances, ce qui deviendra très fastidieux à la longue.
L'injection de dépendances et l'inversion de contrôle sous Spring
Comme on vient de le voir, la gestion des dépendances peut s'avérer complexe. C'est là qu'intervient le conteneur IoC : il gère les dépendances à la place du développeur.
L'injection de dépendances sous Spring se fait de plusieurs façons :
- L'injection par champ
- L'injection par constructeur
- L'injection par setter
Pour ces 3 cas nous partirons d'une interface <span class="css-span">AlphabetService</span>.
public interface AlphabetService {
String letter();
}Implémentée par 2 classes <span class="css-span">ServiceA</span> et <span class="css-span">ServiceB</span>.
@Service
public class ServiceA implements AlphabetService {
public String letter(){ return "A";}
}@Service
public class ServiceB implements AlphabetService {
public String letter(){ return "B";}
}L'injection par champ
On utilise l'annotation @Autowired sur la déclaration de nos attributs, c'est sans doute l'injection la plus rapide, et la plus facile à mettre en place.
@Service
public class MainService {
@Autowired
private AlphabetService alphabetService;
public String letter(){
return alphabetService.letter() ;
}
}Cette manière est toutefois déconseillée à tel point que la documentation officielle de Spring ne liste maintenant plus cette manière de faire et ce pour plusieurs raisons.
Ici Spring réalise une injection par <span class="css-span">type</span>, Spring cherchera d'abord un bean du type de l'attribut annoté. Dans le cas présent, Spring ne sait pas quel implémentation de notre interface injecter.
Ce code générera donc une erreur :
Field alphabetService in org.younup.service.MainService required a single bean, but 2 were found:
- serviceA: defined in file [target\classes\org\younup\service\ServiceA.class]
- serviceB: defined in file [target\classes\org\younup\service\ServiceB.class]On peut résoudre ce problème via l'annotation <span class="css-span">@Qualifier</span>, elle permet de préciser l'implémentation précise que l'on veut injecter (pour plus de détails).
@Service
public class MainService {
@Autowired
@Qualifier("serviceA")
private AlphabetService alphabetService;
public String letter(){
return alphabetService.letter() ;
}
}Cette manière de faire rend aussi impossible la déclaration comme <span class="css-span">final</span> de nos dépendances. Il est même probable que votre IDE signale votre déclaration en erreur à l'écriture.
Une autre raison est la dépendance que cela crée par rapport à notre IoC container. Avec l'injection sur la déclaration de nos attributs, on n'obtient pas une manière directe pour instancier notre classe dans le cas de mocks lors de tests unitaires.
Injection par constructeur
Spring injecte notre dépendance directement via notre constructeur annoté.
@Service
public class MainService {
private final AlphabetService alphabetService;
public MainService(AlphabetService alphabetService){
this.alphabetService = alphabetService;
}
public String letter(){
return alphabetService.letter() ;
}
}Cependant depuis Spring 4.3, il n'est plus nécessaire d'annoter un constructeur avec <span class="css-span">Autowired</span> si notre classe n'en possède qu'un seul et s'il s'agit d'un bean géré par Spring. En effet Spring considère que dans le cas d'un seul constructeur, il sera celui utilisé pour l'injection de nos dépendances.
Et à contrario de l'injection par champs permet d'annoter comme <span class="css-span">final</span> nos dépendances.
Injection par setter
Comme pour l'injection par constructeur, Spring injecte notre dépendance via notre setter annoté.
@Autowired
public void setAlphabetService(AlphabetService alphabetService){
this.alphabetService = alphabetService;
}La différence ici réside dans l'immutabilité de nos objets. En effet, l'injection par setter ne peut garantir que nos dépendances resteront inchangées une fois injectées. Il est donc préférable d'utiliser ce type d'injection uniquement pour des dépendances facultatives. Elle peut rendre également le code moins facile à tester, si les setters sont mal utilisés ou si certaines dépendances ne sont pas injectées correctement, cela peut entraîner des NullPointerException par exemple.
Au final quelle méthode choisir ?
Au final, quelle est la méthode à choisir pour réaliser notre injection de dépendances ? On évitera au possible l'injection sur nos attributs pour les raisons évoquées précédemment.
On privilégiera l'injection par constructeur dans le cadre de dépendances obligatoires à notre objet et par setter dans le cas de dépendances facultatives.
Cas plus avancés
Annotation @Primary
Comme nous l'avons vu dans le cadre de l'injection par attribut Spring, on peut spécifier l'implémentation précise que l'on veut injecter. Il est aussi possible d'utiliser <span class="css-span">@Primary</span> afin de spécifier une implémentation par défaut de notre bean.
Le code suivant aura le même résultat que l'utilisation de @Qualifier vu précédemment.
@Service
@Primary
public class ServiceA implements AlphabetService{
public String letter(){ return "A";}
}Annotation @Configuration et @Bean
L'annotation <span class="css-span">@Configuration</span> permet d'annoter une classe comme générant des beans que Spring ne peut pas détecter automatiquement (le cas où nous avons besoin d'un bean d'une bibliothèque par ex).
@Configuration
public class AppConfig {
@Bean
public MyLibraryBean myLibraryBean(){
MyLibraryBean myLibraryBean = new MyLibraryBean();
myLibraryBean.setName(" custom name !!!");
return myLibraryBean;
}
}On pourra ensuite utiliser ce bean :
@Service
public class MainService {
private final AlphabetService alphabetService;
private final MyLibraryBean libraryBean;
public MainService(AlphabetService alphabetService, MyLibraryBean libraryBean){
this.alphabetService = alphabetService;
this.libraryBean = libraryBean;
}
public String letter(){
return alphabetService.letter()+libraryBean.getName() ;
}
}Conclusion
Nous avons vu l'injection de dépendances et l'inversion de contrôle sous Spring. Confier la gestion de l'injection de dépendance à Spring nous permet de réduire le couplage entre nos objets et permet un code plus testable et plus évolutif.

Chez Younup, on n’est pas expert en babyfoot, on est expert en IT !
Nous aimons l’IT, nous aimons le dev, mettre en place une belle archi, livrer une spec propre, produire des applis clean. Ce sont donc des passionnés unis, à la disposition des projets de transformation digitale de nos clients, qui composent Younup.


