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» в реальном сервисе.