QuarkusMiddleTechnical

Как Quarkus реализует CDI (Jakarta Contexts and Dependency Injection)?

Quarkus реализует CDI через ArC — облегчённый контейнер, который обрабатывает аннотации во время сборки (build-time), генерирует байткод и не использует рефлексию в рантайме.

CDI в Quarkus: ArC и build-time обработка

Quarkus не использует полноценный CDI-контейнер (Weld или OpenWebBeans) в рантайме. Вместо этого применяется ArC — собственная реализация CDI, которая обрабатывает все аннотации во время сборки (build time), генерирует Java-классы-прокси и инжекторы, и стартует с почти нулевыми накладными расходами.

Ключевые аннотации CDI в Quarkus

  • @ApplicationScoped — один экземпляр на всё время жизни приложения (прокси-объект)
  • @RequestScoped — новый экземпляр на каждый HTTP-запрос (или другой контекст запроса)
  • @Singleton — один экземпляр без прокси (быстрее, но нет перехвата методов)
  • @Dependent — новый экземпляр при каждой инъекции (скоуп по умолчанию)
  • @Inject — точка инъекции зависимости
  • @Produces — фабричный метод, возвращающий бин
  • @Qualifier — кастомный квалификатор для разрешения неоднозначности

Пример: инъекция зависимостей и продюсер

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

// Кастомный квалификатор
@jakarta.inject.Qualifier
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD,
    java.lang.annotation.ElementType.METHOD,
    java.lang.annotation.ElementType.PARAMETER})
public @interface Premium {}

// Интерфейс сервиса
public interface GreetingService {
    String greet(String name);
}

// Обычная реализация
@ApplicationScoped
public class SimpleGreetingService implements GreetingService {
    @Override
    public String greet(String name) {
        return "Hello, " + name;
    }
}

// Премиум реализация
@ApplicationScoped
@Premium
public class PremiumGreetingService implements GreetingService {
    @Override
    public String greet(String name) {
        return "Welcome, VIP " + name + "!";
    }
}

// Ресурс, использующий инъекцию
@jakarta.ws.rs.Path("/greet")
@ApplicationScoped
public class GreetingResource {

    @Inject
    GreetingService defaultService;  // инжектируется SimpleGreetingService

    @Inject
    @Premium
    GreetingService premiumService;  // инжектируется PremiumGreetingService

    @jakarta.ws.rs.GET
    @jakarta.ws.rs.Path("/{name}")
    public String greet(@jakarta.ws.rs.PathParam("name") String name) {
        return defaultService.greet(name);
    }
}

Продюсеры и альтернативы

@Singleton
public class ConfigProducer {

    @org.eclipse.microprofile.config.inject.ConfigProperty(name = "app.api.key")
    String apiKey;

    @Produces
    @ApplicationScoped
    public ApiClient produceApiClient() {
        return new ApiClient(apiKey);
    }
}

Перехватчики (Interceptors)

import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
import jakarta.interceptor.InterceptorBinding;

@InterceptorBinding
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE,
    java.lang.annotation.ElementType.METHOD})
public @interface Logged {}

@Logged
@Interceptor
public class LoggingInterceptor {

    @AroundInvoke
    public Object logMethod(InvocationContext ctx) throws Exception {
        System.out.println("Calling: " + ctx.getMethod().getName());
        Object result = ctx.proceed();
        System.out.println("Done: " + ctx.getMethod().getName());
        return result;
    }
}

@ApplicationScoped
@Logged
public class OrderService {
    public void processOrder(Long id) { /* ... */ }
}

Подводные камни

  • ArC поддерживает не весь CDI 4.0 — ряд динамических возможностей (например, BeanManager.createInstance() с рантайм-добавлением контекстов) недоступен.
  • @Singleton не создаёт прокси, поэтому перехватчики и события CDI не работают с ним — используйте @ApplicationScoped, если нужны interceptors.
  • Бины, не используемые напрямую, могут быть удалены ArC при сборке (dead bean elimination) — добавьте @Unremovable или зарегистрируйте через application.properties: quarkus.arc.remove-unused-beans=false.
  • Конструктор без аргументов обязателен для proxy-бинов (@ApplicationScoped, @RequestScoped); без него — jakarta.enterprise.inject.CreationException при старте.
  • Циклические зависимости через поля решаются прокси, но циклические зависимости через конструкторы — нет; Quarkus бросит ошибку на этапе сборки.
  • Обнаружение бинов работает только в архивах с beans.xml или помеченных аннотацией @RegisterForReflection при native build.
  • Транзакции через @Transactional работают только на прокси-бинах — прямые вызовы методов внутри одного класса не перехватываются.
  • В native-сборке ArC не включает рефлексивный доступ автоматически — классы, создаваемые через рефлексию, нужно явно регистрировать.

Common mistakes

  • Путать термин «quarkus cdi» с соседним механизмом Quarkus.
  • Не называть границу lifecycle, transaction, thread или request для «quarkus cdi».
  • Игнорировать production-эффекты «quarkus cdi»: latency, SQL shape, memory, security или observability.

What the interviewer is testing

  • Попросить объяснить механизм «quarkus cdi» на минимальном примере.
  • Проверить, видит ли кандидат failure mode и диагностику для «quarkus cdi».
  • Уточнить, какие настройки или API меняют «quarkus cdi» в реальном сервисе.

Sources

Related topics