QuarkusMiddleTechnical

В чём разница между @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» в реальном сервисе.

Sources

Related topics