Inversion de contrôle en Java

Université de Toulon

LIS UMR CNRS 7020

2024-11-13

Introduction à CDI (Contexts and Dependency Injection)

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.

Problèmes Courants dans le Développement d’Applications Java

  • Pour un objet A…
    • Comment j’ai accès à A dans un autre objet B ?
      • Sans CDI : Références manuelles, risque de couplage fort.
      • Avec CDI : Injection de dépendances simplifiée avec @Inject.
    • Combien de clients est-ce que A peut avoir ?
      • Sans CDI : Difficile à gérer, risque de couplage fort.
      • Avec CDI : Gestion automatique des dépendances et des cycles de vie.
    • Est-ce que A est multi-threadé ?
      • Sans CDI : Gestion manuelle et complexe des threads.
      • Avec CDI : Scopes et contextes gèrent automatiquement la concurrence.
    • Où dois-je garder une référence à A si je ne l’utilise pas ?
      • Sans CDI : Gestion manuelle des références, risque de fuites de mémoire.
      • Avec CDI : Scopes gèrent automatiquement la durée de vie des objets.
    • Est-ce que je dois le détruire explicitement ?
      • Sans CDI : Gestion manuelle de la destruction des objets.
      • Avec CDI : Gestion automatique des cycles de vie des objets.
    • Comment définir des implémentations alternatives, pour en changer au moment du déploiement ?
      • Sans CDI : Configuration manuelle et complexe.
      • Avec CDI : Utilisation de qualifiers (@Qualifier) pour distinguer les implémentations.
    • Comment partager cet objet entre d’autres objets ?
      • Sans CDI : Gestion manuelle des références partagées.
      • Avec CDI : Scopes et contextes facilitent le partage d’objets.

Définition et Concepts de Base

  • 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.

Avantages de l’Utilisation de CDI

  • 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

Historique de CDI (Contexts and Dependency Injection)

  • Ancêtres de CDI

    • EJB (Enterprise JavaBeans) : Introduit dans les premières versions de Java EE pour la gestion des composants d’entreprise, mais complexe et lourd.
    • Spring Framework : Offrait une alternative plus légère et flexible pour l’injection de dépendances et la gestion des transactions.
  • Grandes Évolutions de CDI

    • 2009 : Introduction de CDI avec Java EE 6 (JSR 299)
      • Objectif : Simplifier la gestion des dépendances et des cycles de vie des objets.
    • 2013 : CDI 1.1 avec Java EE 7
      • Améliorations des événements et support des beans transactionnels.
    • 2017 : CDI 2.0 avec Java EE 8
      • Support des événements asynchrones.
    • 2020 : CDI 3.0 avec Jakarta EE 9
      • Transition de javax à jakarta.
      • Alignement avec les nouvelles versions des autres spécifications Jakarta EE.
    • 2022 : CDI 4.0 avec Jakarta EE 10
      • Améliorations des performances et de la sécurité.
  • 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.

Définition d’un Bean au Sens CDI

  • Composant Géré : Un bean CDI est un composant géré par le conteneur CDI. Cela signifie que le conteneur CDI est responsable de la création, de la gestion et de la destruction du bean.
  • Cycle de Vie : Le conteneur CDI contrôle le cycle de vie du bean, en fonction de son scope. Par exemple, un bean @RequestScoped est créé et détruit avec chaque requête HTTP.
  • Injection de Dépendances : Les dépendances du bean sont injectées automatiquement par le conteneur CDI, éliminant ainsi le besoin de gestion manuelle des dépendances.
  • Annotations : Utilisation d’annotations telles que @Inject pour l’injection de dépendances, @Named pour nommer les beans, @Produces pour les méthodes productrices, etc.
  • Scopes : Définition de la portée du bean avec des annotations comme @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.
  • Qualifiers : Utilisation de qualifiers (@Qualifier) pour distinguer les différentes implémentations d’une même interface, permettant une injection plus flexible et contextuelle.

Définition Technique d’un Bean CDI

CDI donne une définition claire d’un bean, qui peut être l’un des suivants :

  • Classe avec un Constructeur Sans Paramètre : Un bean peut être une classe qui possède un constructeur sans paramètre.
  • Classe avec un Constructeur Annoté @Inject : Un bean peut être une classe qui possède un constructeur annoté avec @Inject, permettant l’injection de dépendances.
  • Producteur de Beans : Un bean peut être produit par une méthode ou un champ annoté avec @Produces.

Exemple de Définition d’un Bean

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class MyBean {
    private String message;

    public MyBean() {
        this.message = "Hello, CDI!";
    }

    public String getMessage() {
        return message;
    }
}

Exemple de Définition d’un Bean avec Injection

@Named
@RequestScoped
public class MyService {
    private final MyBean myBean;

    // Constructeur annoté @Inject
    @Inject
    public MyService(MyBean myBean) {
        this.myBean = myBean;
    }

