В чём разница между @ApplicationScoped, @RequestScoped и @Singleton в Quarkus?
@ApplicationScoped создаёт один прокси-объект на всё приложение, @RequestScoped — новый экземпляр на каждый HTTP-запрос, @Singleton — один объект без прокси (быстрее, но без перехватчиков и CDI-событий).
Скоупы CDI в Quarkus: @ApplicationScoped, @RequestScoped, @Singleton
@ApplicationScoped
Создаёт один экземпляр бина на всё время жизни приложения, обёрнутый в прокси-объект. Прокси позволяет:
- Использовать перехватчики (
@Transactional,@Logged) - Ленивую инициализацию — экземпляр создаётся при первом обращении
- CDI-события (
@Observes)
Требует конструктор без аргументов или конструктор с @Inject для CDI-управляемых зависимостей.
@RequestScoped
Создаёт новый экземпляр для каждого HTTP-запроса (или другого активного контекста запроса — Kafka-сообщения, gRPC-вызова). После завершения запроса экземпляр уничтожается и вызывается метод @PreDestroy. Тоже прокси-объект.
@Singleton
Создаёт один экземпляр без прокси. Преимущества: чуть быстрее (нет накладных расходов прокси), проще в дебаге. Ограничения: перехватчики не работают, ленивая инициализация недоступна (создаётся при старте приложения).
Сравнительная таблица
- @ApplicationScoped: 1 экземпляр / прокси / поддерживает interceptors / ленивый
- @Singleton: 1 экземпляр / без прокси / не поддерживает interceptors / eager
- @RequestScoped: 1 на запрос / прокси / поддерживает interceptors / ленивый
- @Dependent: 1 на инъекцию / без прокси / не поддерживает interceptors / ленивый
Пример кода
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Singleton;
import jakarta.inject.Inject;
import jakarta.interceptor.Interceptors;
// ApplicationScoped: кешируем данные, используем @Transactional
@ApplicationScoped
public class UserCacheService {
private final java.util.Map<Long, String> cache = new java.util.concurrent.ConcurrentHashMap<>();
public String get(Long id) { return cache.get(id); }
public void put(Long id, String name) { cache.put(id, name); }
}
// RequestScoped: держим данные текущего запроса
@RequestScoped
public class RequestContext {
private String currentUserId;
private long startTime = System.currentTimeMillis();
public String getCurrentUserId() { return currentUserId; }
public void setCurrentUserId(String id) { this.currentUserId = id; }
public long getElapsed() { return System.currentTimeMillis() - startTime; }
}
// Singleton: простая утилита без перехватчиков
@Singleton
public class MathUtils {
public double calculateDiscount(double price, int percent) {
return price * (1 - percent / 100.0);
}
}
// Использование всех трёх в ресурсе
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
@Path("/orders")
@ApplicationScoped
public class OrderResource {
@Inject UserCacheService cache; // один на всё приложение
@Inject RequestContext reqCtx; // один на этот запрос
@Inject MathUtils math; // один, без прокси
@GET
@Path("/price")
@Produces(MediaType.APPLICATION_JSON)
public double getPrice() {
reqCtx.setCurrentUserId("user-42");
return math.calculateDiscount(100.0, 15);
}
}
Когда что выбирать
@ApplicationScoped— большинство сервисов: репозитории, сервисы с транзакциями, кеши.@RequestScoped— данные, специфичные для запроса: текущий пользователь, трассировка, контекст безопасности.@Singleton— утилиты без состояния и без нужды в перехватчиках; конфигурационные объекты.
Подводные камни
@Singletonне создаёт прокси — если вы добавите@Transactionalна метод@Singleton-бина, транзакция НЕ будет применена; Quarkus выдаст предупреждение при сборке.- Инъекция
@RequestScoped-бина в@ApplicationScoped-бин через поле работает корректно только через прокси — не через конструктор без аргументов; в противном случае вы получите экземпляр вне контекста запроса иContextNotActiveException. @ApplicationScopedленивый — первый запрос чуть медленнее. Для принудительной eager-инициализации добавьте@Startup(Quarkus 3.x).@Singletonсоздаётся при старте приложения — тяжёлая инициализация (соединение с БД, загрузка файлов) в конструкторе замедлит старт.- В native-режиме прокси-классы генерируются ArC на этапе сборки — динамическое создание скоупных бинов в рантайме невозможно.
- Отсутствие конструктора без аргументов у прокси-бина (
@ApplicationScoped,@RequestScoped) — ошибка при деплое; добавьте защищённый no-arg конструктор если используете конструкторную инъекцию. @RequestScopedработает только в рамках активного CDI-контекста — в фоновых задачах (@Scheduled) контекст запроса не активен, бин не доступен без явной активации контекста.
Common mistakes
- Путать термин «quarkus scopes» с соседним механизмом Quarkus.
- Не называть границу lifecycle, transaction, thread или request для «quarkus scopes».
- Игнорировать production-эффекты «quarkus scopes»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «quarkus scopes» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «quarkus scopes».
- Уточнить, какие настройки или API меняют «quarkus scopes» в реальном сервисе.