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

Внедрение зависимостей в Quarkus с помощью CDI: ArC, области видимости, квалификаторы и внедрение во время сборки

Внедрение зависимостей в Quarkus с помощью CDI: ArC, области видимости, квалификаторы и внедрение во время сборки

Внедрение зависимостей (DI) — это основа современной разработки корпоративных приложений Java. В течение многих лет разработчики Java полагались на контейнеры Spring IoC (инверсия управления) или стандартные контейнеры Jakarta EE CDI (внедрение контекста и зависимостей), такие как Hibernate Weld или OpenWebBeans, для разделения жизненных циклов компонентов, управления зависимостями сервисов и внедрения свойств конфигурации.

Однако по мере перехода корпоративных приложений к облачным микросервисам, контейнеризации с помощью Docker и Kubernetes и бессерверным моделям выполнения (AWS Lambda, Knative) традиционные контейнеры внедрения зависимостей выявили критический недостаток: тяжелое отражение во время выполнения и большие затраты памяти.

Познакомьтесь с Quarkus и его облегченным механизмом CDI ArC. Созданный с нуля для компиляции Kubernetes и GraalVM, Quarkus переопределяет внедрение зависимостей, перемещая отражение, сканирование путей к классам и связывание графов компонентов со времени выполнения приложения на время сборки.

В этом подробном руководстве мы рассмотрим Внедрение зависимостей в Quarkus с помощью CDI, охватывая внутреннюю механику ArC-контейнера, области CDI, пользовательские квалификаторы, производители, перехватчики, управляемые событиями наблюдатели, программный поиск и лучшие практики написания высокопроизводительных облачных Java-приложений.


1. Понимание ArC: Quarkus Build-Time CDI Engine

Стандартные контейнеры Jakarta EE CDI работают полностью при запуске приложения. Когда загружается устаревшее приложение, контейнер CDI сканирует каждый JAR в пути к классам, анализирует метаданные, проверяет аннотации с помощью Java Reflection (java.lang.reflect), генерирует динамические прокси в памяти и строит граф зависимостей. Это динамическое отражение потребляет значительный объем оперативной памяти (метапространство) и увеличивает время загрузки при холодном запуске.

Quarkus решает это узкое место с помощью ArC (реализация CDI во время сборки, основанная на спецификациях Jakarta CDI 4.0).

Традиционный CDI во время выполнения с Reflection и подключение Quarkus ArC во время сборки

Чем ArC отличается от стандартного CDI:

  1. Дополнение во время сборки: во время mvn package или ./gradlew build ArC сканирует все прикладные компоненты, проверяет точки внедрения и предварительно строит граф зависимостей.
  2. Упреждающая генерация байт-кода (AOT): ArC заранее генерирует прямой байт-код Java для создания экземпляров, прокси-компонентов и перехватчиков, устраняя отражение во время выполнения.
  3. Очистка неиспользуемых компонентов (устранение мертвого кода). Любой компонент CDI, который никогда не внедряется и не используется динамически, идентифицируется во время компиляции и автоматически удаляется из конечного двоичного файла, что уменьшает объем памяти.
  4. Встроенная оптимизация образа GraalVM. Поскольку связывание компонентов разрешается во время сборки, приложения Quarkus легко компилируются в собственные исполняемые файлы с подсказками по настройке без отражения.

2. Области применения CDI-компонентов и управление жизненным циклом

В CDI Область определяет жизненный цикл и видимость экземпляра компонента. Quarkus ArC поддерживает стандартные области Jakarta CDI с оптимизацией во время сборки.

Диаграмма областей действия и жизненного цикла компонентов Quarkus CDI Bean

Основные области CDI в Quarkus:

Аннотация к области действия Жизненный цикл и поведение Создание прокси Лучший вариант использования
@ApplicationScoped Один экземпляр для каждого приложения. Ленивая инициализация при первом вызове. Состояние является общим для всех потоков. Создан клиентский прокси Сервисы без сохранения состояния, репозитории, конфигурации.
@RequestScoped Экземпляр, привязанный к одному HTTP-запросу или вызову события. Уничтожается после завершения запроса. Создан клиентский прокси Пользовательские сеансы, контекстные данные запроса, токены безопасности.
@Зависимый Область по умолчанию, если она не указана. Экземпляр напрямую связан со временем жизни внедряющего компонента. Нет прокси (прямой экземпляр) Вспомогательные служебные классы, держатели эфемерных состояний.
@Синглтон Один экземпляр для каждого приложения. Создается с нетерпением или лениво. Нет оболочки клиентского прокси. Нет прокси (прямой экземпляр) Высокопроизводительные синглтоны, в которых необходимо избегать накладных расходов клиентского прокси.
@Неудаляемый Маркерная аннотация, указывающая ArC не удалять компонент во время оптимизации во время сборки. Зависит от объема Цели динамического поиска или bean-компоненты, ссылающиеся на отражение.

Пример кода: объявление bean-компонентов с ограниченной областью действия

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. Шаблоны внедрения зависимостей в Quarkus

