什么是 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);}});}
}
✅ 不阻塞主启动流程,提升启动速度