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

Inyección de dependencia en Quarkus con CDI: ArC, alcances, calificadores e inyección en tiempo de compilación

Inyección de dependencia en Quarkus con CDI: ArC, alcances, calificadores e inyección en tiempo de compilación

La inyección de dependencia (DI) es la base del desarrollo de aplicaciones Java empresariales modernas. Durante años, los desarrolladores de Java confiaron en Spring IoC (Inversión de control) o contenedores estándar Jakarta EE CDI (Contexts and Dependency injection) como Hibernate Weld u OpenWebBeans para desacoplar los ciclos de vida de los componentes, administrar las dependencias de los servicios e inyectar propiedades de configuración.

Sin embargo, a medida que las aplicaciones empresariales hicieron la transición hacia microservicios nativos de la nube, la contenedorización con Docker y Kubernetes y modelos de ejecución sin servidor (AWS Lambda, Knative), los contenedores tradicionales de inyección de dependencia expusieron una falla crítica: pesada reflexión en tiempo de ejecución y alta sobrecarga de memoria.

Ingrese Quarkus y su motor CDI liviano, ArC. Creado desde cero para la compilación nativa de Kubernetes y GraalVM, Quarkus redefine la inyección de dependencia al cambiar la reflexión, el escaneo de classpath y el cableado de gráficos de beans desde el tiempo de ejecución de la aplicación hasta el tiempo de compilación.

En esta guía detallada, exploraremos la Inyección de dependencia en Quarkus con CDI, cubriendo la mecánica interna del contenedor ArC, alcances CDI, calificadores personalizados, productores, interceptores, observadores controlados por eventos, búsquedas programáticas y mejores prácticas para escribir aplicaciones Java nativas de la nube de alto rendimiento.


1. Comprensión de ArC: motor CDI de tiempo de construcción Quarkus

Los contenedores estándar Jakarta EE CDI funcionan completamente al iniciar la aplicación. Cuando se inicia una aplicación heredada, el contenedor CDI escanea cada JAR en el classpath, analiza los metadatos, inspecciona las anotaciones usando Java Reflection (java.lang.reflect), genera servidores proxy dinámicos en la memoria y construye el gráfico de dependencia. Esta reflexión dinámica consume una cantidad significativa de RAM (Metaespacio) y aumenta el tiempo de arranque en frío.

Quarkus resuelve este cuello de botella con ArC (una implementación CDI en tiempo de compilación basada en las especificaciones Jakarta CDI 4.0).

CDI en tiempo de ejecución tradicional con reflexión frente al cableado en tiempo de construcción Quarkus ArC

En qué se diferencia ArC del CDI estándar:

  1. Aumento del tiempo de compilación: durante mvn package o ./gradlew build, ArC escanea todos los beans de la aplicación, valida los puntos de inyección y crea previamente el gráfico de dependencia.
  2. Generación de código de bytes anticipada (AOT): ArC genera código de bytes de Java directo para creación de instancias, servidores proxy de beans e interceptores por adelantado, eliminando la reflexión en tiempo de ejecución.
  3. Poda de beans no utilizados (eliminación de código muerto): cualquier bean CDI que nunca se inyecta o al que se hace referencia dinámicamente se identifica durante la compilación y se elimina automáticamente del binario final, lo que reduce el uso de memoria.
  4. Optimización de imagen nativa de GraalVM: debido a que el cableado del bean se resuelve en el momento de la compilación, las aplicaciones Quarkus se compilan sin problemas en ejecutables nativos con sugerencias de configuración de reflexión cero.

2. Alcance del Bean CDI y gestión del ciclo de vida

En CDI, un Alcance dicta el ciclo de vida y la visibilidad de una instancia de bean. Quarkus ArC admite osciloscopios CDI estándar de Yakarta con optimizaciones en el tiempo de construcción.

Diagrama de ciclos de vida y alcances de Quarkus CDI Bean

Alcances CDI primarios en Quarkus:

Anotación de alcance Ciclo de vida y comportamiento Creación de proxy Mejor caso de uso
@ApplicationScoped Instancia única por aplicación. Inicialización diferida tras la primera invocación. Estado compartido en todos los hilos. Proxy de cliente generado Servicios, repositorios, configuraciones sin estado.
@RequestScoped Instancia vinculada a una única solicitud HTTP o invocación de evento. Destruido cuando se completa la solicitud. Proxy de cliente generado Sesiones de usuario, solicitar datos de contexto, tokens de seguridad.
@Dependiente Alcance predeterminado si no se especifica ninguno. Instancia vinculada directamente a la vida útil del bean de inyección. Sin proxy (instancia directa) Clases de utilidad auxiliares, titulares de estados efímeros.
@Singleton Instancia única por aplicación. Instanciado con avidez o pereza. Sin contenedor de proxy de cliente. Sin proxy (instancia directa) Singletons de alto rendimiento donde se debe evitar la sobrecarga del proxy del cliente.
@Inamovible Anotación de marcador que indica a ArC que no elimine el bean durante la optimización del tiempo de compilación. Depende del alcance Objetivos de búsqueda dinámica o beans referenciados por reflexión.

