🔥 FREE PRO OFFER OnlyLink.click Pro Version is 100% Free of Cost till 31 December, 2026! Claim Free Pro

Injection de dépendances dans Quarkus avec CDI : ArC, scopes, qualificateurs et injection au moment de la construction

Injection de dépendances dans Quarkus avec CDI : ArC, scopes, qualificateurs et injection au moment de la construction

L’injection de dépendances (DI) est le fondement du développement d’applications Java d’entreprise moderne. Pendant des années, les développeurs Java se sont appuyés sur des conteneurs Spring IoC (Inversion of Control) ou Jakarta EE CDI (Contexts and Dependency Injection) standard comme Hibernate Weld ou OpenWebBeans pour découpler les cycles de vie des composants, gérer les dépendances de service et injecter des propriétés de configuration.

Cependant, à mesure que les applications d’entreprise ont évolué vers des microservices cloud natifs, la conteneurisation avec Docker et Kubernetes et des modèles d’exécution sans serveur (AWS Lambda, Knative), les conteneurs d’injection de dépendances traditionnels ont révélé une faille critique : réflexion d’exécution importante et surcharge de mémoire élevée.

Entrez Quarkus et son moteur CDI léger, ArC. Construit à partir de zéro pour la compilation native Kubernetes et GraalVM, Quarkus redéfinit l’injection de dépendances en déplaçant la réflexion, l’analyse du chemin de classe et le câblage des graphiques de bean du moment de l’exécution de l’application au moment de la construction.

Dans ce guide approfondi, nous explorerons l’injection de dépendances dans Quarkus avec CDI, couvrant les mécanismes internes du conteneur ArC, les étendues CDI, les qualificatifs personnalisés, les producteurs, les intercepteurs, les observateurs pilotés par les événements, les recherches programmatiques et les meilleures pratiques pour l’écriture d’applications Java cloud natives hautes performances.


1. Comprendre ArC : moteur CDI Quarkus Build-Time

Les conteneurs Jakarta EE CDI standard fonctionnent entièrement au démarrage de l’application. Lorsqu’une application héritée démarre, le conteneur CDI analyse chaque JAR du chemin de classe, analyse les métadonnées, inspecte les annotations à l’aide de Java Reflection (java.lang.reflect), génère des proxys dynamiques en mémoire et crée le graphique de dépendances. Cette réflexion dynamique consomme beaucoup de RAM (Metaspace) et augmente le temps de démarrage à froid.

Quarkus résout ce goulot d’étranglement avec ArC (une implémentation CDI au moment de la construction basée sur les spécifications Jakarta CDI 4.0).

CDI d'exécution traditionnel avec câblage Reflection vs Quarkus ArC Build-Time

En quoi ArC diffère du CDI standard :

  1. Augmentation du temps de construction : pendant mvn package ou ./gradlew build, ArC analyse tous les beans d’application, valide les points d’injection et pré-construit le graphe de dépendances.
  2. Génération de bytecode en avance (AOT) : ArC génère du bytecode Java direct pour l’instanciation, les proxys de bean et les intercepteurs dès le départ, éliminant ainsi la réflexion au moment de l’exécution.
  3. Élagage des haricots inutilisés (élimination du code mort) : tout bean CDI qui n’est jamais injecté ou référencé dynamiquement est identifié lors de la compilation et automatiquement supprimé du binaire final, réduisant ainsi l’empreinte mémoire.
  4. Optimisation d’image native GraalVM : étant donné que le câblage du bean est résolu au moment de la construction, les applications Quarkus se compilent de manière transparente en exécutables natifs avec des conseils de configuration sans réflexion.

2. Portées des beans CDI et gestion du cycle de vie

Dans CDI, un Scope dicte le cycle de vie et la visibilité d’une instance de bean. Quarkus ArC prend en charge les étendues CDI Jakarta standard avec des optimisations au moment de la construction.

Diagramme des étendues et des cycles de vie des beans Quarkus CDI

Portées CDI principales dans Quarkus :

Annotation de la portée Cycle de vie et comportement Création de proxy Meilleur cas d’utilisation
@ApplicationScoped Une seule instance par application. Initialisation paresseuse lors du premier appel. État partagé sur tous les threads. Proxy client généré Services, référentiels, configurations sans état.
@RequestScoped Instance liée à une seule requête HTTP ou à un appel d’événement. Détruit une fois la demande terminée. Proxy client généré Sessions utilisateur, données contextuelles de demande, jetons de sécurité.
@Dépendant Portée par défaut si aucune n’est spécifiée. Instance directement liée à la durée de vie du bean injecté. Pas de proxy (instance directe) Classes d’utilité auxiliaire, détenteurs d’État éphémères.
@Singleton Une seule instance par application. Instancié avec impatience ou paresse. Aucun wrapper de proxy client. Pas de proxy (instance directe) Singletons hautes performances où la surcharge du proxy client doit être évitée.
@Inamovible Annotation de marqueur indiquant à ArC de ne pas supprimer le bean pendant l’optimisation au moment de la construction. Dépend de la portée Cibles de recherche dynamique ou beans référencés par réflexion.

