Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 61

Injection de dépendances avec @Autowired

Recommended resources

Bonjour ! Dans la leçon précédente, vous avez découvert comment définir des composants Spring avec @RestController et @Service. Vous avez même réussi à les faire communiquer en utilisant une injection par constructeur, un premier aperçu de la puissance de l'Inversion de Contrôle (IoC).

Aujourd'hui, nous allons approfondir ce mécanisme fondamental en nous concentrant sur l'annotation qui est au cœur de l'injection de dépendances automatique dans Spring : @Autowired. Vous apprendrez comment l'utiliser, explorerez les différentes manières de l'appliquer (sur les attributs, les setters et les constructeurs) et, plus important encore, vous comprendrez pourquoi l'une de ces méthodes est fortement recommandée dans un contexte professionnel.

À la fin de cette leçon, vous saurez comment injecter des dépendances entre vos composants de manière efficace et robuste en utilisant @Autowired.

1. Qu'est-ce que l'injection de dépendances ?

Rappelons brièvement le principe. Au lieu que vos objets créent eux-mêmes les dépendances dont ils ont besoin (par exemple, avec new MessageService()), vous déclarez simplement ce dont ils ont besoin, et le conteneur Spring se charge de leur "fournir" (injecter) une instance appropriée.

L'annotation @Autowired est la façon la plus directe de demander à Spring d'effectuer cette injection.

Ce diagramme illustre le concept : le `CarService` a besoin d'un `EngineService`. Plutôt que de le créer lui-même, il le demande. Le conteneur Spring, qui gère le cycle de vie des composants, injecte l'instance requise.

Pour une définition plus formelle, l'article suivant introduit parfaitement @Autowired.

Déclaration par annotations — Spring Framework / Spring Boot

Cet extrait de la documentation de Gayerie.dev explique de manière concise et technique le rôle de @Autowired.

Lisez la section "L’annotation @Autowired". Concentrez-vous sur les trois endroits où elle peut être placée : un constructeur, une méthode setter, ou directement sur un attribut.


2. Les Trois Stratégies d'Injection

@Autowired peut être utilisé de trois manières différentes. Bien qu'elles semblent faire la même chose, elles ont des implications importantes en termes de conception, de lisibilité et de testabilité.

La vidéo suivante vous guidera à travers ces stratégies, en commençant par la plus simple mais la moins recommandée, pour arriver à la meilleure pratique.

Part 2- DI and IOC- Ce qu'il fait savoir et savoir faire avant de commencer Spring Boot

Dans cette vidéo, le Professeur Mohamed YOUSSFI démontre de manière très claire comment implémenter l'injection de dépendances avec les annotations Spring.

Commencez par regarder la section sur l'injection par attribut. L'instructeur utilise @Autowired directement sur un champ. C'est l'injection par attribut (field injection). Ensuite, regardez attentivement la section sur l'injection par constructeur. Notez bien pourquoi cette approche est préférable : elle respecte mieux l'encapsulation et garantit qu'un objet est toujours créé dans un état valide.

Analysons ces trois approches :

  1. Injection par attribut (Field Injection)

    @RestController
    public class HelloController {
        @Autowired
        private MessageService messageService; // Injection directement sur le champ
    
        // ...
    }
    
    • Avantage : Très concis.
    • Inconvénients :
      • Rend les tests unitaires plus difficiles car vous ne pouvez pas facilement fournir une implémentation "mock" (factice) du service sans utiliser des mécanismes complexes (réflexion).
      • Cache les dépendances de la classe. Il n'est pas évident, en regardant juste les constructeurs, de savoir ce qui est requis pour créer l'objet.
      • Permet de violer le principe d'immutabilité (un champ ne peut pas être déclaré final).
  2. Injection par mutateur (Setter Injection)

    @RestController
    public class HelloController {
        private MessageService messageService;
    
        @Autowired
        public void setMessageService(MessageService messageService) { // Injection via une méthode 'set'
            this.messageService = messageService;
        }
        // ...
    }
    
    • Avantage : Permet des dépendances optionnelles. On peut ne pas appeler le setter si la dépendance n'est pas nécessaire.
    • Inconvénient : L'objet peut être créé et exister pendant un temps dans un état incomplet (avant que le setter soit appelé).
  3. Injection par constructeur (Constructor Injection)
    C'est l'approche que nous avons utilisée dans la leçon précédente.

    @RestController
    public class HelloController {
        private final MessageService messageService; // Le champ peut être 'final' !
    
        // L'annotation @Autowired est optionnelle ici s'il n'y a qu'un seul constructeur
        public HelloController(MessageService messageService) {
            this.messageService = messageService;
        }
        // ...
    }
    
    • Avantages :
      • Dépendances explicites : Le constructeur liste clairement tout ce dont la classe a besoin pour fonctionner.
      • Immutabilité : Les dépendances peuvent être déclarées final, garantissant qu'elles ne changeront pas après la création de l'objet.
      • Testabilité : Extrêmement facile à tester. Il suffit d'appeler le constructeur avec des implémentations factices (mocks) des dépendances.
      • État garanti : L'objet ne peut pas être créé dans un état incomplet. Toutes ses dépendances obligatoires sont fournies à la création.

Pour ces raisons, l'équipe Spring elle-même recommande l'injection par constructeur. L'article suivant renforce cette idée et constitue une lecture essentielle sur les bonnes pratiques.

[Bonne pratique] Utilisez l'injection par constructeur avec Spring

Cet article de blog par Keyboard Playing explique de manière convaincante pourquoi l'injection par constructeur est supérieure aux autres méthodes.

Lisez les sections suivantes pour bien ancrer cette bonne pratique : "La version courte" et "La recommandation de l’équipe Spring" : pour aller droit au but. "Avantage : testabilité" : C'est l'un des arguments les plus importants. "Avantage : paradigme POO" : Comprenez pourquoi c'est plus cohérent avec les principes de la programmation orientée objet.