Ejemplo de código: declaración de beans con alcance

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. Patrones de inyección de dependencia en Quarkus

Quarkus admite múltiples estilos de inyección utilizando la anotación estándar jakarta.inject.Inject.

A. Inyección de constructor (mejor práctica recomendada)

La inyección de constructor garantiza la inmutabilidad (campos final), simplifica las pruebas unitarias sin requerir contenedores CDI y hace que las dependencias sean explícitas.

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

Consejo profesional: En Quarkus ArC, si un bean tiene solo un constructor, puedes omitir la anotación @Inject por completo. ArC lo reconoce automáticamente como el constructor de inyección.

B. Inyección de campo

La inyección de campo es concisa pero hace que los campos sean mutables y complica las pruebas unitarias independientes.

@ApplicationScoped
public class NotificationService {

    @Inject
    EmailClient emailClient; // Field injection
}

4. Calificadores y desambiguación de implementaciones múltiples

Cuando varios beans implementan la misma interfaz, CDI genera una excepción de dependencia ambigua en el momento de la compilación. Quarkus utiliza Calificadores para resolver implementaciones de beans de destino de forma segura.

Definición de calificadores personalizados

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

Implementación de beans calificados

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

Inyectar frijoles calificados

@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. Productores y Eliminadores: Inyección de Objetos de Terceros

A veces es necesario inyectar clases de bibliotecas de terceros (por ejemplo, clientes de AWS SDK, conexiones de Redis, beans de configuración personalizados) que no están anotadas con @ApplicationScoped. Quarkus maneja esto usando Métodos de productor y Disposers.

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. Interceptores CDI: programación orientada a aspectos (AOP)

Los interceptores permiten adjuntar preocupaciones transversales (registro, pistas de auditoría, métricas, limitación de velocidad) a los métodos de beans sin contaminar la lógica empresarial.

Paso 1: Definir el enlace del interceptor

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

Paso 2: implementar la lógica del interceptor

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

Paso 3: Anotar los beans de destino

@ApplicationScoped
public class AnalyticsService {

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

Debido a que Quarkus genera servidores proxy de interceptor estáticos en el momento de la compilación, la sobrecarga de invocación de métodos es prácticamente nula en comparación con los servidores proxy de tiempo de ejecución CGLIB dinámicos en Spring Boot.


7. Arquitectura basada en eventos con eventos CDI

CDI proporciona publicación de eventos sincrónica y asincrónica integrada para comunicación desacoplada en proceso.

Publicación y observación de eventos

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

Eventos del ciclo de vida de la aplicación

Quarkus expone eventos de inicio y apagado para ejecutar el código de inicialización cuando la aplicación se inicia o finaliza:

@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. Búsqueda programática de beans con Instance<T> y Arc.container()

Cuando las implementaciones de destino deben elegirse dinámicamente en tiempo de ejecución (o dentro de instancias heredadas que no sean CDI), use la búsqueda estática Instance<T> o Quarkus Arc.container().

@ApplicationScoped
public class DynamicPluginRegistry {

    @Inject
    Instance<PaymentProcessor> processors;

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

9. Comparación arquitectónica: Quarkus ArC vs Spring IoC vs Weld

Característica Quarkus ArC Contenedor de COI de primavera Soldadura estándar de Yakarta
Resolución de dependencia Tiempo de construcción (AOT) Reflexión en tiempo de ejecución Reflexión en tiempo de ejecución
Sobrecarga de RAM de metaespacio Extremadamente bajo (~0 MB para metadatos) Alto (caché de clase de proxy dinámico) Moderado a alto
Hora de inicio Milisegundos (< 0,05 s) Segundos (2,5 s - 8 s) Segundos (1,5s - 4s)
Soporte nativo de GraalVM Nativo listo para usar Requiere sugerencias nativas de Spring Configuración compleja
Poda de frijol no utilizado Poda automática en tiempo de construcción Mantenido en Classpath Mantenido en Classpath
Inyección de un solo constructor Implícito (no se necesita anotación) Apoyado Requiere @Inject

10. Prueba unitaria de beans CDI con @QuarkusTest y @InjectMock

Quarkus facilita las pruebas de beans CDI con JUnit 5 y RestAssured. Utilice @InjectMock para reemplazar los beans de dependencia con simulacros de Mockito automáticamente durante las pruebas.

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. Conclusión

La inyección de dependencia en Quarkus con CDI (ArC) marca un cambio monumental en el diseño empresarial de Java. Al transferir el escaneo de metadatos de beans, la generación de proxy y la resolución de gráficos de inyección al tiempo de compilación, Quarkus ofrece velocidades de inicio supersónicas, huellas de memoria subatómica y soporte binario nativo GraalVM perfecto.

Ya sea que esté migrando desde Spring Boot o creando microservicios nativos de la nube totalmente nuevos en Kubernetes, dominar Quarkus CDI le brinda el rendimiento y la productividad del desarrollador necesarios para la arquitectura de software moderna.

Ghaznix Ecosystem Products

Empower Your Digital Presence & Workflows

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