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

Abhängigkeitsinjektion in Quarkus mit CDI: ArC, Scopes, Qualifiers und Build-Time-Injection

Abhängigkeitsinjektion in Quarkus mit CDI: ArC, Scopes, Qualifiers und Build-Time-Injection

Dependency Injection (DI) ist die Grundlage der modernen Java-Anwendungsentwicklung für Unternehmen. Jahrelang verließen sich Java-Entwickler auf Spring IoC (Inversion of Control) oder standardmäßige Jakarta EE CDI (Contexts and Dependency Injection)-Container wie Hibernate Weld oder OpenWebBeans, um Komponentenlebenszyklen zu entkoppeln, Dienstabhängigkeiten zu verwalten und Konfigurationseigenschaften einzufügen.

Als Unternehmensanwendungen jedoch auf Cloud-native Mikrodienste, Containerisierung mit Docker und Kubernetes sowie serverlose Ausführungsmodelle (AWS Lambda, Knative) umstellten, offenbarten herkömmliche Dependency-Injection-Container einen kritischen Fehler: starke Laufzeitreflexion und hoher Speicher-Overhead.

Hier kommen Quarkus und seine leichte CDI-Engine ArC ins Spiel. Quarkus wurde von Grund auf für die native Kompilierung von Kubernetes und GraalVM entwickelt und definiert die Abhängigkeitsinjektion neu, indem Reflektion, Klassenpfad-Scanning und Bean-Graph-Verkabelung von der Anwendungslaufzeit zur Erstellungszeit verlagert werden.

In diesem ausführlichen Leitfaden befassen wir uns mit Abhängigkeitsinjektion in Quarkus mit CDI und behandeln dabei die inneren Mechanismen des ArC-Containers, CDI-Umfänge, benutzerdefinierte Qualifizierer, Produzenten, Interceptoren, ereignisgesteuerte Beobachter, programmgesteuerte Lookups und Best Practices für das Schreiben leistungsstarker cloudnativer Java-Anwendungen.


1. ArC verstehen: Quarkus Build-Time CDI Engine

Standard-Jakarta-EE-CDI-Container werden vollständig beim Anwendungsstart ausgeführt. Wenn eine Legacy-Anwendung gestartet wird, scannt der CDI-Container jedes JAR im Klassenpfad, analysiert Metadaten, prüft Annotationen mithilfe von Java Reflection (java.lang.reflect), generiert dynamische Proxys im Speicher und erstellt das Abhängigkeitsdiagramm. Diese dynamische Reflexion verbraucht erheblich RAM (Metaspace) und verlängert die Bootzeit beim Kaltstart.

Quarkus löst diesen Engpass mit ArC (einer Build-Time-CDI-Implementierung basierend auf den Jakarta CDI 4.0-Spezifikationen).

Traditionelle Laufzeit-CDI mit Reflection vs. Quarkus ArC Build-Time-Verkabelung

Wie sich ArC vom Standard-CDI unterscheidet:

  1. Build-Time Augmentation: Während mvn package oder ./gradlew build scannt ArC alle Anwendungs-Beans, validiert Injektionspunkte und erstellt den Abhängigkeitsgraphen vorab.
  2. Ahead-Of-Time (AOT) Bytecode-Generierung: ArC generiert direkten Java-Bytecode für Instanziierung, Bean-Proxys und Interceptoren im Voraus und eliminiert so Reflexionen zur Laufzeit.
  3. Bereinigung nicht verwendeter Beans (Eliminierung von totem Code): Jede CDI-Bean, die niemals dynamisch injiziert oder referenziert wird, wird während der Kompilierung identifiziert und automatisch aus der endgültigen Binärdatei entfernt, wodurch der Speicherbedarf reduziert wird.
  4. Native Image-Optimierung von GraalVM: Da die Bean-Verkabelung zur Build-Zeit aufgelöst wird, werden Quarkus-Anwendungen nahtlos in native ausführbare Dateien kompiliert, ohne dass Konfigurationshinweise reflektiert werden.