    public void execute() {
        System.out.println(myBean.getMessage());
    }
}

Injection par Type

  • CDI permet d’injecter des dépendances en utilisant le type de l’attribut ou du paramètre, sans avoir besoin de spécifier explicitement le bean à injecter.
  • Si plusieurs beans correspondent au type, une exception est levée.
@Named
@RequestScoped
public class MyBean {
    @Inject
    private MyService myService;

    public void execute() {
        myService.performService();
    }
}

Un exemple plus concret 1/2

public interface PaymentService {
    void processPayment();
}
public abstract class AbstractPaymentService implements PaymentService {
    @Override
    public void processPayment() {
        // Logique commune à toutes les implémentations
        System.out.println("Traitement du paiement de base");
    }
}
public class CreditCardPaymentService extends AbstractPaymentService {
    @Override
    public void processPayment() {
        super.processPayment();
        // Logique spécifique au paiement par carte de crédit
        System.out.println("Traitement du paiement par carte de crédit");
    }
}

Un exemple plus concret 2/2

  • Type du bean à injecter ?
  • Qu’est-ce qui est injecté ?
@Named
@RequestScoped
public class PaymentProcessor {
    private final PaymentService paymentService;

    @Inject
    public PaymentProcessor(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void executePayment() {
        paymentService.processPayment();
    }
}

Implémentation Weld

  • Weld est l’implémentation de référence de CDI (Contexts and Dependency Injection).
  • Fonctionnalités : Weld fournit toutes les fonctionnalités de CDI, y compris l’injection de dépendances, la gestion des scopes, les intercepteurs, les décorateurs et les événements.
  • Environnements :
    • Java SE : Weld peut être utilisé pour apporter les fonctionnalités de CDI aux applications Java SE.
    • Jakarta EE : Weld est intégré dans les serveurs d’applications Jakarta EE (comme WildFly, Payara, etc.), offrant une gestion complète des dépendances et des cycles de vie des objets.

Utilisation de Weld dans Java SE

  • Ajoutez les dépendances Weld à votre projet (Maven ou Gradle).
<dependency>
    <groupId>org.jboss.weld.servlet</groupId>
    <artifactId>weld-servlet-core</artifactId>
    <version>6.0.0.CR1</version>
</dependency>

Initialisation de Weld dans Java SE

  • Points Clés :
    • Classe principale avec main : Est-ce qu’on doit écrire une classe principale avec un main ?
      • Réponse : NON ! Weld fournit une méthode main intégrée pour initialiser le conteneur CDI.
    • Instance du conteneur de beans : Où est l’instance du conteneur de beans ?
      • Weld initialise et gère automatiquement l’instance du conteneur de beans.
    • Point d’entrée de l’application : Le point d’entrée de notre application est un écouteur.
      • Utilisez un écouteur de l’évènement ContainerInitialized pour démarrer votre application.
    • Injection des paramètres de la ligne de commande : On récupère les paramètres de la ligne de commande par injection.

Exemple d’Initialisation de Weld dans Java SE

public class Main {
    @Inject
    private MyService myService;

    public void onStartup(@Observes ContainerInitialized event) {
        myService.performService();
    }
}

Utilisation de Weld dans Jakarta EE

  • 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

    • Rôle de beans.xml : beans.xml est un fichier de configuration utilisé par CDI pour activer le conteneur de dépendances.
    • Emplacement :
      • Pour les applications Java SE : Placez beans.xml dans le répertoire META-INF.
      • Pour les applications web : Placez beans.xml dans le répertoire WEB-INF.
<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">
</beans>

Conflits de Dépendances avec CDI et une Interface

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 :

  • Définition de l’Interface
public interface PaymentService {
    void processPayment();
}

Deux Implémentations de l’Interface

@ApplicationScoped
public class PayPalPaymentService implements PaymentService {
    @Override
    public void processPayment() {
        System.out.println("Traitement du paiement via PayPal");
    }
}
@ApplicationScoped
public class CreditCardPaymentService implements PaymentService {
    @Override
    public void processPayment() {
        System.out.println("Traitement du paiement par carte de crédit");
    }
}

Injection de l’Interface

@Named
@RequestScoped
public class PaymentProcessor {
    @Inject
    private PaymentService paymentService;

    public void executePayment() {
        paymentService.processPayment();
    }
}
  • Lorsque CDI tente d’injecter PaymentService dans PaymentProcessor, il ne sait pas quelle implémentation utiliser et lève une exception.
  • Pour résoudre ce conflit, nous utilisons des qualifiers pour spécifier quelle implémentation injecter.

Qualifiers