Quarkus поддерживает несколько стилей внедрения, используя стандартную аннотацию jakarta.inject.Inject.

A. Внедрение в конструктор (рекомендуемая передовая практика)

Внедрение в конструктор гарантирует неизменность (поля final), упрощает модульное тестирование без необходимости использования контейнеров CDI и делает зависимости явными.

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

Совет: если в Quarkus ArC компонент имеет только один конструктор, вы можете полностью опустить аннотацию @Inject. ArC автоматически распознает его как конструктор внедрения.

B. Полевая инъекция

Внедрение полей является кратким, но делает поля изменяемыми и усложняет автономное модульное тестирование.

@ApplicationScoped
public class NotificationService {

    @Inject
    EmailClient emailClient; // Field injection
}

4. Квалификаторы и устранение неоднозначности в нескольких реализациях

Когда несколько bean-компонентов реализуют один и тот же интерфейс, CDI генерирует неоднозначное исключение зависимости во время сборки. Quarkus использует Квалификаторы для безопасного разрешения реализаций целевых компонентов.

Определение пользовательских квалификаторов

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

Реализация квалифицированных компонентов

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

Внедрение квалифицированных компонентов

@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. Производители и диспоузеры: внедрение сторонних объектов

Иногда вам необходимо внедрить классы из сторонних библиотек (например, клиентов AWS SDK, соединений Redis, пользовательских компонентов конфигурации), которые не помечены @ApplicationScoped. Quarkus решает эту проблему с помощью Producer Methods и 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. Перехватчики CDI: аспектно-ориентированное программирование (АОП)

Перехватчики позволяют привязывать к методам bean-компонентов сквозные задачи (ведение журналов, контрольные журналы, метрики, ограничение скорости), не затрагивая бизнес-логику.

Шаг 1. Определите привязку перехватчика

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

Шаг 2. Реализация логики перехватчика

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

Шаг 3. Аннотирование целевых компонентов

@ApplicationScoped
public class AnalyticsService {

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

Поскольку Quarkus генерирует статические прокси-перехватчики во время сборки, накладные расходы на вызов методов практически равны нулю по сравнению с динамическими прокси-серверами времени выполнения CGLIB в Spring Boot.


7. Архитектура, управляемая событиями, с событиями CDI

CDI обеспечивает встроенную синхронную и асинхронную публикацию событий для разделения внутрипроцессного взаимодействия.

Публикация и наблюдение за событиями

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

События жизненного цикла приложения

Quarkus предоставляет события запуска и завершения работы для выполнения кода инициализации при запуске или завершении приложения:

@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. Программный поиск компонентов с помощью Instance<T> и Arc.container()

Если целевые реализации должны выбираться динамически во время выполнения (или внутри устаревших экземпляров, не поддерживающих CDI), используйте статический поиск Instance<T> или Quarkus Arc.container().

@ApplicationScoped
public class DynamicPluginRegistry {

    @Inject
    Instance<PaymentProcessor> processors;

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

9. Архитектурное сравнение: Quarkus ArC, Spring IoC и Weld

Особенность Кваркус ArC Spring IoC-контейнер Стандартная Джакарта Сварка
Разрешение зависимостей Время сборки (AOT) Отражение во время выполнения Отражение во время выполнения
Затраты оперативной памяти Metaspace Крайне низкий (~0 МБ для метаданных) Высокий (кэш классов динамического прокси) От умеренного до высокого
Время запуска Миллисекунды (< 0,05 с) Секунды (2,5–8 с) Секунды (1,5–4 с)
Встроенная поддержка GraalVM Нативный, готовый к использованию Требуются встроенные подсказки Spring Сложная конфигурация
Обрезка неиспользованных бобов Автоматическое сокращение во время сборки Хранится в пути к классам Хранится в пути к классам
Одиночное внедрение в конструктор Неявный (аннотация не требуется) Поддерживается Требуется @Inject

10. Модульное тестирование CDI-компонентов с помощью @QuarkusTest и @InjectMock

Quarkus упрощает тестирование компонентов CDI с помощью JUnit 5 и RestAssured. Используйте @InjectMock для автоматической замены компонентов зависимостей макетами Mockito во время тестирования.

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. Заключение

Внедрение зависимостей в Quarkus с помощью CDI (ArC) знаменует собой монументальный сдвиг в корпоративном дизайне Java. Перенося сканирование метаданных компонентов, генерацию прокси и разрешение графов внедрения во время компиляции, Quarkus обеспечивает сверхзвуковую скорость запуска, субатомный объем памяти и бесшовную встроенную поддержку двоичных файлов GraalVM.

Независимо от того, переходите ли вы со Spring Boot или создаете новые облачные микросервисы на Kubernetes, освоение Quarkus CDI обеспечит вам производительность и продуктивность разработки, необходимые для современной архитектуры программного обеспечения.

Ghaznix Ecosystem Products

Empower Your Digital Presence & Workflows

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