2026-11-07
À la fin de cette leçon, vous serez capable de :
Comment suivre cette leçon : ce document est le cœur conceptuel. Il s’accompagne d’un TP pratique (
02_java_CDI_P_cdi-injection.qmd) et d’une partie avancée optionnelle (02_java_CDI_L_advanced.qmd) pour les approfondissements.
CDI (Contexts and Dependency Injection) repose sur le design pattern d’inversion de contrôle (IoC) : c’est le conteneur, et non le code applicatif, qui crée, assemble et gère le cycle de vie des objets.
La spécification CDI, au cœur de Jakarta EE, fournit un cadre puissant pour la gestion déclarative des dépendances et des cycles de vie des objets au sein des applications Java.
Principe Fondamental : couplage faible avec un typage fort.
Un exemple de code simple utilisant CDI : sample-cdi
CDI résout des problèmes récurrents dans le développement d’applications Java :
Sans CDI
Avec CDI
@Inject@RequestScoped, @ApplicationScoped, etc.)@Qualifier) et alternatives (@Alternative)CDI naît en 2009 avec la version 1.0 (Java EE 6, JSR 299), qui standardise l’injection de dépendances en s’inspirant de Spring. La version 2.0 (2017, Java EE 8) introduit les événements asynchrones (@ObservesAsync) et le support Java SE. La version 4.0 (2022, Jakarta EE 10) apporte CDI Lite, un sous-ensemble optimisé pour la compilation native, ainsi que des extensions compatibles au build.
Un bean CDI est un composant géré par le conteneur CDI qui respecte certaines règles et conventions.
Un bean CDI est défini par un contrat d’injection comprenant :
class PayPalService implements PaymentService), qui permettent l’injection par type.@PayPal, @CreditCard), permettant une injection spécifique.@ApplicationScoped, @RequestScoped) et gère automatiquement sa création/destruction.@Named("paypal") → #{paypal} dans JSF).CDI définit techniquement un bean comme :
✅ Bean valide si :
@Inject :❌ Pas un bean si :
@Produces)@Produces)Les scopes définissent la durée de vie et la visibilité des beans CDI.
| Scope | Durée de vie | Cas d’usage | Exemple |
|---|---|---|---|
@Dependent |
Même durée que le bean consommateur | Bean utilitaire, pas de partage | Calculateur, validateur |
@RequestScoped |
Une requête HTTP | Données formulaire, requête REST | Formulaire JSF, DTO REST |
@SessionScoped |
Session utilisateur | Données utilisateur persistantes | Panier e-commerce, préférences |
@ApplicationScoped |
Application entière | Configuration, services partagés | Cache, configuration globale |
@ConversationScoped |
Conversation utilisateur multi-requêtes | Processus métier multi-étapes | Tunnel de commande, wizard |
@Dependent.@Dependent n’est jamais partagé : chaque injection crée une nouvelle instance.La passivation/activation (@PrePassivate/@PostActivate) des beans @SessionScoped/@ConversationScoped est abordée en partie avancée.
L’injection de dépendances est le mécanisme central de CDI : le conteneur fournit automatiquement les dépendances d’un bean.
L’injection par setter (@Inject sur un setter) permet des dépendances optionnelles, mais le bean peut se trouver dans un état incomplet ; elle est rarement utilisée.
CDI injecte par défaut en se basant sur le type de l’attribut ou du paramètre.
public interface PaymentService {
void processPayment(double amount);
}
@ApplicationScoped
public class CreditCardPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Paiement par carte : " + amount);
}
}
@ApplicationScoped
public class OrderService {
@Inject
private PaymentService paymentService; // Type = PaymentService
public void checkout(double amount) {
paymentService.processPayment(amount);
}
}Règle CDI : le conteneur cherche un bean dont le type correspond à PaymentService.
@NamedL’annotation @Named permet de donner un nom à un bean pour le référencer dans Expression Language (EL), notamment dans les pages JSF.
Utilisation dans JSF :
Nom par défaut : si @Named est sans valeur, le nom est celui de la classe avec la première lettre minuscule (ex. ShoppingCartBean → shoppingCartBean).
Lorsqu’on injecte une dépendance avec @Inject, CDI recherche un bean correspondant au contrat d’injection : contrat = Type + Qualifiers + @Default. Le bean injecté doit :
@Alternative non activé — voir la leçon avancée, beans alternatifs)Cas particulier : si aucun qualifier n’est spécifié, toute injection utilise par défaut le qualifier implicite @Default (détaillé dans la section Qualifiers ci-dessous).
Les initializer methods sont des méthodes annotées @Inject : @Inject peut donc cibler non seulement les champs et le constructeur, mais aussi des méthodes. CDI établit un ordre d’initialisation fixé (constructeur, puis injection des champs et des méthodes, enfin @PostConstruct) ; l’ordre complet et ses détails sont traités en partie avancée.
TP : la mise en place de bout en bout d’une première application CDI (projet Quarkus, bean, injection par constructeur, scopes, qualifiers, événements) fait l’objet du TP dédié : « Premiers pas avec CDI » (
02_java_CDI_P_cdi-injection.qmd).
Que se passe-t-il si plusieurs implémentations d’une même interface existent ?
public interface PaymentService {
void processPayment(double amount);
}
@ApplicationScoped
public class PayPalPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("💳 Paiement PayPal : " + amount);
}
}
@ApplicationScoped
public class CreditCardPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("💳 Paiement Carte : " + amount);
}
}
@ApplicationScoped
public class OrderService {
@Inject
private PaymentService paymentService; // ❌ Ambiguïté !
}CDI lève une exception : AmbiguousResolutionException — deux beans correspondent au type PaymentService, CDI ne sait pas lequel injecter.
Trois solutions :
Les qualifiers permettent de distinguer différentes implémentations d’une même interface.
Un qualifier est une annotation custom, déclarée avec @Qualifier :
@DefaultLorsqu’aucun qualifier n’est spécifié, CDI utilise le qualifier implicite @Default : @Inject private PaymentService service; est équivalent à @Inject @Default private PaymentService service;.
Règle importante : un bean portant un qualifier personnalisé (ex. @PayPal) n’a pas @Default ; pour qu’un bean ait @Default, il ne doit porter aucun qualifier explicite.
Les qualifiers avec valeurs (String/
value) et les combinaisons de qualifiers → partie avancée.
Les événements permettent une communication découplée entre composants : un émetteur envoie un événement, des observateurs réagissent, sans que l’émetteur connaisse les observateurs.
Avantages : ✅ couplage faible (émetteur ≠ observateurs) · ✅ extensibilité (ajouter des observateurs sans modifier l’émetteur) · ✅ communication asynchrone possible.
// Classe d'événement (POJO)
public class OrderCreatedEvent {
private final String orderId;
private final double amount;
public OrderCreatedEvent(String orderId, double amount) {
this.orderId = orderId;
this.amount = amount;
}
// getters
}
@ApplicationScoped
public class OrderService {
@Inject
private Event<OrderCreatedEvent> orderEvent;
public void createOrder(String orderId, double amount) {
// Logique métier...
orderEvent.fire(new OrderCreatedEvent(orderId, amount));
}
}@Observes)Les observateurs synchrones sont invoqués dans le même thread que l’émetteur :
Résultat :
⚠️ Attention : si un observateur lève une exception, les suivants ne sont pas exécutés.
@ObservesAsync)Les observateurs asynchrones sont invoqués dans un thread séparé (on peut aussi émettre de façon asynchrone avec fireAsync) :
Les événements qualifiés et l’observation conditionnelle → partie avancée. L’observation d’événements à des phases transactionnelles (
TransactionPhase) relève de la leçon 03 (Transactions) et de la partie avancée.
@Inject (constructeur, field, setter)@ApplicationScoped, @RequestScoped, @SessionScoped, @Dependent@Qualifier)@Default : qualifier implicite si aucun qualifier personnalisé@Named : nommer un bean pour l’EL (JSF)@Observes, @ObservesAsync)Les concepts avancés (stereotypes, producers, intercepteurs, décorateurs) et les implémentations (Weld, Quarkus ArC) → partie avancée.
Dépendances finales, explicites et faciles à tester — voir la section « Injection de Dépendances ».
Les bonnes pratiques avancées (
@Alternative, producers/@Disposes, documentation des stereotypes) → partie avancée.
Partie avancée : les approfondissements (qualifiers avec valeurs, beans alternatifs, producers, intercepteurs, décorateurs, événements avancés, stereotypes) sont traités dans la leçon optionnelle (
02_java_CDI_L_advanced.qmd).
E. Bruno - CDI : Inversion de contrôle et injection de dépendances