  • Utilisation des qualifiers (@Qualifier) :
    • Permettent de distinguer les différentes implémentations d’une même interface.
    • Utilisés pour injecter des implémentations spécifiques en fonction du contexte.
    • Améliorent la flexibilité et la maintenabilité du code en permettant de choisir dynamiquement l’implémentation appropriée.
  • Création de qualifiers personnalisés :
    • Définir des qualifiers spécifiques pour les besoins de l’application.
    • Facilite la gestion des dépendances en permettant une injection plus flexible et contextuelle.
    • Permet de créer des annotations personnalisées pour qualifier les beans.

Exemple de Qualifier

  • Les qualifiers permettent de distinguer les différentes implémentations d’une même interface, facilitant ainsi l’injection de dépendances spécifiques.
  • Ils peuvent être combinés pour une injection plus précise.
//|fig-cap: Définition d'un Qualifier
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface PayPal {}
//|fig-cap: Utilisation d'un Qualifier
@PayPal
@ApplicationScoped
public class PayPalPaymentService implements PaymentService {
  @Override
  public void processPayment() {
    System.out.println("Traitement du paiement via PayPal");
  }
}
//|fig-cap: Injection d'une Implémentation Qualifiée
@PayPal
@Inject
private PaymentService paymentService;

Ce qui est Injecté

  • Référence à un Bean :
    • Lorsqu’on injecte une dépendance, on obtient une référence à un bean géré par le conteneur CDI.
  • Contrat du Bean Injecté :
    • Un attribut injecté spécifie un « contrat » que le bean injecté doit respecter. Ce contrat est défini par :
      • Un ensemble non vide de types :
        • Les types que le bean implémente ou étend, y compris ses interfaces et sa classe de base.
        • Exemple : Si un bean implémente une interface MyInterface, il peut être injecté dans un attribut de type MyInterface.
      • Un ensemble non vide de qualifieurs :
        • Les qualifiers (@Qualifier) qui permettent de distinguer les différentes implémentations d’une même interface.
        • Exemple : Si un bean est annoté avec @PayPal, il peut être injecté dans un attribut qualifié par @PayPal.

Scopes en CDI

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.
    • Utilisé pour les objets qui doivent être créés et détruits avec chaque requête.
    • Exemple : Un bean qui gère les données d’un formulaire soumis par l’utilisateur dans une application JSF ou une requête REST dans JAX-RS.
  • @SessionScoped : Durée de vie limitée à une session utilisateur.
    • Utilisé pour les objets qui doivent être partagés au sein d’une session utilisateur.
    • Exemple : Un bean qui stocke les informations de connexion de l’utilisateur dans une application JSF.
  • @ApplicationScoped : Durée de vie limitée à l’application entière.
    • Utilisé pour les objets qui doivent être partagés à travers toute l’application.
    • Exemple : Un bean qui gère les paramètres de configuration de l’application, utilisé à la fois par JSF et JAX-RS.
  • @Dependent : Durée de vie liée au bean qui l’injecte.
    • Utilisé pour les objets qui doivent avoir la même durée de vie que le bean consommateur.
    • Exemple : Un bean utilitaire qui est utilisé uniquement par un autre bean, indépendamment du contexte (JSF, JAX-RS, etc.).
  • @ConversationScoped : Durée de vie limitée à une conversation utilisateur.
    • Utilisé pour les objets qui doivent être partagés au sein d’une conversation utilisateur, qui peut s’étendre sur plusieurs requêtes HTTP.
    • Exemple : Un bean qui gère les étapes d’un processus de commande en ligne dans une application JSF.

Beans Alternatifs en CDI

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.

  • Définition : Un bean alternatif est une implémentation de bean qui peut être activée ou désactivée en fonction de la configuration.
  • Annotation : Les beans alternatifs sont définis avec l’annotation @Alternative.
  • Activation : Ils doivent être activés dans le fichier de configuration beans.xml.

Exemple de Définition d’un Bean Alternatif

import javax.enterprise.inject.Alternative;
import javax.enterprise.context.RequestScoped;

@Alternative
@RequestScoped
public class MockPaymentService implements PaymentService {
    @Override
    public void processPayment() {
        System.out.println("Traitement du paiement (Mock)");
    }
}

Activation d’un Bean Alternatif dans beans.xml

  • Pour activer un bean alternatif, vous devez le déclarer dans le fichier 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>

Producers en CDI

  • Définition : Un producer est une méthode ou un champ annoté avec @Produces qui permet de créer des objets à injecter.
  • Utilisation : Utilisé pour produire des objets complexes ou des objets dont la création nécessite une logique particulière.
  • Flexibilité : Les producers permettent de contrôler finement le processus de création des objets et d’injecter des objets qui ne sont pas des beans CDI.

Définition du Qualifier

@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface RandomQualifier {}

Définition d’un Producer avec Qualifier

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
    }
}

Utilisation d’un Producer

@Named
@RequestScoped
public class RandomNumberConsumer {
    @Inject
    @RandomQualifier
    private int randomNumber;

