什么是 ApplicationRunner 和 CommandLineRunner

Spring Boot 提供了两个接口,用于在 应用上下文(ApplicationContext)完全初始化后 执行自定义逻辑:

1. CommandLineRunner

@FunctionalInterface
public interface CommandLineRunner {void run(String... args) throws Exception;
}
  • 接收的是 main 方法传入的原始命令行参数
  • 更贴近“命令行应用”语义

2. ApplicationRunner

@FunctionalInterface
public interface ApplicationRunner {void run(ApplicationArguments args) throws Exception;
}
  • 参数是封装后的 ApplicationArguments,可方便地解析 --option 和非选项参数
  • 功能更强大,推荐使用

实战:五大核心应用场景

场景一:缓存预热(Cache Warm-up)

@Component
@Order(1) // 优先执行
@Slf4j
public class CacheWarmupRunner implements ApplicationRunner {@Autowiredprivate ProductService productService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic void run(ApplicationArguments args) {log.info("Starting cache warm-up...");List<Product> products = productService.getAllActiveProducts();products.forEach(product -> redisTemplate.opsForValue().set("product:" + product.getId(), product, Duration.ofHours(24)));log.info("Cache warm-up completed. Loaded {} products.", products.size());}
}

避免“缓存雪崩”,提升首访性能

场景二:启动后健康检查(Post-Startup Health Check)

@Component
@Order(2)
@Slf4j
public class PostStartupHealthCheckRunner implements ApplicationRunner {@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DataSource dataSource;@Overridepublic void run(ApplicationArguments args) {log.info("Running post-startup health checks...");try {// 检查外部服务String result = restTemplate.getForObject("http://payment-service/health", String.class);log.info("Payment service health: {}", result);// 检查数据库try (Connection conn = dataSource.getConnection()) {Assertions.assertTrue(conn.isValid(2));}log.info("Database connection OK.");} catch (Exception e) {log.error("Health check failed!", e);// 可选择:发送告警、标记服务为“不健康”等// 注意:这里抛异常会阻止应用启动!需根据策略处理}}
}

⚠️ 注意:若任务失败,可能阻止应用启动。生产环境建议异步或降级处理。

场景三:异步加载非关键数据

@Component
@Slf4j
public class AsyncDataLoaderRunner implements ApplicationRunner {@Autowiredprivate AsyncTaskExecutor taskExecutor; // 如 SimpleAsyncTaskExecutor@Autowiredprivate ReportService reportService;@Overridepublic void run(ApplicationArguments args) {log.info("Scheduling async data loading...");taskExecutor.execute(() -> {try {reportService.preloadMonthlyReports();log.info("Monthly reports preloaded.");} catch (Exception e) {log.warn("Async preload failed, will retry later.", e);}});}
}

不阻塞主启动流程,提升启动速度