2. CDI-Bean-Bereiche und Lebenszyklusmanagement

In CDI bestimmt ein Scope den Lebenszyklus und die Sichtbarkeit einer Bean-Instanz. Quarkus ArC unterstützt Standard-CDI-Bereiche von Jakarta mit Optimierungen zur Buildzeit.

Quarkus CDI Bean Scopes und Lebenszyklusdiagramm

Primäre CDI-Bereiche in Quarkus:

Bereichsanmerkung Lebenszyklus und Verhalten Proxy-Erstellung Bester Anwendungsfall
„@ApplicationScoped“. Einzelne Instanz pro Anwendung. Verzögerte Initialisierung beim ersten Aufruf. Status in allen Threads geteilt. Client-Proxy generiert Zustandslose Dienste, Repositorys, Konfigurationen.
„@RequestScoped“. Instanz, die an eine einzelne HTTP-Anfrage oder einen einzelnen Ereignisaufruf gebunden ist. Wird zerstört, wenn die Anfrage abgeschlossen ist. Client-Proxy generiert Benutzersitzungen, Anforderungskontextdaten, Sicherheitstoken.
„@Dependent“. Standardbereich, wenn keiner angegeben ist. Die Instanz ist direkt mit der Lebensdauer der injizierenden Bohne verknüpft. Kein Proxy (direkte Instanz) Hilfsdienstprogrammklassen, kurzlebige Zustandsinhaber.
„@Singleton“. Einzelne Instanz pro Anwendung. Eifrig oder träge instanziiert. Kein Client-Proxy-Wrapper. Kein Proxy (direkte Instanz) Hochleistungs-Singletons, bei denen der Client-Proxy-Overhead vermieden werden muss.
„@Unremovable“. Markierungsanmerkung, die ArC anweist, die Bean während der Build-Time-Optimierung nicht zu entfernen. Hängt vom Umfang ab Dynamische Suchziele oder reflexionsreferenzierte Beans.

Codebeispiel: Scoped Beans deklarieren

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. Abhängigkeitsinjektionsmuster in Quarkus

Quarkus unterstützt mehrere Injektionsstile mithilfe der Standardannotation jakarta.inject.Inject.

A. Konstruktorinjektion (empfohlene Best Practice)

Die Konstruktorinjektion garantiert Unveränderlichkeit (final-Felder), vereinfacht Unit-Tests, ohne dass CDI-Container erforderlich sind, und macht Abhängigkeiten explizit.

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

Profi-Tipp: Wenn in Quarkus ArC eine Bean nur einen Konstruktor hat, können Sie die Annotation @Inject vollständig weglassen. ArC erkennt es automatisch als Injektionskonstruktor.

B. Feldinjektion

Die Feldinjektion ist prägnant, macht Felder jedoch veränderbar und erschwert das Testen eigenständiger Einheiten.

@ApplicationScoped
public class NotificationService {

    @Inject
    EmailClient emailClient; // Field injection
}

4. Qualifizierer und Eindeutigkeit mehrerer Implementierungen

Wenn mehrere Beans dieselbe Schnittstelle implementieren, löst CDI zur Erstellungszeit eine mehrdeutige Abhängigkeitsausnahme aus. Quarkus verwendet Qualifizierer, um Ziel-Bean-Implementierungen sicher aufzulösen.

Benutzerdefinierte Qualifizierer definieren

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

Qualifizierte Beans implementieren

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

Qualifizierte Bohnen injizieren

@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. Produzenten und Entsorger: Einschleusen von Objekten Dritter

Manchmal müssen Sie Klassen aus Bibliotheken von Drittanbietern (z. B. AWS SDK-Clients, Redis-Verbindungen, benutzerdefinierte Konfigurations-Beans) einfügen, die nicht mit @ApplicationScoped annotiert sind. Quarkus handhabt dies mithilfe von Produzentenmethoden und Entsorgern.

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. CDI-Interceptoren: Aspektorientierte Programmierung (AOP)