    public void printRandomNumber() {
        System.out.println("Random Number: " + randomNumber);
    }
}

Producer Fields en CDI

  • Un producer field est un champ annoté avec @Produces qui permet de créer des objets à injecter.
@ApplicationScoped
public class RandomNumberProducer {
    @Produces
    @RandomQualifier
    private int randomNumber = new Random().nextInt(100); // Retourne un entier aléatoire entre 0 et 99
}

Le Design Pattern d’Interception

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 :

  • Journalisation : Enregistrer les actions effectuées par les utilisateurs ou les événements du système.
  • Sécurité : Contrôler l’accès aux méthodes et aux ressources.
  • Gestion des transactions : Gérer les transactions de manière déclarative.
  • Validation : Vérifier les données avant de les traiter.

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

  • 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).

Exemple d’Intercepteur

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 :

@InterceptorBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD, CONSTRUCTOR})
public @interface Logging {}

Implémentation de l’intercepteur

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());
        }
    }
}

Utilisation de l’intercepteur

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

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.

  • Transparence : Le décorateur implémente la même interface que l’objet décoré, ce qui permet de l’utiliser de manière interchangeable.
  • Composition : Utilise la composition plutôt que l’héritage pour étendre les fonctionnalités, favorisant ainsi une plus grande flexibilité et réutilisabilité.

Décorateurs

  • Décorateurs (@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");
    }
}

Activations de Décorateurs

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>

Exemple d’Utilisation

@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.

Définition Précise d’un Bean CDI

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 :

  • Un ensemble non vide de types :
    • Les types que le bean implémente ou étend, y compris ses interfaces et sa classe de base. Cela permet au conteneur CDI de savoir quels types peuvent être injectés.
  • Un ensemble non vide de qualifieurs :
    • Les qualifiers (@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.
  • Un contexte (scope) :
    • Le scope du bean, qui détermine son cycle de vie. Les scopes courants incluent @RequestScoped, @SessionScoped, @ApplicationScoped, etc. Le scope définit quand le bean est créé et détruit.
  • En option : Un nom EL :
    • Un nom d’expression de langage (EL) qui permet de référencer le bean dans les pages JSP ou les fichiers Facelets. Cela facilite l’intégration avec les technologies de présentation.
  • Un ensemble de liens de type intercepteurs :
    • Les intercepteurs (@Interceptor) peuvent être appliqués au bean pour ajouter des comportements transversaux, comme la gestion des transactions ou la journalisation.
  • Une implémentation :
    • Le code concret qui définit le comportement du bean. C’est la logique métier que le bean encapsule.
  • En option : être un bean alternatif :
    • Un bean qui peut être activé ou désactivé en fonction de la configuration (par exemple, via beans.xml). Cela permet de remplacer facilement une implémentation par une autre sans modifier le code source.

Événements

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.

Émission d’événements synchrones (@Observes)

  • Les méthodes observatrices synchrones sont annotées avec @Observes.
  • Lorsque l’événement est émis, tous les observateurs synchrones sont invoqués dans le même thread.
  • Utile pour les actions qui doivent être terminées avant de continuer le flux principal.
public void onUserLogin(@Observes UserLoginEvent event) {
    // Logique à exécuter lors de la connexion d'un utilisateur
}

Émission d’événements asynchrones (@ObservesAsync)

  • Les méthodes observatrices asynchrones sont annotées avec @ObservesAsync.
  • Les observateurs asynchrones sont invoqués dans un thread séparé, ce qui n’affecte pas le flux principal.
  • Utile pour les actions qui peuvent être exécutées en arrière-plan sans bloquer le flux principal.
public void onUserLoginAsync(@ObservesAsync UserLoginEvent event) {
    // Logique à exécuter de manière asynchrone lors de la connexion d'un utilisateur
}

Exemple d’Émission d’Événements

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.

Résumé

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

    • Injection de Dépendances : Utilisation d’annotations telles que @Inject, @Produces, et @Qualifier pour simplifier la gestion des dépendances.
    • Scopes : Définition des cycles de vie des beans avec des annotations comme @RequestScoped, @SessionScoped, et @ApplicationScoped.
    • Intercepteurs et Décorateurs : Ajout de comportements transversaux et extension des fonctionnalités des beans sans modifier leur code source.
    • Événements : Communication découplée entre les composants via des événements synchrones (@Observes) et asynchrones (@ObservesAsync).
  • Avantages de CDI

    • Réduction du Couplage : Favorise une architecture modulaire et découplée.
    • Amélioration de la Testabilité : Facilite l’injection de mocks et de stubs pour les tests unitaires.
    • Extensibilité et Flexibilité : Permet d’ajouter des fonctionnalités sans modifier le code existant.
    • Gestion Automatique des Cycles de Vie : Simplifie la gestion des états et des ressources des objets.