TP5.1 - Injection de Dépendances Avancée
Objectifs
À la fin de ce TP, vous serez capable de:
- ✅ Utiliser les 3 types d'injection (constructeur, setter, champ)
- ✅ Choisir entre plusieurs implémentations avec @Qualifier
- ✅ Gérer des dépendances optionnelles
- ✅ Comprendre et résoudre les dépendances circulaires
Durée estimée: 1h30
Prérequis
- ✅ Chapitres 1 et 2 du Module 5 complétés
- ✅ Maven 3.9+ installé
- ✅ Java 17+ installé
- ✅ VS Code avec extensions Java
Préparation: Créer le Projet
Étape 1: Générer le Projet avec Spring Initializr
Via le site web:
-
Allez sur https://start.spring.io/
-
Configurez:
- Project: Maven
- Language: Java
- Spring Boot: 3.2.0 (ou dernière version)
- Group:
com.example - Artifact:
injection-demo - Name:
injection-demo - Package name:
com.example.injection - Packaging: Jar
- Java: 17
-
Dépendances à ajouter:
- Spring Web
-
Cliquez sur GENERATE
-
Dézippez et ouvrez dans VS Code
Étape 2: Structure du Projet
Créez la structure suivante:
src/main/java/com/example/injection/
├── InjectionDemoApplication.java
├── service/
│ ├── EmailService.java
│ ├── GmailService.java
│ ├── SendGridService.java
│ └── NotificationService.java
├── controller/
│ └── TestController.java
└── config/
└── AppConfig.java
Exercice 1: Injection par Constructeur ✅
Objectif
Créer un service avec injection par constructeur (bonne pratique).
Étape 1.1: Créer l'Interface EmailService
Fichier: service/EmailService.java
package com.example.injection.service;
public interface EmailService {
void send(String to, String message);
String getProviderName();
}
Ce qu'on fait: Créer une interface pour avoir plusieurs implémentations possibles.
Étape 1.2: Créer l'Implémentation GmailService
Fichier: service/GmailService.java
package com.example.injection.service;
import org.springframework.stereotype.Service;
@Service
public class GmailService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("[Gmail] Envoi email à " + to + ": " + message);
}
@Override
public String getProviderName() {
return "Gmail";
}
}
Points clés:
@Service→ Spring gère ce Bean- Implémente
EmailService
Étape 1.3: Créer le NotificationService avec Injection par Constructeur
Fichier: service/NotificationService.java
package com.example.injection.service;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
// Dépendance déclarée comme 'final' (immutable)
private final EmailService emailService;
// Injection par constructeur (BONNE PRATIQUE ✅)
public NotificationService(EmailService emailService) {
this.emailService = emailService;
System.out.println("NotificationService créé avec: " +
emailService.getProviderName());
}
public void notifyUser(String email, String message) {
System.out.println("=== NotificationService ===");
emailService.send(email, message);
}
}
Points clés:
private final→ Immutable- Injection via constructeur
- Pas besoin de
@Autowired(optionnel sur constructeur unique)
Étape 1.4: Créer le Controller de Test
Fichier: controller/TestController.java
package com.example.injection.controller;
import com.example.injection.service.NotificationService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/test")
public class TestController {
private final NotificationService notificationService;
// Injection par constructeur aussi ici
public TestController(NotificationService notificationService) {
this.notificationService = notificationService;
}
@GetMapping("/constructor")
public String testConstructorInjection() {
notificationService.notifyUser("user@example.com",
"Test injection par constructeur");
return "✅ Injection par constructeur testée - Voir console";
}
}
Étape 1.5: Tester
Démarrer l'application:
mvn clean install -DskipTests spring-boot:run
Tester dans le navigateur:
http://localhost:8080/api/test/constructor
Console devrait afficher:
NotificationService créé avec: Gmail
=== NotificationService ===
[Gmail] Envoi email à user@example.com: Test injection par constructeur
✅ Résultat attendu: "✅ Injection par constructeur testée - Voir console"
Exercice 2: Gérer Plusieurs Implémentations avec @Qualifier
Objectif
Avoir 2 implémentations d'EmailService et choisir laquelle utiliser.
Étape 2.1: Créer SendGridService
Fichier: service/SendGridService.java
package com.example.injection.service;
import org.springframework.stereotype.Service;
@Service("sendGridService") // ← Nom explicite
public class SendGridService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("[SendGrid] Envoi email à " + to + ": " + message);
}
@Override
public String getProviderName() {
return "SendGrid";
}
}
Étape 2.2: Problème - Deux Implémentations!
Redémarrez l'application:
mvn clean install -DskipTests spring-boot:run
Vous devriez voir une ERREUR:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.injection.service.NotificationService
required a single bean, but 2 were found:
- gmailService: defined in file [...]
- sendGridService: defined in file [...]
Action:
Consider marking one of the beans as @Primary, or use @Qualifier
C'est normal! Spring ne sait pas laquelle choisir.
Étape 2.3: Solution 1 - @Primary
Modifiez GmailService.java:
package com.example.injection.service;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@Service
@Primary // ← Implémentation par défaut
public class GmailService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("[Gmail] Envoi email à " + to + ": " + message);
}
@Override
public String getProviderName() {
return "Gmail";
}
}
Redémarrez: L'application démarre maintenant! Gmail est utilisé par défaut.
Testez:
http://localhost:8080/api/test/constructor
Console:
[Gmail] Envoi email à user@example.com: ...
Étape 2.4: Solution 2 - @Qualifier pour Choisir
Créez un nouveau service qui utilise SendGrid:
Fichier: service/SendGridNotificationService.java
package com.example.injection.service;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class SendGridNotificationService {
private final EmailService emailService;
// Utiliser @Qualifier pour choisir SendGrid
public SendGridNotificationService(
@Qualifier("sendGridService") EmailService emailService
) {
this.emailService = emailService;
System.out.println("SendGridNotificationService créé avec: " +
emailService.getProviderName());
}
public void notifyUser(String email, String message) {
System.out.println("=== SendGridNotificationService ===");
emailService.send(email, message);
}
}
Étape 2.5: Tester @Qualifier
Ajoutez une méthode dans TestController.java:
@RestController
@RequestMapping("/api/test")
public class TestController {
private final NotificationService notificationService;
private final SendGridNotificationService sendGridNotificationService;
public TestController(
NotificationService notificationService,
SendGridNotificationService sendGridNotificationService
) {
this.notificationService = notificationService;
this.sendGridNotificationService = sendGridNotificationService;
}
@GetMapping("/constructor")
public String testConstructorInjection() {
notificationService.notifyUser("user@example.com",
"Test avec Gmail");
return "✅ Gmail utilisé - Voir console";
}
@GetMapping("/qualifier")
public String testQualifier() {
sendGridNotificationService.notifyUser("user@example.com",
"Test avec SendGrid");
return "✅ SendGrid utilisé via @Qualifier - Voir console";
}
}
Testez:
http://localhost:8080/api/test/qualifier
Console:
=== SendGridNotificationService ===
[SendGrid] Envoi email à user@example.com: Test avec SendGrid
✅ Vous avez 2 services utilisant des implémentations différentes!
Exercice 3: Dépendances Optionnelles
Objectif
Créer un service qui peut fonctionner avec ou sans une dépendance.
Étape 3.1: Créer un SmsService (Optionnel)
Fichier: service/SmsService.java
package com.example.injection.service;
public interface SmsService {
void send(String phone, String message);
}
Ne créez PAS d'implémentation pour l'instant!
Étape 3.2: Créer CompleteNotificationService avec Dépendance Optionnelle
Fichier: service/CompleteNotificationService.java
package com.example.injection.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class CompleteNotificationService {
private final EmailService emailService;
private final Optional<SmsService> smsService; // ← Optionnel!
public CompleteNotificationService(
EmailService emailService,
Optional<SmsService> smsService // ← Wrapper Optional
) {
this.emailService = emailService;
this.smsService = smsService;
System.out.println("CompleteNotificationService créé");
System.out.println("- Email: " + emailService.getProviderName());
System.out.println("- SMS: " + (smsService.isPresent() ? "Disponible" : "Non disponible"));
}
public void notify(String email, String phone, String message) {
// Email toujours envoyé
emailService.send(email, message);
// SMS seulement si disponible
smsService.ifPresent(sms -> sms.send(phone, message));
}
}
Points clés:
Optional<SmsService>→ Peut être absentifPresent()→ Exécuté seulement si présent
Étape 3.3: Tester Sans SmsService
Ajoutez dans TestController.java:
private final CompleteNotificationService completeService;
public TestController(
NotificationService notificationService,
SendGridNotificationService sendGridNotificationService,
CompleteNotificationService completeService
) {
this.notificationService = notificationService;
this.sendGridNotificationService = sendGridNotificationService;
this.completeService = completeService;
}
@GetMapping("/optional")
public String testOptional() {
completeService.notify("user@example.com", "123-456-7890",
"Test dépendance optionnelle");
return "✅ Email envoyé, SMS non disponible - Voir console";
}
Testez:
http://localhost:8080/api/test/optional
Console:
CompleteNotificationService créé
- Email: Gmail
- SMS: Non disponible
[Gmail] Envoi email à user@example.com: Test dépendance optionnelle
✅ L'application fonctionne même sans SmsService!
Étape 3.4: Ajouter SmsService et Retester
Fichier: service/SimpleSmsService.java
package com.example.injection.service;
import org.springframework.stereotype.Service;
@Service
public class SimpleSmsService implements SmsService {
@Override
public void send(String phone, String message) {
System.out.println("[SMS] Envoi à " + phone + ": " + message);
}
}
Redémarrez et testez:
http://localhost:8080/api/test/optional
Console:
CompleteNotificationService créé
- Email: Gmail
- SMS: Disponible
[Gmail] Envoi email à user@example.com: Test dépendance optionnelle
[SMS] Envoi à 123-456-7890: Test dépendance optionnelle
✅ Maintenant les 2 sont utilisés!
Exercice 4: Injecter Toutes les Implémentations
Objectif
Récupérer TOUTES les implémentations d'une interface.
Étape 4.1: Créer MultiChannelNotificationService
Fichier: service/MultiChannelNotificationService.java
package com.example.injection.service;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MultiChannelNotificationService {
private final List<EmailService> allEmailServices;
// Spring injecte une Liste avec TOUTES les implémentations
public MultiChannelNotificationService(List<EmailService> allEmailServices) {
this.allEmailServices = allEmailServices;
System.out.println("MultiChannelNotificationService créé avec " +
allEmailServices.size() + " services email:");
allEmailServices.forEach(service ->
System.out.println(" - " + service.getProviderName())
);
}
public void broadcastEmail(String to, String message) {
System.out.println("=== Envoi via TOUS les providers ===");
allEmailServices.forEach(service -> service.send(to, message));
}
}
Étape 4.2: Tester
Ajoutez dans TestController.java:
private final MultiChannelNotificationService multiChannelService;
public TestController(
NotificationService notificationService,
SendGridNotificationService sendGridNotificationService,
CompleteNotificationService completeService,
MultiChannelNotificationService multiChannelService
) {
this.notificationService = notificationService;
this.sendGridNotificationService = sendGridNotificationService;
this.completeService = completeService;
this.multiChannelService = multiChannelService;
}
@GetMapping("/all")
public String testAllImplem() {
multiChannelService.broadcastEmail("user@example.com",
"Message broadcast");
return "✅ Envoi via tous les providers - Voir console";
}
Testez:
http://localhost:8080/api/test/all
Console:
=== Envoi via TOUS les providers ===
[Gmail] Envoi email à user@example.com: Message broadcast
[SendGrid] Envoi email à user@example.com: Message broadcast
✅ Les 2 implémentations sont utilisées!
Exercice 5: Injection par Setter (Dépendances Optionnelles)
Objectif
Utiliser l'injection par setter pour des dépendances vraiment optionnelles.
Étape 5.1: Créer LoggerService avec Setter
Fichier: service/LoggerService.java
package com.example.injection.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoggerService {
private SmsService smsService; // ← Pas 'final', peut être null
// Injection par setter - OPTIONNELLE
@Autowired(required = false) // ← required = false
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
System.out.println("SMS Service injecté dans LoggerService");
}
public void logNotification(String message) {
System.out.println("[LOG] " + message);
if (smsService != null) {
System.out.println("[LOG] SMS Service disponible");
} else {
System.out.println("[LOG] SMS Service non disponible");
}
}
}
Points clés:
@Autowired(required = false)→ Pas obligatoire- Vérification
if (smsService != null)
Étape 5.2: Tester
Ajoutez dans TestController.java:
private final LoggerService loggerService;
@GetMapping("/setter")
public String testSetter() {
loggerService.logNotification("Test injection par setter");
return "✅ Injection par setter testée - Voir console";
}
Testez:
http://localhost:8080/api/test/setter
Console (avec SmsService):
SMS Service injecté dans LoggerService
[LOG] Test injection par setter
[LOG] SMS Service disponible
Récapitulatif: Comparaison des Méthodes
Tableau Comparatif
| Type | Syntaxe | Obligatoire? | Immutable? | Usage |
|---|---|---|---|---|
| Constructeur | public Service(Dep d) | ✅ Oui | ✅ Oui (final) | Recommandé pour dépendances obligatoires |
| Setter | @Autowired(required=false) | ❌ Non | ❌ Non | Dépendances optionnelles |
| Champ | @Autowired private Dep d | ⚠️ Oui | ❌ Non | ❌ À éviter |
Optional<T> | Optional<Dep> d | ❌ Non | ✅ Oui | Dépendances optionnelles modernes |
List<T> | List<Dep> all | ✅ Oui | ✅ Oui | Toutes les implémentations |
Code Final Complet
Votre projet devrait maintenant avoir:
✅ GmailService (@Primary)
✅ SendGridService
✅ NotificationService (constructeur + Gmail)
✅ SendGridNotificationService (constructeur + @Qualifier)
✅ CompleteNotificationService (Optional<SmsService>)
✅ SimpleSmsService (optionnel)
✅ MultiChannelNotificationService (List<EmailService>)
✅ LoggerService (setter + required=false)
Auto-Évaluation
Répondez aux questions suivantes:
- Pourquoi l'injection par constructeur est-elle préférée?
- Quelle est la différence entre @Primary et @Qualifier?
- Comment gérer une dépendance vraiment optionnelle?
- Comment injecter toutes les implémentations d'une interface?
- Quand utiliser
Optional<T>vs@Autowired(required=false)?
Si vous pouvez répondre à toutes ces questions, vous avez maîtrisé l'injection de dépendances! ✅
Challenge Bonus 🎯
Créez un système de notification multi-canaux:
- Interface
NotificationChannel - Implémentations:
EmailChannel,SmsChannel,PushChannel - Service
NotificationOrchestratorqui:- Injecte tous les canaux
- Permet d'activer/désactiver des canaux
- Envoie sur tous les canaux actifs
Temps estimé: 30 minutes
Prochaine Étape
Vous maîtrisez maintenant l'injection de dépendances! Passez au TP2 pour apprendre les Beans et les Scopes.