Spring Boot中的Runners:它们是什么以及如何使用

概述
您是否曾经想过如何在Spring Boot应用程序启动时自动运行一些代码?这就是runners发挥作用的地方!
Spring Boot为您提供了两种简单的方法来在应用程序启动后立即运行逻辑:
CommandLineRunner
ApplicationRunner
让我们以一种非常简单的方式来分解它们 👇
什么是Runner?
Runner只是Spring Boot中的一个特殊类,它在应用程序启动后运行一些代码,在实际业务逻辑开始之前。
将其视为:
"在应用程序完全启动之前进行这个设置/检查!"
1️⃣ CommandLineRunner 简单易用
您获得传递给应用程序的参数(String... args
当您不需要花哨的参数处理时最佳
示例:
  1. import org.springframework.boot.CommandLineRunner;
  2. import org.springframework.stereotype.Component;

  3. @Component
  4. public class MyStartupRunner implements CommandLineRunner {

  5. @Override
  6. public void run(String... args) throws Exception {
  7. System.out.println("✅ 应用程序已启动!CommandLineRunner正在运行...");
  8. }
  9. }
java
一旦您的应用程序运行,它将在控制台中打印该消息。
2️⃣ ApplicationRunner 更智能的参数处理
它为您提供ApplicationArguments,因此您可以轻松检查传递的--options
当您想要以干净的方式解析命令行输入时很棒
示例:
  1. import org.springframework.boot.ApplicationArguments;
  2. import org.springframework.boot.ApplicationRunner;
  3. import org.springframework.stereotype.Component;

  4. @Component
  5. public class MyAppRunner implements ApplicationRunner {

  6. @Override
  7. public void run(ApplicationArguments args) throws Exception {
  8. System.out.println("✅ ApplicationRunner正在运行...");
  9. System.out.println("👉 选项名称: " + args.getOptionNames());
  10. }
  11. }
java
🎯 何时使用Runners?
以下是一些实际用例
用例
示例
将数据加载到数据库
在开发环境中填充测试数据
启动检查
验证文件路径或连接
缓存设置
调用外部API并存储配置
记录启动信息
打印自定义横幅或信息
🔢 如果您有多个Runners怎么办?
没问题!您可以使用@Order控制它们运行的顺序
  1. @Component
  2. @Order(1)
  3. public class FirstRunner implements CommandLineRunner {
  4. public void run(String... args) {
  5. System.out.println("🏁 第一个runner已执行");
  6. }
  7. }
java
🧠 TL;DR(快速总结)
特性
CommandLineRunner
ApplicationRunner
参数类型
String[]
ApplicationArguments
简单使用
结构化参数解析
实际用例
设置、日志、加载数据
相同,但参数处理更好
最终思考
Runners是一种在启动时运行代码的简洁方法,无需触及控制器或服务。它们简单、强大,经常在现实世界的Spring Boot应用程序中使用。
实际应用示例
1. 数据初始化Runner
  1. @Component
  2. @Order(1)
  3. public class DataInitializationRunner implements CommandLineRunner {
  4. private final UserRepository userRepository;
  5. private final ProductRepository productRepository;
  6. public DataInitializationRunner(UserRepository userRepository,
  7. ProductRepository productRepository) {
  8. this.userRepository = userRepository;
  9. this.productRepository = productRepository;
  10. }
  11. @Override
  12. public void run(String... args) throws Exception {
  13. System.out.println("🔄 开始初始化数据...");
  14. // 检查是否需要初始化用户数据
  15. if (userRepository.count() == 0) {
  16. initializeUsers();
  17. }
  18. // 检查是否需要初始化产品数据
  19. if (productRepository.count() == 0) {
  20. initializeProducts();
  21. }
  22. System.out.println("✅ 数据初始化完成!");
  23. }
  24. private void initializeUsers() {
  25. List<User> users = Arrays.asList(
  26. new User("admin", "admin@example.com"),
  27. new User("user1", "user1@example.com"),
  28. new User("user2", "user2@example.com")
  29. );
  30. userRepository.saveAll(users);
  31. System.out.println("👥 用户数据已初始化");
  32. }
  33. private void initializeProducts() {
  34. List<Product> products = Arrays.asList(
  35. new Product("iPhone 15", 999.99),
  36. new Product("MacBook Pro", 1999.99),
  37. new Product("AirPods Pro", 249.99)
  38. );
  39. productRepository.saveAll(products);
  40. System.out.println("📱 产品数据已初始化");
  41. }
  42. }
java
2. 系统检查Runner
  1. @Component
  2. @Order(2)
  3. public class SystemCheckRunner implements ApplicationRunner {
  4. private final Environment env;
  5. private final DataSource dataSource;
  6. public SystemCheckRunner(Environment env, DataSource dataSource) {
  7. this.env = env;
  8. this.dataSource = dataSource;
  9. }
  10. @Override
  11. public void run(ApplicationArguments args) throws Exception {
  12. System.out.println("🔍 开始系统检查...");
  13. // 检查数据库连接
  14. checkDatabaseConnection();
  15. // 检查必要的文件路径
  16. checkRequiredPaths();
  17. // 检查环境变量
  18. checkEnvironmentVariables();
  19. // 检查命令行参数
  20. if (args.containsOption("debug")) {
  21. System.out.println("🐛 调试模式已启用");
  22. }
  23. System.out.println("✅ 系统检查完成!");
  24. }
  25. private void checkDatabaseConnection() {
  26. try (Connection conn = dataSource.getConnection()) {
  27. System.out.println("✅ 数据库连接正常");
  28. } catch (SQLException e) {
  29. System.err.println("❌ 数据库连接失败: " + e.getMessage());
  30. throw new RuntimeException("数据库连接检查失败", e);
  31. }
  32. }
  33. private void checkRequiredPaths() {
  34. String uploadPath = env.getProperty("app.upload.path", "/tmp/uploads");
  35. File uploadDir = new File(uploadPath);
  36. if (!uploadDir.exists()) {
  37. uploadDir.mkdirs();
  38. System.out.println("📁 创建上传目录: " + uploadPath);
  39. } else {
  40. System.out.println("✅ 上传目录存在: " + uploadPath);
  41. }
  42. }
  43. private void checkEnvironmentVariables() {
  44. String[] requiredVars = {"DB_HOST", "DB_PORT", "DB_NAME"};
  45. for (String var : requiredVars) {
  46. String value = env.getProperty(var);
  47. if (value == null) {
  48. System.err.println("⚠️ 缺少环境变量: " + var);
  49. } else {
  50. System.out.println("✅ 环境变量 " + var + " 已设置");
  51. }
  52. }
  53. }
  54. }
java
3. 缓存预热Runner
  1. @Component
  2. @Order(3)
  3. public class CacheWarmupRunner implements CommandLineRunner {
  4. private final CacheManager cacheManager;
  5. private final ProductService productService;
  6. private final UserService userService;
  7. public CacheWarmupRunner(CacheManager cacheManager,
  8. ProductService productService,
  9. UserService userService) {
  10. this.cacheManager = cacheManager;
  11. this.productService = productService;
  12. this.userService = userService;
  13. }
  14. @Override
  15. public void run(String... args) throws Exception {
  16. System.out.println("🔥 开始缓存预热...");
  17. // 预热产品缓存
  18. warmupProductCache();
  19. // 预热用户缓存
  20. warmupUserCache();
  21. // 预热配置缓存
  22. warmupConfigCache();
  23. System.out.println("✅ 缓存预热完成!");
  24. }
  25. private void warmupProductCache() {
  26. try {
  27. List<Product> popularProducts = productService.getPopularProducts();
  28. System.out.println("📦 产品缓存已预热,加载了 " + popularProducts.size() + " 个产品");
  29. } catch (Exception e) {
  30. System.err.println("❌ 产品缓存预热失败: " + e.getMessage());
  31. }
  32. }
  33. private void warmupUserCache() {
  34. try {
  35. List<User> activeUsers = userService.getActiveUsers();
  36. System.out.println("👥 用户缓存已预热,加载了 " + activeUsers.size() + " 个用户");
  37. } catch (Exception e) {
  38. System.err.println("❌ 用户缓存预热失败: " + e.getMessage());
  39. }
  40. }
  41. private void warmupConfigCache() {
  42. try {
  43. // 加载系统配置到缓存
  44. cacheManager.getCache("config").put("system.version", "1.0.0");
  45. cacheManager.getCache("config").put("system.startup.time", new Date());
  46. System.out.println("⚙️ 配置缓存已预热");
  47. } catch (Exception e) {
  48. System.err.println("❌ 配置缓存预热失败: " + e.getMessage());
  49. }
  50. }
  51. }
java
4. 自定义启动横幅Runner
  1. @Component
  2. @Order(0) // 最高优先级,最先执行
  3. public class CustomBannerRunner implements CommandLineRunner {
  4. @Override
  5. public void run(String... args) throws Exception {
  6. printCustomBanner();
  7. printSystemInfo();
  8. }
  9. private void printCustomBanner() {
  10. System.out.println();
  11. System.out.println("╔══════════════════════════════════════════════════════════════╗");
  12. System.out.println("║ 🚀 我的应用程序 🚀 ║");
  13. System.out.println("║ ║");
  14. System.out.println("║ 欢迎使用Spring Boot应用! ║");
  15. System.out.println("╚══════════════════════════════════════════════════════════════╝");
  16. System.out.println();
  17. }
  18. private void printSystemInfo() {
  19. System.out.println("📊 系统信息:");
  20. System.out.println(" Java版本: " + System.getProperty("java.version"));
  21. System.out.println(" JVM版本: " + System.getProperty("java.vm.version"));
  22. System.out.println(" 操作系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version"));
  23. System.out.println(" 可用内存: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + " MB");
  24. System.out.println();
  25. }
  26. }
java
最佳实践
1. 使用@Order控制执行顺序
  1. @Component
  2. @Order(1) // 数字越小,优先级越高
  3. public class FirstRunner implements CommandLineRunner {
  4. // 实现
  5. }

  6. @Component
  7. @Order(2)
  8. public class SecondRunner implements CommandLineRunner {
  9. // 实现
  10. }
java
2. 异常处理
  1. @Component
  2. public class SafeRunner implements CommandLineRunner {
  3. @Override
  4. public void run(String... args) throws Exception {
  5. try {
  6. // 执行启动逻辑
  7. performStartupTasks();
  8. } catch (Exception e) {
  9. // 记录错误但不阻止应用程序启动
  10. System.err.println("启动任务失败,但应用程序将继续启动: " + e.getMessage());
  11. // 可以选择记录到日志文件
  12. }
  13. }
  14. private void performStartupTasks() {
  15. // 启动任务实现
  16. }
  17. }
java
3. 条件执行
  1. @Component
  2. @ConditionalOnProperty(name = "app.feature.data-init", havingValue = "true")
  3. public class ConditionalDataInitRunner implements CommandLineRunner {
  4. @Override
  5. public void run(String... args) throws Exception {
  6. // 只有在启用数据初始化功能时才执行
  7. System.out.println("数据初始化功能已启用,开始初始化...");
  8. }
  9. }
java
4. 异步执行
  1. @Component
  2. public class AsyncRunner implements CommandLineRunner {
  3. @Async
  4. @Override
  5. public void run(String... args) throws Exception {
  6. // 异步执行的启动任务
  7. System.out.println("异步启动任务开始执行...");
  8. Thread.sleep(5000); // 模拟长时间运行的任务
  9. System.out.println("异步启动任务完成!");
  10. }
  11. }
java
总结
Spring Boot Runners是应用程序启动时执行初始化任务的强大工具。它们提供了一种干净、简单的方式来:
初始化数据
执行系统检查
预热缓存
显示自定义启动信息
执行其他启动时任务
选择合适的Runner类型取决于您的具体需求:
使用CommandLineRunner进行简单的参数处理
使用ApplicationRunner进行更复杂的命令行参数解析
记住使用@Order注解来控制多个Runners的执行顺序,并始终包含适当的错误处理。