2024-11-13
La spécification CDI (Contexts and Dependency Injection), 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.
@Inject
.@Qualifier
) pour distinguer les implémentations.Injection de Dépendances : CDI permet l’injection automatique de dépendances entre les composants à l’aide d’annotations et d’un conteneur d’injection, éliminant ainsi le besoin de code de configuration explicite.
Gestion des Contextes (Scopes) : La spécification offre une gestion fine des contextes de beans, contrôlant le cycle de vie des objets en fonction de leur portée telle que l’application, la session, la requête, etc.
Intercepteurs et Décorateurs : CDI fournit des mécanismes pour intercepter les appels de méthodes (intercepteurs) et pour enrichir dynamiquement les composants existants (décorateurs), permettant l’ajout de fonctionnalités transversales comme la journalisation, la sécurité ou la gestion transactions.
Faible Couplage entre les Composants : En favorisant l’injection de dépendances, CDI réduit le couplage entre les différentes parties de l’application, ce qui améliore la maintenabilité et l’évolutivité du code.
Amélioration de la Testabilité : Les applications développées avec CDI sont plus faciles à tester, car les dépendances peuvent être aisément simulées ou remplacées dans des environnements de test.
Extensibilité et Flexibilité : Grâce aux intercepteurs, décorateurs et événements, CDI permet d’étendre les fonctionnalités des composants sans modifier leur code source, facilitant l’intégration de nouvelles exigences métiers.
Gestion Automatisée des Cycles de Vie : Le conteneur CDI prend en charge la création, la mise en contexte et la destruction des objets, ce qui simplifie le développement d’applications complexes en déléguant ces responsabilités au framework.
Principe Fondamental : Couplage faible avec un typage fort
Ancêtres de CDI
Grandes Évolutions de CDI
javax
à jakarta
.Simplification du Développement : CDI a transformé la manière dont les développeurs gèrent les dépendances et les cycles de vie des objets.
Adoption par la Communauté : CDI est largement adopté dans l’écosystème Java, avec un support étendu dans les frameworks et les bibliothèques.
@RequestScoped
est créé et détruit avec chaque requête HTTP.@Inject
pour l’injection de dépendances, @Named
pour nommer les beans, @Produces
pour les méthodes productrices, etc.@RequestScoped
(durée de vie limitée à une requête HTTP), @SessionScoped
(durée de vie limitée à une session utilisateur), @ApplicationScoped
(durée de vie limitée à l’application entière), etc.@Qualifier
) pour distinguer les différentes implémentations d’une même interface, permettant une injection plus flexible et contextuelle.CDI donne une définition claire d’un bean, qui peut être l’un des suivants :
@Inject
: Un bean peut être une classe qui possède un constructeur annoté avec @Inject
, permettant l’injection de dépendances.@Produces
.main
: Est-ce qu’on doit écrire une classe principale avec un main
?
main
intégrée pour initialiser le conteneur CDI.ContainerInitialized
pour démarrer votre application.Intégration : Weld est intégré dans les serveurs d’applications Jakarta EE (comme WildFly, Payara, etc.).
Déploiement : Déployez votre application sur un serveur compatible Jakarta EE.
Gestion Automatique : Le conteneur CDI (Weld) gère automatiquement l’injection de dépendances et les cycles de vie des beans.
Configuration de beans.xml
beans.xml
: beans.xml
est un fichier de configuration utilisé par CDI pour activer le conteneur de dépendances.beans.xml
dans le répertoire META-INF
.beans.xml
dans le répertoire WEB-INF
.Lorsque plusieurs implémentations d’une même interface existent, CDI ne sait pas laquelle injecter et lève une exception. Voici un exemple de ce conflit :
@Named
@RequestScoped
public class PaymentProcessor {
@Inject
private PaymentService paymentService;
public void executePayment() {
paymentService.processPayment();
}
}
@Qualifier
) :
MyInterface
, il peut être injecté dans un attribut de type MyInterface
.@Qualifier
) qui permettent de distinguer les différentes implémentations d’une même interface.@PayPal
, il peut être injecté dans un attribut qualifié par @PayPal
.CDI utilise des scopes pour définir la durée de vie des beans et gérer leur cycle de vie de manière appropriée.
@RequestScoped
: Durée de vie limitée à une requête HTTP.
@SessionScoped
: Durée de vie limitée à une session utilisateur.
@ApplicationScoped
: Durée de vie limitée à l’application entière.
@Dependent
: Durée de vie liée au bean qui l’injecte.
@ConversationScoped
: Durée de vie limitée à une conversation utilisateur.
Les beans alternatifs permettent de remplacer facilement une implémentation par une autre sans modifier le code source. Ils sont particulièrement utiles pour les tests ou pour adapter l’application à différents environnements.
@Alternative
.beans.xml
.beans.xml
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
<alternatives>
<class>com.example.MockPaymentService</class>
</alternatives>
</beans>
@Produces
qui permet de créer des objets à injecter.import javax.enterprise.inject.Produces;
import javax.enterprise.context.ApplicationScoped;
import java.util.Random;
@ApplicationScoped
public class RandomNumberProducer {
private Random random = new Random();
@Produces
@RandomQualifier
public int produceRandomNumber() {
return random.nextInt(100); // Retourne un entier aléatoire entre 0 et 99
}
}
@Produces
qui permet de créer des objets à injecter.Le design pattern d’interception est un modèle architectural qui permet d’insérer dynamiquement du code supplémentaire, appelé intercepteur, avant ou après l’exécution d’une méthode ou lors de certains événements spécifiques du cycle de vie d’un objet. Ce modèle est largement utilisé pour implémenter des fonctionnalités transversales telles que :
Les intercepteurs permettent de séparer les préoccupations transversales du code métier, améliorant ainsi la modularité et la maintenabilité du code.
Intercepteurs (@Interceptor
) : Utilisés pour intercepter les appels de méthodes et gérer les événements du cycle de vie des beans, en ajoutant des comportements transversaux.
Annotations du Cycle de Vie des Intercepteurs :
@AroundConstruct
: Intercepte la construction d’une instance du bean.@PostConstruct
: Intercepte après la construction de l’instance, juste avant son utilisation.@AroundInvoke
: Intercepte les appels des méthodes du bean.@PreDestroy
: Intercepte avant la destruction de l’instance du bean.@AroundTimeout
: Intercepte les méthodes annotées avec @Timeout
(spécifique aux EJBs).Pour illustrer l’utilisation des intercepteurs en CDI, nous allons créer un intercepteur de journalisation qui enregistre l’entrée et la sortie des méthodes interceptées.
Tout d’abord, nous définissons une annotation d’intercepteur @Logging
:
Ensuite, nous implémentons l’intercepteur de journalisation LoggingInterceptor
:
@Interceptor
@Logging
public class LoggingInterceptor {
@AroundInvoke
public Object logMethod(InvocationContext ctx) throws Exception {
System.out.println("Entering method: " + ctx.getMethod().getName());
try {
return ctx.proceed();
} finally {
System.out.println("Exiting method: " + ctx.getMethod().getName());
}
}
}
Enfin, nous appliquons l’intercepteur à une classe de service PaymentService
:
@Logging
public class PaymentService {
public void processPayment() {
// Logique de traitement du paiement
System.out.println("Processing payment...");
}
}
Dans cet exemple, chaque appel à la méthode processPayment
de PaymentService
sera intercepté par LoggingInterceptor
, qui enregistrera l’entrée et la sortie de la méthode.
Le design pattern décorateur est un modèle de conception structurel qui permet d’ajouter dynamiquement des responsabilités supplémentaires à un objet sans modifier son code source. Il offre une alternative flexible à l’héritage pour étendre les fonctionnalités d’une classe.
@Decorator
) : Permettent de modifier ou d’étendre le comportement des beans sans changer leur code source.@Decorator
public abstract class PaymentServiceDecorator implements PaymentService {
@Inject
@Delegate
private PaymentService paymentService;
@Override
public void processPayment() {
// Logique additionnelle avant
System.out.println("Début du décorateur");
paymentService.processPayment();
// Logique additionnelle après
System.out.println("Fin du décorateur");
}
}
Pour activer le décorateur, il doit être déclaré dans le fichier 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_3_0.xsd"
version="3.0" bean-discovery-mode="all">
<decorators>
<class>com.example.PaymentServiceDecorator</class>
</decorators>
</beans>
@ApplicationScoped
public class PaymentProcessor {
@Inject
private PaymentService paymentService;
public void execute() {
paymentService.processPayment();
}
}
Dans cet exemple, PaymentServiceDecorator
ajoute une logique supplémentaire avant et après l’appel à processPayment
de PaymentService
. Le décorateur est activé via le fichier beans.xml
, et PaymentProcessor
utilise le service décoré pour traiter les paiements.
Un Bean CDI est une classe gérée par le conteneur CDI qui respecte certaines règles et conventions. Voici les éléments clés qui définissent un Bean CDI :
@Qualifier
) permettent de distinguer les différentes implémentations d’une même interface. Ils sont utilisés pour spécifier quel bean doit être injecté lorsque plusieurs implémentations sont disponibles.@RequestScoped
, @SessionScoped
, @ApplicationScoped
, etc. Le scope définit quand le bean est créé et détruit.@Interceptor
) peuvent être appliqués au bean pour ajouter des comportements transversaux, comme la gestion des transactions ou la journalisation.beans.xml
). Cela permet de remplacer facilement une implémentation par une autre sans modifier le code source.Les événements en CDI permettent une communication découplée entre les composants de l’application. Ils offrent un mécanisme pour qu’un composant puisse émettre un événement, tandis que d’autres composants peuvent écouter et réagir à ces événements sans que les deux soient directement liés.
@Observes
)@Observes
.@ObservesAsync
)@ObservesAsync
.Pour émettre un événement, vous pouvez utiliser l’interface Event
fournie par CDI :
@Inject
private Event<UserLoginEvent> userLoginEvent;
public void loginUser(User user) {
// Logique de connexion de l'utilisateur
userLoginEvent.fire(new UserLoginEvent(user));
}
Dans cet exemple, UserLoginEvent
est émis après la connexion de l’utilisateur, et les méthodes observatrices annotées avec @Observes
ou @ObservesAsync
réagiront à cet événement.
L’injection de dépendances est une technique de programmation qui permet de fournir des dépendances à un objet sans que celui-ci ait à les créer lui-même. CDI (Contexts and Dependency Injection) est une spécification Java EE qui fournit un cadre puissant pour la gestion des dépendances et des cycles de vie des objets.
Concepts Clés
@Inject
, @Produces
, et @Qualifier
pour simplifier la gestion des dépendances.@RequestScoped
, @SessionScoped
, et @ApplicationScoped
.@Observes
) et asynchrones (@ObservesAsync
).Avantages de CDI
E. Bruno