Exemple de code : déclaration de scoped beans

package com.ghaznix.services;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.RequestScoped;
import java.util.UUID;

@ApplicationScoped
public class OrderProcessingService {
    
    public String processOrder(String orderId) {
        return "Order " + orderId + " processed successfully.";
    }
}

@RequestScoped
public class RequestContextHolder {
    private final String requestId = UUID.randomUUID().toString();

    public String getRequestId() {
        return requestId;
    }
}

3. Modèles d’injection de dépendances dans Quarkus

Quarkus prend en charge plusieurs styles d’injection à l’aide de l’annotation standard jakarta.inject.Inject.

A. Injection de constructeur (meilleures pratiques recommandées)

L’injection de constructeur garantit l’immuabilité (champs final), simplifie les tests unitaires sans nécessiter de conteneurs CDI et rend les dépendances explicites.

package com.ghaznix.resources;

import com.ghaznix.services.OrderProcessingService;
import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;

@Path("/orders")
public class OrderResource {

    private final OrderProcessingService orderService;

    // In Quarkus, if a class has a single constructor, @Inject is OPTIONAL!
    @Inject
    public OrderResource(OrderProcessingService orderService) {
        this.orderService = orderService;
    }

    @POST
    @Path("/{id}")
    public String checkout(@PathParam("id") String id) {
        return orderService.processOrder(id);
    }
}

Conseil de pro : Dans Quarkus ArC, si un bean n’a qu’un un constructeur, vous pouvez omettre entièrement l’annotation @Inject. ArC le reconnaît automatiquement comme le constructeur d’injection.

B. Injection sur le terrain

L’injection de champ est concise mais rend les champs mutables et complique les tests unitaires autonomes.

@ApplicationScoped
public class NotificationService {

    @Inject
    EmailClient emailClient; // Field injection
}

4. Qualificateurs et levée de l’ambiguïté sur plusieurs implémentations

Lorsque plusieurs beans implémentent la même interface, CDI lève une exception de dépendance ambiguë au moment de la construction. Quarkus utilise des Qualifiers pour résoudre les implémentations du bean cible en toute sécurité.

Définir des qualificatifs personnalisés

package com.ghaznix.payment;

import jakarta.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface PaymentType {
    Mode value();

    enum Mode {
        CREDIT_CARD,
        PAYPAL,
        CRYPTO
    }
}

Implémentation de beans qualifiés

public interface PaymentProcessor {
    void process(double amount);
}

@ApplicationScoped
@PaymentType(PaymentType.Mode.CREDIT_CARD)
public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void process(double amount) {
        System.out.println("Processing Credit Card payment: $" + amount);
    }
}

@ApplicationScoped
@PaymentType(PaymentType.Mode.PAYPAL)
public class PayPalProcessor implements PaymentProcessor {
    @Override
    public void process(double amount) {
        System.out.println("Processing PayPal payment: $" + amount);
    }
}

Injection de haricots qualifiés

@ApplicationScoped
public class CheckoutService {

    private final PaymentProcessor creditCardProcessor;
    private final PaymentProcessor payPalProcessor;

    @Inject
    public CheckoutService(
            @PaymentType(PaymentType.Mode.CREDIT_CARD) PaymentProcessor creditCardProcessor,
            @PaymentType(PaymentType.Mode.PAYPAL) PaymentProcessor payPalProcessor) {
        this.creditCardProcessor = creditCardProcessor;
        this.payPalProcessor = payPalProcessor;
    }
}

5. Producteurs & Disposeurs : Injection d’objets tiers

Parfois, vous devez injecter des classes à partir de bibliothèques tierces (par exemple, clients AWS SDK, connexions Redis, beans de configuration personnalisés) qui ne sont pas annotées avec @ApplicationScoped. Quarkus gère cela à l’aide de Méthodes de producteur et de Disposeurs.

package com.ghaznix.config;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Disposes;
import jakarta.enterprise.inject.Produces;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

@ApplicationScoped
public class DatabaseConnectionProducer {

    @Produces
    @ApplicationScoped
    public Connection createDatabaseConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:postgresql://localhost:5432/mydb", "user", "pass");
    }

    // Disposer method called automatically when bean is destroyed
    public void closeConnection(@Disposes Connection connection) throws SQLException {
        if (connection != null && !connection.isClosed()) {
            connection.close();
        }
    }
}

6. Intercepteurs CDI : programmation orientée aspect (AOP)

Les intercepteurs permettent d’attacher des préoccupations transversales (journalisation, pistes d’audit, métriques, limitation de débit) aux méthodes des beans sans polluer la logique métier.

Étape 1 : Définir la liaison d’intercepteur

package com.ghaznix.interceptors;

import jakarta.interceptor.InterceptorBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

Étape 2 : implémenter la logique de l’intercepteur

package com.ghaznix.interceptors;

import jakarta.annotation.Priority;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;

@LogExecutionTime
@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
public class LogExecutionTimeInterceptor {

    @AroundInvoke
    public Object logInvocation(InvocationContext context) throws Exception {
        long startTime = System.currentTimeMillis();
        try {
            return context.proceed();
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            System.out.printf("[AUDIT] Method %s executed in %d ms%n",
                    context.getMethod().getName(), duration);
        }
    }
}

