CDI : Concepts Avancés
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.
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.
Qualifiers avancés
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.
Qualifiers avec Valeurs
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;
}Combinaison de Qualifiers
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!
}Beans Alternatifs
Les beans alternatifs permettent de remplacer une implémentation par une autre selon l’environnement (développement, test, production) sans modifier le code source.
Définition d’un Bean Alternatif
// 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);
}
}Activation dans beans.xml
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
Alternatives avec Priorité
CDI 4.0+ permet d’utiliser @Priority pour activer les alternatives sans beans.xml :
@Alternative
@Priority(100)
@ApplicationScoped
public class MockPaymentService implements PaymentService {
// ...
}Plus la priorité est élevée, plus l’alternative est prioritaire
Cas d’Usage
- 🧪 Tests : Remplacer services externes par des mocks
- 🔧 Développement : Simuler des services coûteux
- 🌍 Environnements : Basculer entre configurations (dev/staging/prod)
Producers
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
Producer Method
@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
}
}Producer Field
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
}Producer avec Qualifier
public class RandomProducer {
private Random random = new Random();
@Produces
@RandomNumber
public int produceRandomInt() {
return random.nextInt(100);
}
}
@ApplicationScoped
public class LotteryService {
@Inject
@RandomNumber
private int luckyNumber; // Nombre aléatoire injecté
}Disposer Method
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();
}
}
}Cas d’Usage
- 📚 Bibliothèques tierces : Créer des beans pour des classes non-CDI
- ⚙️ Configuration : Injecter des valeurs de configuration
- 🔧 Création complexe : Logique de création nécessitant plusieurs étapes
- 🎲 Objets dynamiques : Valeurs calculées à chaque injection
Intercepteurs
Les intercepteurs permettent d’ajouter des comportements transversaux (cross-cutting concerns) à des méthodes sans modifier leur code source.
Design Pattern d’Interception
L’interception insère du code avant, après ou autour de l’exécution d’une méthode :
Client → Intercepteur → Méthode Cible → Intercepteur → Client
[Avant] [Exécution] [Après]
Cas d’usage typiques : - 📝 Journalisation (logging) - 🔒 Sécurité (vérification permissions) - 💾 Transactions - ⏱️ Mesure de performance - ✅ Validation
Création d’un Intercepteur
Étape 1 : Définir l’annotation d’interception
@InterceptorBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Logged {}É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
@ApplicationScoped
public class OrderService {
@Logged // Méthode interceptée
public void createOrder(String productId) {
// Logique métier
System.out.println("Création commande : " + productId);
}
}Résultat :
→ Entrée dans createOrder
Création commande : ABC123
← Sortie de createOrder (15ms)
Annotations de Cycle de Vie
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 |
Intercepteurs Multiples
Plusieurs intercepteurs peuvent être appliqués :
@Logged
@Transactional
@Secured
@ApplicationScoped
public class OrderService {
public void createOrder(String productId) {
// Ordre d'exécution : Secured → Transactional → Logged → méthode
}
}Ordre d’exécution déterminé par @Priority (plus petit = plus tôt)
Décorateurs
Les décorateurs permettent d’étendre dynamiquement le comportement d’un bean en implémentant la même interface.
Design Pattern Décorateur
Le décorateur enveloppe l’objet original et peut modifier son comportement :
Client → Décorateur → Bean Original
[Logique +] [Logique métier]
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 |
Création d’un Décorateur
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;
}
}Activation des Décorateurs
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)
public abstract class PaymentLoggingDecorator implements PaymentService {
// Activé automatiquement
}Exemple : Décorateur de Validation
@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);
}
}Décorateurs en Chaîne
Plusieurs décorateurs peuvent être chaînés :
Client → ValidationDecorator → LoggingDecorator → BasicPaymentService
[Valide] [Log] [Traite]
Ordre déterminé par @Priority ou ordre dans beans.xml
Événements avancés
Au-delà de l’émission et de l’observation de base, CDI raffine les événements : qualifiers, filtrage conditionnel et phases transactionnelles.
Événements Qualifiés
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!");
}Événements Conditionnels
Les observateurs peuvent filtrer les événements :
@ApplicationScoped
public class LargeOrderService {
public void onLargeOrder(@Observes OrderCreatedEvent event) {
if (event.getAmount() > 1000) {
System.out.println("⚠️ Commande importante : " + event.getAmount() + "€");
}
}
}Transaction Phases (Java EE)
Dans un contexte transactionnel (EJB/JTA), les événements peuvent être observés à différentes phases :
public void afterSuccess(@Observes(during = TransactionPhase.AFTER_SUCCESS)
OrderCreatedEvent event) {
// Exécuté seulement si transaction réussit
}
public void afterFailure(@Observes(during = TransactionPhase.AFTER_FAILURE)
OrderCreatedEvent event) {
// Exécuté seulement si transaction échoue
}Stereotypes
Les stereotypes permettent de combiner plusieurs annotations CDI en une seule annotation réutilisable, simplifiant le code et standardisant les patterns.
Création d’un Stereotype
@Stereotype
@Named
@RequestScoped
@Retention(RUNTIME)
@Target(TYPE)
public @interface WebController {}Utilisation d’un Stereotype
Au lieu de :
@Named
@RequestScoped
public class OrderController {
// ...
}On peut écrire :
@WebController
public class OrderController {
// ...
}Stereotypes Avancés
Les stereotypes peuvent inclure interceptors :
@Stereotype
@ApplicationScoped
@Transactional
@Logged
@Retention(RUNTIME)
@Target(TYPE)
public @interface Service {}
// Usage
@Service
public class OrderService {
// Automatiquement @ApplicationScoped + @Transactional + @Logged
}Stereotypes Prédéfinis
CDI définit quelques stereotypes standards :
@Model=@Named+@RequestScoped(pour controllers MVC)
@Model
public class ProductFormController {
// Équivalent à @Named @RequestScoped
}Implémentations CDI
Deux implémentations méritent d’être nommées :
- Weld — l’implémentation de référence de la spécification CDI, fournie pour Java SE et Jakarta EE (utilisée par WildFly, Payara, GlassFish).
- ArC — le mécanisme CDI de Quarkus : l’injection y est résolue au build-time (code généré, sans réflexion) et aucun fichier
beans.xmln’est requis.
Bonnes Pratiques avancées
Quelques règles qui prennent tout leur sens une fois les mécanismes avancés en place :
1. Utilisez @Alternative pour les Tests
// Production
@ApplicationScoped
public class RealEmailService implements EmailService { }
// Test
@Alternative
@Priority(100)
@ApplicationScoped
public class MockEmailService implements EmailService { }2. Gérez les Ressources avec Producer/Disposer
@ApplicationScoped
public class DatabaseProducer {
@Produces
@ApplicationScoped
public DataSource createDataSource() {
// Création
}
public void closeDataSource(@Disposes DataSource ds) {
// Nettoyage
}
}3. Documentez les Qualifiers et Stereotypes
/**
* Qualifier pour les services de paiement rapide.
* Ces services garantissent un traitement en < 1s.
*/
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface FastProcessing { }Ressources
- 📚 Jakarta CDI Specification
- 📖 Weld Documentation
- 🔧 CDI TCK — Test Compatibility Kit
- 🛠️ Arquillian — Testing CDI applications