Mit Interceptoren können übergreifende Anliegen (Protokollierung, Audit-Trails, Metriken, Ratenbegrenzung) an Bean-Methoden angehängt werden, ohne die Geschäftslogik zu beeinträchtigen.

Schritt 1: Definieren Sie die Interceptor-Bindung

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

Schritt 2: Interceptor-Logik implementieren

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

Schritt 3: Ziel-Beans mit Anmerkungen versehen

@ApplicationScoped
public class AnalyticsService {

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

Da Quarkus zum Zeitpunkt der Erstellung statische Interceptor-Proxys generiert, ist der Overhead beim Methodenaufruf im Vergleich zu dynamischen CGLIB-Laufzeit-Proxys in Spring Boot praktisch gleich Null.


7. Ereignisgesteuerte Architektur mit CDI-Ereignissen

CDI bietet integrierte synchrone und asynchrone Ereignisveröffentlichung für entkoppelte In-Process-Kommunikation.

Ereignisse veröffentlichen und beobachten

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

Anwendungslebenszyklusereignisse

Quarkus macht Start- und Shutdown-Ereignisse verfügbar, um Initialisierungscode auszuführen, wenn die Anwendung gestartet oder beendet wird:

@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. Programmatic Bean Lookup mit Instance<T> & Arc.container()

Wenn Zielimplementierungen dynamisch zur Laufzeit (oder innerhalb älterer Nicht-CDI-Instanzen) ausgewählt werden müssen, verwenden Sie die statische Suche Instance<T> oder Quarkus Arc.container().

@ApplicationScoped
public class DynamicPluginRegistry {

    @Inject
    Instance<PaymentProcessor> processors;

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

9. Architekturvergleich: Quarkus ArC vs. Spring IoC vs. Weld

Besonderheit Quarkus ArC Spring IoC-Container Standard-Jakarta-Schweißnaht
Abhängigkeitsauflösung Build-Zeit (AOT) Laufzeitreflexion Laufzeitreflexion
Metaspace-RAM-Overhead Extrem niedrig (~0 MB für Metadaten) Hoch (Dynamischer Proxy-Klassen-Cache) Mäßig bis hoch
Startzeit Millisekunden (< 0,05 s) Sekunden (2,5 s – 8 s) Sekunden (1,5 s – 4 s)
Native Unterstützung von GraalVM Native, sofort einsatzbereit Erfordert Spring Native Hints Komplexe Konfiguration
Unbenutzter Bohnenschnitt Automatisches Bereinigen zur Build-Zeit Wird im Klassenpfad gespeichert Wird im Klassenpfad gespeichert
Einzelkonstruktorinjektion Implizit (keine Anmerkung erforderlich) Unterstützt Erfordert „@Inject“.

10. Unit-Test von CDI-Beans mit @QuarkusTest und @InjectMock

Quarkus ermöglicht das nahtlose Testen von CDI-Beans mit JUnit 5 und RestAssured. Verwenden Sie @InjectMock, um Abhängigkeits-Beans während des Tests automatisch durch Mockito-Mocks zu ersetzen.

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

Die Abhängigkeitsinjektion in Quarkus mit CDI (ArC) markiert einen monumentalen Wandel im Enterprise-Java-Design. Durch die Übertragung von Bean-Metadaten-Scanning, Proxy-Generierung und Injektionsdiagrammauflösung auf die Kompilierungszeit liefert Quarkus Überschall-Startgeschwindigkeiten, subatomaren Speicherbedarf und nahtlose native GraalVM-Binärunterstützung.

Ganz gleich, ob Sie von Spring Boot migrieren oder cloudnative Microservices auf der grünen Wiese auf Kubernetes erstellen, die Beherrschung von Quarkus CDI stattet Sie mit der Leistung und Entwicklerproduktivität aus, die für eine moderne Softwarearchitektur erforderlich sind.

Ghaznix Ecosystem Products

Empower Your Digital Presence & Workflows

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