Spring BootMiddleTechnical
Что такое CommandLineRunner и ApplicationRunner и когда их использовать?
CommandLineRunner и ApplicationRunner — функциональные интерфейсы, чьи run()-методы вызываются после полной инициализации ApplicationContext. CommandLineRunner получает String[], ApplicationRunner — ApplicationArguments с именованными опциями. Оба блокируют завершение старта до возврата из run().
Контракт и порядок вызова
После того как SpringApplication.run() поднял ApplicationContext и вызвал ApplicationContext.refresh(), он обходит все бины типа CommandLineRunner и ApplicationRunner и вызывает их run()-методы. Порядок между несколькими runner-ами задаётся аннотацией @Order или интерфейсом Ordered.
CommandLineRunner — сырые аргументы
@Component
@Order(1)
public class DataSeeder implements CommandLineRunner {
private final UserRepository userRepo;
public DataSeeder(UserRepository userRepo) {
this.userRepo = userRepo;
}
@Override
public void run(String... args) throws Exception {
// args = ["--env=prod", "migrate"] — сырые строки из командной строки
if (userRepo.count() == 0) {
userRepo.save(new User("admin", "admin@example.com"));
System.out.println("Seed data inserted");
}
}
}
ApplicationRunner — разобранные аргументы
@Component
@Order(2)
public class MigrationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// запуск: java -jar app.jar --mode=migrate --dry-run
boolean dryRun = args.containsOption("dry-run");
List<String> modes = args.getOptionValues("mode"); // ["migrate"]
List<String> nonOption = args.getNonOptionArgs(); // позиционные аргументы
System.out.printf("Mode: %s, dryRun: %s%n", modes, dryRun);
if (!dryRun) {
// реальная миграция
}
}
}
Использование в тестах
@SpringBootTest
class DataSeederTest {
@Autowired
private UserRepository userRepo;
@Test
void seedRuns_whenRepoEmpty() {
// DataSeeder уже запустился при старте контекста
assertThat(userRepo.count()).isGreaterThan(0);
}
}
// Если нужно запустить runner вручную в тесте:
@SpringBootTest
class MigrationRunnerTest {
@Autowired
private MigrationRunner runner;
@Test
void dryRunDoesNotMutate() throws Exception {
ApplicationArguments args = new DefaultApplicationArguments("--dry-run");
runner.run(args); // не бросает исключений, ничего не меняет
}
}
Типичные сценарии применения
- Наполнение БД начальными данными (seed)
- Проверка конфигурации при старте (fail-fast)
- Запуск однократных миграций данных
- Прогрев кэша перед принятием трафика
- CLI-режим: приложение выполняет задачу и завершается
Подводные камни
- Блокировка readiness: runner блокирует финальный этап старта. Долгий runner задержит сигнал готовности в Kubernetes (readiness probe), что приведёт к таймауту и перезапуску пода. Тяжёлые операции запускайте в отдельном потоке или через
@Async. - Исключение в run() = падение приложения: непойманное исключение из runner завершит приложение с ненулевым exit code. Оборачивайте в try/catch или используйте retry-логику.
- Тест-контекст запускает runner-ы: в
@SpringBootTestвсе runner-ы выполняются при каждом поднятии контекста. Если runner делает side-effect (insert в БД), тест может упасть при повторном запуске. Используйте@MockBeanдля изоляции или проверяйте идемпотентность. - CommandLineRunner не разбирает опции:
--key=valueпридёт как одна строка, а не как пара ключ/значение. Для параметризованных CLI используйтеApplicationRunner. - Несколько runner-ов без @Order: порядок выполнения недетерминирован, если
@Orderне задан. В зависимых runner-ах это приведёт к случайным ошибкам. - Транзакции в runner: runner-методы не оборачиваются в транзакцию автоматически. Добавьте
@Transactionalна методrun()или вызывайте транзакционный сервис. - Не подходит для периодических задач: runner выполняется один раз при старте. Для повторяющихся задач используйте
@Scheduledили внешний планировщик. - Профили и условная активация: если runner нужен только в определённой среде, комбинируйте с
@Profile("dev")или@ConditionalOnProperty— иначе seed-данные попадут в продакшн.
Common mistakes
- Путать термин «commandline application runner» с соседним механизмом Spring Boot.
- Не называть границу lifecycle, transaction, thread или request для «commandline application runner».
- Игнорировать production-эффекты «commandline application runner»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «commandline application runner» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «commandline application runner».
- Уточнить, какие настройки или API меняют «commandline application runner» в реальном сервисе.