2026-11-07
Cette leçon rassemble les mécanismes CDI qui dépassent le socle de base : qualifiers avancés, beans alternatifs, producers, intercepteurs, décorateurs, événements avancés et stereotypes. Elle prolonge directement Introduction à CDI, où le modèle d’injection, les scopes et les qualifiers de base ont été posés.
Note
Partie optionnelle. Cette leçon approfondit les mécanismes CDI au-delà du socle de base. Elle suppose que vous avez lu Introduction à CDI et n’est pas requise pour la suite du cours.
Les sections ci-dessous sont largement indépendantes les unes des autres : lisez-les dans l’ordre, ou accédez directement à celle qui vous intéresse.
Les qualifiers de base (@Qualifier, @Default) ont été posés dans l’introduction ; cette section exploite leur expressivité : qualifiers porteurs d’une valeur, et combinaison de qualifiers pour une injection fine.
Les qualifiers peuvent avoir des valeurs pour plus de flexibilité :
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD, PARAMETER})
public @interface PaymentMethod {
PaymentType value();
enum PaymentType {
CREDIT_CARD, PAYPAL, BANK_TRANSFER
}
}
@PaymentMethod(PaymentType.PAYPAL)
@ApplicationScoped
public class PayPalPaymentService implements PaymentService {
// ...
}
@ApplicationScoped
public class OrderService {
@Inject
@PaymentMethod(PaymentType.PAYPAL)
private PaymentService paypalService;
}Plusieurs qualifiers peuvent être combinés pour une injection encore plus précise :
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface Secure {}
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface FastProcessing {}
@PayPal
@Secure
@FastProcessing
@ApplicationScoped
public class SecureFastPayPalService implements PaymentService {
// ...
}
@ApplicationScoped
public class PremiumOrderService {
@Inject
@PayPal
@Secure
@FastProcessing
private PaymentService paymentService; // Injection précise!
}Les beans alternatifs permettent de remplacer une implémentation par une autre selon l’environnement (développement, test, production) sans modifier le code source.
// Bean par défaut (production)
@ApplicationScoped
public class ProductionPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
// Appel réel à l'API de paiement
realPaymentGateway.charge(amount);
}
}
// Bean alternatif (test/développement)
@Alternative
@ApplicationScoped
public class MockPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("🧪 [MOCK] Paiement simulé : " + amount);
}
}Pour activer un bean alternatif, déclarez-le dans beans.xml :
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0" bean-discovery-mode="annotated">
<alternatives>
<class>com.example.MockPaymentService</class>
</alternatives>
</beans>Résultat : MockPaymentService remplace ProductionPaymentService partout
CDI 4.0+ permet d’utiliser @Priority pour activer les alternatives sans beans.xml :
Plus la priorité est élevée, plus l’alternative est prioritaire
Les producers permettent de créer des beans de manière programmatique, utile pour : - Objets non-CDI (bibliothèques tierces) - Logique de création complexe - Configuration dynamique
@ApplicationScoped
public class LoggerProducer {
@Produces
public Logger createLogger(InjectionPoint injectionPoint) {
// Création personnalisée basée sur le point d'injection
Class<?> targetClass = injectionPoint.getMember().getDeclaringClass();
return Logger.getLogger(targetClass.getName());
}
}
@ApplicationScoped
public class OrderService {
@Inject
private Logger logger; // Logger créé par producer avec le bon nom
public void createOrder() {
logger.info("Commande créée"); // [OrderService] Commande créée
}
}Pour les cas simples, un champ peut produire un bean :
@ApplicationScoped
public class ConfigurationProducer {
@Produces
private final String apiUrl = System.getProperty("api.url", "http://localhost:8080");
@Produces
@Named("appVersion")
private final String version = "1.0.0";
}
@ApplicationScoped
public class ApiClient {
@Inject
private String apiUrl; // Injecté depuis producer field
}Un disposer permet de nettoyer les ressources créées par un producer :
@ApplicationScoped
public class DatabaseProducer {
@Produces
@ApplicationScoped
public EntityManager createEntityManager() {
return Persistence.createEntityManagerFactory("myPU")
.createEntityManager();
}
public void closeEntityManager(@Disposes EntityManager em) {
if (em.isOpen()) {
em.close();
}
}
}Les intercepteurs permettent d’ajouter des comportements transversaux (cross-cutting concerns) à des méthodes sans modifier leur code source.
L’interception insère du code avant, après ou autour de l’exécution d’une méthode :
Cas d’usage typiques : - 📝 Journalisation (logging) - 🔒 Sécurité (vérification permissions) - 💾 Transactions - ⏱️ Mesure de performance - ✅ Validation
Étape 1 : Définir l’annotation d’interception
Étape 2 : Implémenter l’intercepteur
@Interceptor
@Logged
@Priority(Interceptor.Priority.APPLICATION)
public class LoggingInterceptor {
@Inject
private Logger logger;
@AroundInvoke
public Object logMethod(InvocationContext ctx) throws Exception {
String methodName = ctx.getMethod().getName();
logger.info("→ Entrée dans " + methodName);
long start = System.currentTimeMillis();
try {
Object result = ctx.proceed(); // Appel méthode cible
return result;
} finally {
long duration = System.currentTimeMillis() - start;
logger.info("← Sortie de " + methodName + " (" + duration + "ms)");
}
}
}Étape 3 : Appliquer l’intercepteur
Résultat :
CDI offre plusieurs points d’interception :
| Annotation | Point d’interception | Usage |
|---|---|---|
@AroundConstruct |
Autour de la construction | Initialisation custom |
@PostConstruct |
Après construction | Setup initial |
@AroundInvoke |
Autour d’un appel de méthode | Logging, transactions |
@PreDestroy |
Avant destruction | Nettoyage |
@AroundTimeout |
Autour d’un timeout (EJB) | Gestion timeouts |
Plusieurs intercepteurs peuvent être appliqués :
Ordre d’exécution déterminé par @Priority (plus petit = plus tôt)
Les décorateurs permettent d’étendre dynamiquement le comportement d’un bean en implémentant la même interface.
Le décorateur enveloppe l’objet original et peut modifier son comportement :
Différence avec les intercepteurs :
| Aspect | Intercepteur | Décorateur |
|---|---|---|
| Implémente interface | ❌ Non | ✅ Oui |
| Peut modifier params/résultat | ⚠️ Limité | ✅ Oui |
| Application | Annotation sur méthode/classe | Délégation explicite |
| Use case | Aspects transversaux (logging, sécurité) | Extension comportement métier |
public interface PaymentService {
boolean processPayment(double amount);
}
@ApplicationScoped
public class BasicPaymentService implements PaymentService {
@Override
public boolean processPayment(double amount) {
System.out.println("💳 Paiement de " + amount + "€");
return true;
}
}
@Decorator
@Priority(Interceptor.Priority.APPLICATION)
public abstract class PaymentLoggingDecorator implements PaymentService {
@Inject
@Delegate
@Any
private PaymentService delegate; // Bean original
@Inject
private Logger logger;
@Override
public boolean processPayment(double amount) {
logger.info("🔔 Tentative de paiement : " + amount + "€");
boolean result = delegate.processPayment(amount); // Appel bean original
if (result) {
logger.info("✅ Paiement réussi");
} else {
logger.warning("❌ Paiement échoué");
}
return result;
}
}Avec beans.xml :
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0" bean-discovery-mode="annotated">
<decorators>
<class>com.example.PaymentLoggingDecorator</class>
</decorators>
</beans>Ou avec @Priority (CDI 2.0+) :
@Decorator
@Priority(Interceptor.Priority.APPLICATION + 10)
public abstract class PaymentValidationDecorator implements PaymentService {
@Inject
@Delegate
@Any
private PaymentService delegate;
@Override
public boolean processPayment(double amount) {
// Validation avant délégation
if (amount <= 0) {
throw new IllegalArgumentException("Montant invalide : " + amount);
}
if (amount > 10000) {
throw new IllegalArgumentException("Montant trop élevé : " + amount);
}
return delegate.processPayment(amount);
}
}Plusieurs décorateurs peuvent être chaînés :
Ordre déterminé par @Priority ou ordre dans beans.xml
Au-delà de l’émission et de l’observation de base, CDI raffine les événements : qualifiers, filtrage conditionnel et phases transactionnelles.
Les événements peuvent être qualifiés pour filtrer les observateurs :
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, PARAMETER})
public @interface Important {}
// Émission qualifiée
@Inject
@Important
private Event<OrderCreatedEvent> importantOrderEvent;
public void createVIPOrder(String orderId, double amount) {
importantOrderEvent.fire(new OrderCreatedEvent(orderId, amount));
}
// Observation qualifiée
public void onImportantOrder(@Observes @Important OrderCreatedEvent event) {
System.out.println("🔔 Commande VIP importante!");
}Les observateurs peuvent filtrer les événements :
Dans un contexte transactionnel (EJB/JTA), les événements peuvent être observés à différentes phases :
Les stereotypes permettent de combiner plusieurs annotations CDI en une seule annotation réutilisable, simplifiant le code et standardisant les patterns.
Au lieu de :
On peut écrire :
Les stereotypes peuvent inclure interceptors :
CDI définit quelques stereotypes standards :
@Model = @Named + @RequestScoped (pour controllers MVC)Deux implémentations méritent d’être nommées :
beans.xml n’est requis.Quelques règles qui prennent tout leur sens une fois les mécanismes avancés en place :
@Alternative pour les TestsE. Bruno - CDI : Concepts Avancés