QuarkusMiddleTechnical

Что такое build-time augmentation и как оно связано с быстрым startup?

Build-time augmentation — этап сборки Quarkus, когда расширения анализируют аннотации, генерируют байткод и выполняют конфигурацию заранее. В результате при старте приложения JVM не сканирует classpath и не строит DI-контейнер — отсюда быстрый запуск.

Build-time Augmentation в Quarkus

Build-time augmentation — центральная архитектурная идея Quarkus. Вместо того чтобы выполнять инициализацию фреймворка во время запуска приложения (как это делает Spring Boot), Quarkus делает максимум работы на этапе сборки Maven/Gradle.

Что происходит при обычном фреймворке (Spring Boot)

  1. JVM загружает все классы из classpath (~5000+)
  2. Spring сканирует аннотации (@Component, @Autowired)
  3. Строит BeanFactory, создаёт ApplicationContext
  4. Разрешает зависимости, создаёт прокси через CGLIB
  5. Инициализирует DataSource, Kafka consumers и т.д.

Всё это происходит при каждом запуске → 2–5 секунд старта.

Build-time Augmentation: механизм

Quarkus-расширение состоит из двух частей:

  • Build-time processor — запускается при mvn package, анализирует аннотации, генерирует байткод
  • Runtime recorder — сгенерированный код, который выполняется при старте
// Пример build-time processor из расширения Quarkus
// (упрощённо — так работают внутренние механизмы)
@BuildStep
public BeanDefiningAnnotationBuildItem registerCdiAnnotations() {
    // Сообщает CDI-контейнеру о кастомной аннотации на этапе сборки
    return new BeanDefiningAnnotationBuildItem(
        DotName.createSimple("com.example.MyComponent"),
        BuiltinScope.APPLICATION_SCOPED.getInfo()
    );
}

@BuildStep
@Record(RUNTIME_INIT)
public void configureDataSource(
        DataSourceBuildItem dataSource,
        DataSourceRecorder recorder) {
    // recorder.createPool() — это шаблон, который будет
    // выполнен при старте, но уже с предвычисленными параметрами
    recorder.createPool(dataSource.getName(), dataSource.getConfig());
}

Что делается на build-time

  • CDI DI-граф полностью строится и верифицируется
  • Hibernate ORM маппинги анализируются, SQL генерируется заранее
  • JAX-RS роуты компилируются в оптимальный dispatch-код
  • Аннотации Fault Tolerance, Health, Metrics регистрируются
  • Reflection-конфиги для GraalVM генерируются автоматически

Визуализация: что происходит при запуске

./mvnw package  # Build-time augmentation: 15-30 сек
                # Результат: target/quarkus-app/ с предгенерированным кодом

java -jar target/quarkus-app/quarkus-run.jar
# Quarkus стартует только runtime-часть: ~100-800 мс
# Никакого classpath-сканирования, никакого рефлексивного DI

Как это связано с Native

# При native-сборке augmentation + GraalVM native-image:
./mvnw package -Pnative
# 1. Build-time augmentation (30 сек)
# 2. GraalVM static analysis + compilation (5-10 мин)
# Результат: бинарник, стартующий за 15 мс

./target/app-runner
# 2026-01-15 10:00:00 INFO  [io.quarkus] app started in 0.015s

Ограничения build-time подхода

// НЕЛЬЗЯ на build-time: читать env vars или внешние конфиги
@BuildStep
public void badStep() {
    String env = System.getenv("MY_VAR"); // ошибка!
    // env не существует во время сборки
}

// ПРАВИЛЬНО: читать конфиг через ConfigProperty в runtime recorder
@Record(RUNTIME_INIT)
public void goodStep(MyConfig config, MyRecorder recorder) {
    recorder.initialize(config); // config читается при старте
}

Dev-режим и augmentation

В quarkus:dev augmentation выполняется инкрементально при каждом изменении файла — отсюда live coding без полного перезапуска.

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

  • @ConfigProperty с defaultValue в native — значение встраивается в бинарник на build-time; изменить без пересборки невозможно. Для runtime-конфигурации используйте env vars.
  • Сторонние библиотеки без Quarkus-расширения — их инициализация остаётся в runtime; они не получают build-time оптимизаций.
  • Сложность написания расширений — BuildStep API нетривиален; стандартные расширения покрывают 95% случаев, но кастомное расширение требует глубокого понимания API.
  • Ошибки DI видны только при сборке — это хорошо, но к этому нужно привыкнуть; в Spring ошибки DI иногда обнаруживаются только в runtime через lazy init.
  • Live reload не всегда срабатывает — изменения в build-step коде расширения требуют полного перезапуска dev-режима.
  • Условные бины через @IfBuildProfile — условие проверяется на build-time; нельзя переключать профиль в runtime без пересборки.

Common mistakes

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

What the interviewer is testing

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics