Chapitre 2: Injection de Dépendances (DI)
Introduction
Dans le Chapitre 1, vous avez compris POURQUOI l'IoC existe. Maintenant, vous allez apprendre COMMENT Spring injecte les dépendances.
Ce que vous allez apprendre
- Les 3 types d'injection de dépendances
- Pourquoi l'injection par constructeur est préférée
- Comment utiliser
@Autowired,@Inject,@Resource - Gérer les dépendances optionnelles
Durée: 45 minutes
Les 3 Types d'Injection
Spring propose 3 façons d'injecter des dépendances:
- Injection par Constructeur ✅ (Recommandée)
- Injection par Setter
- Injection par Champ ⚠️ (Déconseillée)
Voyons chacune en détail.
1. Injection par Constructeur ✅
Syntaxe
@Service
public class OrderService {
// Dépendances déclarées comme 'final'
private final EmailService emailService;
private final PaymentService paymentService;
// Injection via le constructeur
public OrderService(EmailService emailService,
PaymentService paymentService) {
this.emailService = emailService;
this.paymentService = paymentService;
}
}
Comment Ça Marche?
Au démarrage de l'application:
1. Spring trouve OrderService
↓
2. Spring voit le constructeur avec 2 paramètres
↓
3. Spring crée EmailService
↓
4. Spring crée PaymentService
↓
5. Spring crée OrderService en passant les 2 dépendances
↓
6. OrderService est prêt!
Avantages de l'Injection par Constructeur
Avantage 1: Immutabilité
private final EmailService emailService;
// ^^^^^
// Impossible de modifier après la création!
Pourquoi c'est bien?
- ✅ Thread-safe (pas de problèmes de concurrence)
- ✅ Prévisible (ne peut pas changer)
- ✅ Moins de bugs
Avantage 2: Dépendances Obligatoires
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
Si Spring ne trouve pas EmailService:
❌ Application refuse de démarrer!
❌ Erreur claire au démarrage
C'est BIEN! Vous découvrez le problème immédiatement, pas en production!
Avantage 3: Facile à Tester
@Test
public void testOrderService() {
// Créer des fakes
EmailService fakeEmail = new FakeEmailService();
PaymentService fakePayment = new FakePaymentService();
// Injecter facilement via le constructeur
OrderService service = new OrderService(fakeEmail, fakePayment);
// Tester
service.processOrder(new Order());
}
Pas besoin de Spring pour tester!
Avantage 4: Pas Besoin de @Autowired
Depuis Spring 4.3, @Autowired est optionnel sur les constructeurs:
@Service
public class OrderService {
private final EmailService emailService;
// Pas besoin de @Autowired!
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
Spring détecte automatiquement le constructeur!
Résumé: Injection par Constructeur
✅ Dépendances immutables (final)
✅ Dépendances obligatoires
✅ Facile à tester
✅ Pas besoin de @Autowired
✅ Recommandée par Spring
👉 UTILISEZ TOUJOURS L'INJECTION PAR CONSTRUCTEUR!
2. Injection par Setter
Syntaxe
@Service
public class OrderService {
// Dépendance NON final
private EmailService emailService;
// Injection via un setter
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
Comment Ça Marche?
1. Spring crée OrderService (constructeur vide)
↓
2. Spring crée EmailService
↓
3. Spring appelle setEmailService(emailService)
↓
4. OrderService est prêt
Quand Utiliser?
Cas d'usage: Dépendances optionnelles
@Service
public class OrderService {
private EmailService emailService;
private SmsService smsService; // Optionnel
@Autowired(required = false) // ← Optionnel!
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}
public void notifyCustomer(String message) {
// Email toujours envoyé
emailService.send(message);
// SMS seulement si disponible
if (smsService != null) {
smsService.send(message);
}
}
}
Inconvénients
❌ Dépendances mutables (pas 'final')
❌ Peut être null (risque de NullPointerException)
❌ Objet créé dans un état incomplet
❌ Plus difficile à tester
Utilisez seulement pour des dépendances optionnelles!
3. Injection par Champ ⚠️
Syntaxe
@Service
public class OrderService {
// Injection directe dans le champ
@Autowired
private EmailService emailService;
@Autowired
private PaymentService paymentService;
}
Comment Ça Marche?
1. Spring crée OrderService
↓
2. Spring utilise la RÉFLEXION pour accéder aux champs privés
↓
3. Spring injecte les dépendances directement
↓
4. OrderService est prêt
Pourquoi C'est Déconseillé?
Problème 1: Impossible de Rendre 'final'
@Autowired
private EmailService emailService; // ❌ Pas 'final' possible
Problème 2: Difficile à Tester
@Test
public void testOrderService() {
OrderService service = new OrderService();
// ❌ Comment injecter les dépendances?
// Les champs sont privés!
// Il faut utiliser la réflexion... compliqué!
}
Problème 3: Dépendances Cachées
@Service
public class OrderService {
@Autowired
private Dep1 dep1;
@Autowired
private Dep2 dep2;
@Autowired
private Dep3 dep3;
@Autowired
private Dep4 dep4;
@Autowired
private Dep5 dep5;
// ... 10 dépendances!
}
Problème: On ne voit pas facilement toutes les dépendances!
Avec constructeur:
public OrderService(Dep1 d1, Dep2 d2, Dep3 d3, Dep4 d4, Dep5 d5) {
// ❌ WOW! Trop de dépendances!
// ➡️ Signal que la classe fait trop de choses
}
Résumé: Injection par Champ
❌ Pas immutable
❌ Difficile à tester
❌ Cache les problèmes de design
❌ Utilise la réflexion (plus lent)
👉 ÉVITEZ L'INJECTION PAR CHAMP!
Comparaison des 3 Types
| Critère | Constructeur | Setter | Champ |
|---|---|---|---|
| Immutabilité (final) | ✅ Oui | ❌ Non | ❌ Non |
| Dépendances obligatoires | ✅ Oui | ❌ Non | ⚠️ Pas clair |
| Facile à tester | ✅ Oui | ⚠️ Moyen | ❌ Non |
| Lisibilité | ✅ Claire | ✅ Claire | ⚠️ Cachée |
| @Autowired nécessaire | ❌ Non | ✅ Oui | ✅ Oui |
| Recommandation | ✅ UTILISEZ | ⚠️ Optionnel | ❌ ÉVITEZ |
Annotations d'Injection
Spring supporte 3 annotations pour l'injection:
1. @Autowired (Spring)
@Service
public class OrderService {
private final EmailService emailService;
@Autowired // Annotation Spring
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
Origine: Spring Framework
Utilisation: La plus courante
2. @Inject (Java Standard)
import jakarta.inject.Inject;
@Service
public class OrderService {
private final EmailService emailService;
@Inject // Annotation Jakarta (Java standard)
public OrderService(EmailService emailService) {
this.emailService = emailService;
}
}
Origine: Jakarta EE (anciennement Java EE)
Utilisation: Si vous voulez du code portable (pas lié à Spring)
3. @Resource (Java Standard)
import jakarta.annotation.Resource;
@Service
public class OrderService {
@Resource(name = "emailService") // Injection par nom
private EmailService emailService;
}
Origine: Jakarta EE
Différence: Injecte par nom au lieu du type
Laquelle Choisir?
Recommandation: @Autowired (ou aucune pour constructeur)
Pourquoi?
✅ Standard Spring
✅ Optionnel sur constructeur
✅ Plus d'options (required, @Qualifier)
✅ Mieux documentée
Gestion de Plusieurs Implémentations
Problème
public interface EmailService {
void send(String to, String message);
}
@Service
public class GmailService implements EmailService { ... }
@Service
public class SendGridService implements EmailService { ... }
// ❌ Spring ne sait pas laquelle choisir!
Solution 1: @Primary
@Service
@Primary // ← Cette implémentation par défaut
public class GmailService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("Envoi via Gmail à " + to);
}
}
@Service
public class SendGridService implements EmailService {
@Override
public void send(String to, String message) {
System.out.println("Envoi via SendGrid à " + to);
}
}
Spring utilisera GmailService par défaut!
Solution 2: @Qualifier
@Service
public class OrderService {
private final EmailService emailService;
public OrderService(@Qualifier("sendGridService") EmailService emailService) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Nom spécifique du bean
this.emailService = emailService;
}
}
Spring utilisera spécifiquement SendGridService!
Solution 3: Nommage Explicite
@Service("gmailEmailService") // ← Nom explicite
public class GmailService implements EmailService { ... }
@Service("sendGridEmailService") // ← Nom explicite
public class SendGridService implements EmailService { ... }
// Utilisation
public OrderService(@Qualifier("sendGridEmailService") EmailService service) {
this.emailService = service;
}
Dépendances Optionnelles
Avec @Autowired(required = false)
@Service
public class NotificationService {
private final EmailService emailService;
private SmsService smsService; // Optionnel
// Obligatoire
public NotificationService(EmailService emailService) {
this.emailService = emailService;
}
// Optionnel
@Autowired(required = false)
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}
public void notify(String message) {
emailService.send(message); // Toujours
if (smsService != null) {
smsService.send(message); // Si disponible
}
}
}
Avec Optional<T>
import java.util.Optional;
@Service
public class NotificationService {
private final EmailService emailService;
private final Optional<SmsService> smsService;
public NotificationService(
EmailService emailService,
Optional<SmsService> smsService // ← Wrapper Optional
) {
this.emailService = emailService;
this.smsService = smsService;
}
public void notify(String message) {
emailService.send(message);
smsService.ifPresent(sms -> sms.send(message));
// ^^^^^^^^^
// Appelé seulement si présent
}
}
Exemple Complet
// Interface
public interface PaymentProcessor {
void process(Order order);
}
// Implémentation 1
@Service
@Primary // ← Par défaut
public class StripePaymentProcessor implements PaymentProcessor {
@Override
public void process(Order order) {
System.out.println("Paiement via Stripe: " + order.getTotal());
}
}
// Implémentation 2
@Service("paypalProcessor")
public class PayPalPaymentProcessor implements PaymentProcessor {
@Override
public void process(Order order) {
System.out.println("Paiement via PayPal: " + order.getTotal());
}
}
// Utilisation - Option 1: Utiliser @Primary
@Service
public class OrderService {
private final PaymentProcessor paymentProcessor;
// Spring injecte StripePaymentProcessor (@Primary)
public OrderService(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
}
// Utilisation - Option 2: Spécifier avec @Qualifier
@Service
public class SpecialOrderService {
private final PaymentProcessor paymentProcessor;
// Force l'utilisation de PayPal
public SpecialOrderService(
@Qualifier("paypalProcessor") PaymentProcessor paymentProcessor
) {
this.paymentProcessor = paymentProcessor;
}
}
Résumé du Chapitre
Les 3 Types d'Injection
// 1. Constructeur ✅ RECOMMANDÉ
public MyService(Dependency dep) {
this.dep = dep;
}
// 2. Setter ⚠️ Pour dépendances optionnelles
@Autowired(required = false)
public void setDependency(Dependency dep) {
this.dep = dep;
}
// 3. Champ ❌ À ÉVITER
@Autowired
private Dependency dep;
Choix d'Implémentation
// Option 1: @Primary
@Primary
@Service
public class DefaultImpl implements MyInterface { }
// Option 2: @Qualifier
public MyService(@Qualifier("specificImpl") MyInterface service) { }
Points Clés
✅ Toujours utiliser l'injection par constructeur
✅ Déclarer les dépendances comme final
✅ @Autowired optionnel sur constructeur
✅ @Primary pour implémentation par défaut
✅ @Qualifier pour choix spécifique
⚠️ Setter seulement pour dépendances optionnelles
❌ Éviter l'injection par champ
Auto-Évaluation
- Quels sont les 3 types d'injection?
- Pourquoi l'injection par constructeur est-elle préférée?
- Quelle est la différence entre @Primary et @Qualifier?
- Comment rendre une dépendance optionnelle?
Prochaine Étape
Vous savez maintenant COMMENT injecter des dépendances. Passons à la création et la gestion des Beans!