En résumé : privilégiez toujours l'injection par constructeur. C'est la marque d'un code Spring moderne, robuste et testable.


3. Gérer les Ambiguïtés avec @Qualifier

Que se passe-t-il si Spring trouve plusieurs beans qui correspondent au type que vous essayez d'injecter ?

Par exemple, imaginez une interface NotificationService et deux implémentations : EmailService et SmsService, toutes deux déclarées comme des @Service.

@Autowired
private NotificationService notificationService; // Lequel choisir ? Email ou SMS ?

Spring ne peut pas deviner et lèvera une exception au démarrage. Pour résoudre cette ambiguïté, on utilise l'annotation @Qualifier.

Dans une architecture en couches, `@Autowired` est utilisé pour connecter les composants. Ici, il injecte un `MiLibroRepository` dans `MiLibroService`. Si plusieurs implémentations de `MiLibroRepository` existaient, il faudrait utiliser `@Qualifier` pour spécifier laquelle injecter.

La vidéo suivante illustre parfaitement ce scénario et sa solution.

Part 2- DI and IOC- Ce qu'il fait savoir et savoir faire avant de commencer Spring Boot

Retournons à la vidéo du Professeur Mohamed YOUSSFI pour voir comment il gère ce cas de figure.

Regardez la section sur la gestion de la confusion. L'instructeur montre comment, en présence de deux implémentations d'une même interface, l'utilisation de @Autowired seule provoque une erreur, et comment @Qualifier permet de spécifier précisément quel bean injecter.

Comment ça marche ?

Par défaut, un bean Spring prend le nom de sa classe avec la première lettre en minuscule. Par exemple, la classe EmailService devient le bean nommé emailService.

On utilise @Qualifier pour indiquer le nom du bean que l'on souhaite injecter.

@Service
public class OrderService {

    private final NotificationService notificationService;

    public OrderService(@Qualifier("smsService") NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    // ...
}

Ici, nous demandons explicitement à Spring d'injecter le bean nommé smsService, levant ainsi toute ambiguïté.


4. Exercice Pratique

Mettons ces concepts en application en modifiant le code de la leçon précédente.

  1. Passez à l'injection par attribut :

    • Dans votre HelloController, supprimez le constructeur.
    • Ajoutez l'annotation @Autowired directement sur le champ private MessageService messageService;.
    • Relancez l'application et testez l'endpoint /greeting. Il devrait toujours fonctionner.
    • Réfléchissez : Bien que ça marche, pourquoi est-ce une moins bonne pratique ?
  2. Créez une ambiguïté :

    • Créez une nouvelle interface GreetingService dans le package service :
      package com.votreentreprise.monapirest.service;
      
      public interface GreetingService {
          String greet();
      }
      
    • Créez deux implémentations de cette interface :
      • FormalGreetingService.java :
        package com.votreentreprise.monapirest.service;
        
        import org.springframework.stereotype.Service;
        
        @Service
        public class FormalGreetingService implements GreetingService {
            @Override
            public String greet() {
                return "Bonjour, soyez le bienvenu.";
            }
        }
        
      • InformalGreetingService.java :
        package com.votreentreprise.monapirest.service;
        
        import org.springframework.stereotype.Service;
        
        @Service
        public class InformalGreetingService implements GreetingService {
            @Override
            public String greet() {
                return "Salut ! Ça va ?";
            }
        }
        
  3. Injectez avec @Qualifier :

    • Revenez à une injection par constructeur dans HelloController (c'est la bonne pratique !).

    • Modifiez HelloController pour qu'il utilise GreetingService et résolve l'ambiguïté.

      // ... imports
      import com.votreentreprise.monapirest.service.GreetingService;
      import org.springframework.beans.factory.annotation.Qualifier;
      
      @RestController
      public class HelloController {
      
          private final MessageService messageService;
          private final GreetingService greetingService;
      
          // Injection de plusieurs dépendances
          public HelloController(MessageService messageService, @Qualifier("informalGreetingService") GreetingService greetingService) {
              this.messageService = messageService;
              this.greetingService = greetingService;
          }
          
          // ... endpoints existants ...
      
          @GetMapping("/casual-greeting")
          public String getCasualGreeting() {
              return greetingService.greet();
          }
      }
      
    • Notez que le nom du bean (informalGreetingService) est bien le nom de la classe (InformalGreetingService) en camelCase.

  4. Testez :

    • Relancez l'application.
    • Accédez à http://localhost:8080/casual-greeting. Vous devriez voir "Salut ! Ça va ?".
    • Essayez de changer le @Qualifier pour "formalGreetingService", relancez et testez à nouveau.

Conclusion

Félicitations ! Vous maîtrisez maintenant le mécanisme central de l'injection de dépendances dans Spring.

Voici les points clés à retenir :

  • @Autowired est l'annotation qui demande à Spring d'injecter automatiquement une dépendance.
  • Il existe trois types d'injection : par attribut, par setter et par constructeur.
  • L'injection par constructeur est la meilleure pratique : elle rend le code plus clair, plus sûr (immuabilité) et beaucoup plus facile à tester.
  • Lorsque plusieurs beans du même type existent, utilisez @Qualifier("nomDuBean") pour dire à Spring lequel injecter et résoudre l'ambiguïté.

Vous savez désormais non seulement comment "faire fonctionner" l'injection, mais aussi comment l'utiliser de manière professionnelle et robuste.

Dans la prochaine leçon, nous allons pousser notre API un peu plus loin. Nous verrons comment un @RestController peut retourner des objets Java complexes, et comment Spring les convertit automatiquement en JSON, le format de données standard pour les API modernes.