Étape 3 : Annoter les beans cibles

@ApplicationScoped
public class AnalyticsService {

    @LogExecutionTime
    public void computeAggregates() {
        // Business computation
    }
}

Étant donné que Quarkus génère des proxys d’intercepteur statiques au moment de la construction, la surcharge d’invocation de méthode est pratiquement nulle par rapport aux proxys d’exécution dynamiques CGLIB dans Spring Boot.


7. Architecture basée sur les événements avec les événements CDI

CDI fournit une publication d’événements synchrones et asynchrones intégrée pour une communication découplée en cours de processus.

Publication et observation d’événements

// 1. Define Event Payload
public record OrderCreatedEvent(String orderId, double totalAmount) {}

// 2. Publish Event
@ApplicationScoped
public class OrderService {

    @Inject
    Event<OrderCreatedEvent> orderEventPublisher;

    public void createOrder(String orderId, double amount) {
        // Create order logic...
        orderEventPublisher.fire(new OrderCreatedEvent(orderId, amount));
    }
}

// 3. Observe Event Synchronously or Asynchronously
@ApplicationScoped
public class InventoryObserver {

    public void onOrderCreated(@Observes OrderCreatedEvent event) {
        System.out.println("Updating inventory for order: " + event.orderId());
    }

    public void onOrderCreatedAsync(@ObservesAsync OrderCreatedEvent event) {
        System.out.println("Async audit log for order: " + event.orderId());
    }
}

Événements du cycle de vie des applications

Quarkus expose les événements de démarrage et d’arrêt pour exécuter le code d’initialisation lorsque l’application démarre ou se termine :

@ApplicationScoped
public class ApplicationLifecycleBean {

    void onStart(@Observes StartupEvent ev) {
        System.out.println("Quarkus Application starting up...");
    }

    void onStop(@Observes ShutdownEvent ev) {
        System.out.println("Quarkus Application shutting down...");
    }
}

8. Recherche de beans programmatiques avec Instance<T> et Arc.container()

Lorsque les implémentations cibles doivent être choisies dynamiquement au moment de l’exécution (ou dans des instances non CDI héritées), utilisez la recherche statique Instance<T> ou Quarkus Arc.container().

@ApplicationScoped
public class DynamicPluginRegistry {

    @Inject
    Instance<PaymentProcessor> processors;

    public void executeAll(double amount) {
        for (PaymentProcessor processor : processors) {
            processor.process(amount);
        }
    }
}

9. Comparaison architecturale : Quarkus ArC vs Spring IoC vs Weld

Fonctionnalité Quarkus ArC Conteneur Spring IoC Soudure standard Jakarta
Résolution des dépendances Temps de construction (AOT) Réflexion d’exécution Réflexion d’exécution
Surcharge de RAM métaspace Extrêmement faible (~ 0 Mo pour les métadonnées) Élevé (Cache de classe proxy dynamique) Modéré à élevé
Heure de démarrage Millisecondes (< 0,05 s) Secondes (2,5 s - 8 s) Secondes (1,5 s - 4 s)
Support natif GraalVM Natif prêt à l’emploi Nécessite des conseils natifs de Spring Configuration complexe
Taille des haricots inutilisée Élagage automatique au moment de la construction Conservé dans Classpath Conservé dans Classpath
Injection de constructeur unique Implicite (aucune annotation nécessaire) Soutenu Nécessite @Inject

10. Test unitaire des beans CDI avec @QuarkusTest et @InjectMock

Quarkus facilite le test des beans CDI avec JUnit 5 et RestAssured. Utilisez @InjectMock pour remplacer automatiquement les beans de dépendance par des simulations Mockito pendant les tests.

package com.ghaznix;

import com.ghaznix.services.OrderProcessingService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

@QuarkusTest
public class OrderServiceTest {

    @Inject
    OrderService orderService;

    @InjectMock
    OrderProcessingService orderProcessingService;

    @Test
    public void testOrderCheckout() {
        Mockito.when(orderProcessingService.processOrder("ORD-101"))
               .thenReturn("Mocked Success");

        String result = orderService.checkout("ORD-101");
        Assertions.assertEquals("Mocked Success", result);
    }
}

11. Conclusion

L’injection de dépendances dans Quarkus avec CDI (ArC) marque un changement monumental dans la conception Java d’entreprise. En transférant l’analyse des métadonnées du bean, la génération de proxy et la résolution des graphiques d’injection au moment de la compilation, Quarkus offre des vitesses de démarrage supersoniques, des empreintes de mémoire subatomiques et une prise en charge binaire native transparente de GraalVM.

Que vous migraciez depuis Spring Boot ou que vous créiez de nouveaux microservices cloud natifs sur Kubernetes, la maîtrise de Quarkus CDI vous offre les performances et la productivité des développeurs requises pour une architecture logicielle moderne.

Ghaznix Ecosystem Products

Empower Your Digital Presence & Workflows

Explore top-tier tools built by Ghaznix to streamline your links, surveys, and brand growth.