Spring Boot中的Runners:它们是什么以及如何使用
概述
您是否曾经想过如何在Spring Boot应用程序启动时自动运行一些代码?这就是runners发挥作用的地方!
Spring Boot为您提供了两种简单的方法来在应用程序启动后立即运行逻辑:
CommandLineRunner
ApplicationRunner
让我们以一种非常简单的方式来分解它们 👇
✅ 什么是Runner?
Runner只是Spring Boot中的一个特殊类,它在应用程序启动后运行一些代码,在实际业务逻辑开始之前。
将其视为:
"在应用程序完全启动之前进行这个设置/检查!"
1️⃣ CommandLineRunner – 简单易用
您获得传递给应用程序的参数(String... args)
当您不需要花哨的参数处理时最佳
✨ 示例:
- import org.springframework.boot.CommandLineRunner;
- import org.springframework.stereotype.Component;
- @Component
- public class MyStartupRunner implements CommandLineRunner {
- @Override
- public void run(String... args) throws Exception {
- System.out.println("✅ 应用程序已启动!CommandLineRunner正在运行...");
- }
- }
一旦您的应用程序运行,它将在控制台中打印该消息。
2️⃣ ApplicationRunner – 更智能的参数处理
它为您提供ApplicationArguments,因此您可以轻松检查传递的--options
当您想要以干净的方式解析命令行输入时很棒
✨ 示例:
- import org.springframework.boot.ApplicationArguments;
- import org.springframework.boot.ApplicationRunner;
- import org.springframework.stereotype.Component;
- @Component
- public class MyAppRunner implements ApplicationRunner {
- @Override
- public void run(ApplicationArguments args) throws Exception {
- System.out.println("✅ ApplicationRunner正在运行...");
- System.out.println("👉 选项名称: " + args.getOptionNames());
- }
- }
🎯 何时使用Runners?
以下是一些实际用例:
|
用例
|
示例
|
|
将数据加载到数据库
|
在开发环境中填充测试数据
|
|
启动检查
|
验证文件路径或连接
|
|
缓存设置
|
调用外部API并存储配置
|
|
记录启动信息
|
打印自定义横幅或信息
|
🔢 如果您有多个Runners怎么办?
没问题!您可以使用@Order控制它们运行的顺序:
- @Component
- @Order(1)
- public class FirstRunner implements CommandLineRunner {
- public void run(String... args) {
- System.out.println("🏁 第一个runner已执行");
- }
- }
🧠 TL;DR(快速总结)
|
特性
|
CommandLineRunner
|
ApplicationRunner
|
|
参数类型
|
String[]
|
ApplicationArguments
|
|
简单使用
|
✅
|
✅
|
|
结构化参数解析
|
❌
|
✅
|
|
实际用例
|
设置、日志、加载数据
|
相同,但参数处理更好
|
✅ 最终思考
Runners是一种在启动时运行代码的简洁方法,无需触及控制器或服务。它们简单、强大,经常在现实世界的Spring Boot应用程序中使用。
实际应用示例
1. 数据初始化Runner
- @Component
- @Order(1)
- public class DataInitializationRunner implements CommandLineRunner {
- private final UserRepository userRepository;
- private final ProductRepository productRepository;
- public DataInitializationRunner(UserRepository userRepository,
- ProductRepository productRepository) {
- this.userRepository = userRepository;
- this.productRepository = productRepository;
- }
- @Override
- public void run(String... args) throws Exception {
- System.out.println("🔄 开始初始化数据...");
- // 检查是否需要初始化用户数据
- if (userRepository.count() == 0) {
- initializeUsers();
- }
- // 检查是否需要初始化产品数据
- if (productRepository.count() == 0) {
- initializeProducts();
- }
- System.out.println("✅ 数据初始化完成!");
- }
- private void initializeUsers() {
- List<User> users = Arrays.asList(
- new User("admin", "admin@example.com"),
- new User("user1", "user1@example.com"),
- new User("user2", "user2@example.com")
- );
- userRepository.saveAll(users);
- System.out.println("👥 用户数据已初始化");
- }
- private void initializeProducts() {
- List<Product> products = Arrays.asList(
- new Product("iPhone 15", 999.99),
- new Product("MacBook Pro", 1999.99),
- new Product("AirPods Pro", 249.99)
- );
- productRepository.saveAll(products);
- System.out.println("📱 产品数据已初始化");
- }
- }
2. 系统检查Runner
- @Component
- @Order(2)
- public class SystemCheckRunner implements ApplicationRunner {
- private final Environment env;
- private final DataSource dataSource;
- public SystemCheckRunner(Environment env, DataSource dataSource) {
- this.env = env;
- this.dataSource = dataSource;
- }
- @Override
- public void run(ApplicationArguments args) throws Exception {
- System.out.println("🔍 开始系统检查...");
- // 检查数据库连接
- checkDatabaseConnection();
- // 检查必要的文件路径
- checkRequiredPaths();
- // 检查环境变量
- checkEnvironmentVariables();
- // 检查命令行参数
- if (args.containsOption("debug")) {
- System.out.println("🐛 调试模式已启用");
- }
- System.out.println("✅ 系统检查完成!");
- }
- private void checkDatabaseConnection() {
- try (Connection conn = dataSource.getConnection()) {
- System.out.println("✅ 数据库连接正常");
- } catch (SQLException e) {
- System.err.println("❌ 数据库连接失败: " + e.getMessage());
- throw new RuntimeException("数据库连接检查失败", e);
- }
- }
- private void checkRequiredPaths() {
- String uploadPath = env.getProperty("app.upload.path", "/tmp/uploads");
- File uploadDir = new File(uploadPath);
- if (!uploadDir.exists()) {
- uploadDir.mkdirs();
- System.out.println("📁 创建上传目录: " + uploadPath);
- } else {
- System.out.println("✅ 上传目录存在: " + uploadPath);
- }
- }
- private void checkEnvironmentVariables() {
- String[] requiredVars = {"DB_HOST", "DB_PORT", "DB_NAME"};
- for (String var : requiredVars) {
- String value = env.getProperty(var);
- if (value == null) {
- System.err.println("⚠️ 缺少环境变量: " + var);
- } else {
- System.out.println("✅ 环境变量 " + var + " 已设置");
- }
- }
- }
- }
3. 缓存预热Runner
- @Component
- @Order(3)
- public class CacheWarmupRunner implements CommandLineRunner {
- private final CacheManager cacheManager;
- private final ProductService productService;
- private final UserService userService;
- public CacheWarmupRunner(CacheManager cacheManager,
- ProductService productService,
- UserService userService) {
- this.cacheManager = cacheManager;
- this.productService = productService;
- this.userService = userService;
- }
- @Override
- public void run(String... args) throws Exception {
- System.out.println("🔥 开始缓存预热...");
- // 预热产品缓存
- warmupProductCache();
- // 预热用户缓存
- warmupUserCache();
- // 预热配置缓存
- warmupConfigCache();
- System.out.println("✅ 缓存预热完成!");
- }
- private void warmupProductCache() {
- try {
- List<Product> popularProducts = productService.getPopularProducts();
- System.out.println("📦 产品缓存已预热,加载了 " + popularProducts.size() + " 个产品");
- } catch (Exception e) {
- System.err.println("❌ 产品缓存预热失败: " + e.getMessage());
- }
- }
- private void warmupUserCache() {
- try {
- List<User> activeUsers = userService.getActiveUsers();
- System.out.println("👥 用户缓存已预热,加载了 " + activeUsers.size() + " 个用户");
- } catch (Exception e) {
- System.err.println("❌ 用户缓存预热失败: " + e.getMessage());
- }
- }
- private void warmupConfigCache() {
- try {
- // 加载系统配置到缓存
- cacheManager.getCache("config").put("system.version", "1.0.0");
- cacheManager.getCache("config").put("system.startup.time", new Date());
- System.out.println("⚙️ 配置缓存已预热");
- } catch (Exception e) {
- System.err.println("❌ 配置缓存预热失败: " + e.getMessage());
- }
- }
- }
4. 自定义启动横幅Runner
- @Component
- @Order(0) // 最高优先级,最先执行
- public class CustomBannerRunner implements CommandLineRunner {
- @Override
- public void run(String... args) throws Exception {
- printCustomBanner();
- printSystemInfo();
- }
- private void printCustomBanner() {
- System.out.println();
- System.out.println("╔══════════════════════════════════════════════════════════════╗");
- System.out.println("║ 🚀 我的应用程序 🚀 ║");
- System.out.println("║ ║");
- System.out.println("║ 欢迎使用Spring Boot应用! ║");
- System.out.println("╚══════════════════════════════════════════════════════════════╝");
- System.out.println();
- }
- private void printSystemInfo() {
- System.out.println("📊 系统信息:");
- System.out.println(" Java版本: " + System.getProperty("java.version"));
- System.out.println(" JVM版本: " + System.getProperty("java.vm.version"));
- System.out.println(" 操作系统: " + System.getProperty("os.name") + " " + System.getProperty("os.version"));
- System.out.println(" 可用内存: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + " MB");
- System.out.println();
- }
- }
最佳实践
1. 使用@Order控制执行顺序
- @Component
- @Order(1) // 数字越小,优先级越高
- public class FirstRunner implements CommandLineRunner {
- // 实现
- }
- @Component
- @Order(2)
- public class SecondRunner implements CommandLineRunner {
- // 实现
- }
2. 异常处理
- @Component
- public class SafeRunner implements CommandLineRunner {
- @Override
- public void run(String... args) throws Exception {
- try {
- // 执行启动逻辑
- performStartupTasks();
- } catch (Exception e) {
- // 记录错误但不阻止应用程序启动
- System.err.println("启动任务失败,但应用程序将继续启动: " + e.getMessage());
- // 可以选择记录到日志文件
- }
- }
- private void performStartupTasks() {
- // 启动任务实现
- }
- }
3. 条件执行
- @Component
- @ConditionalOnProperty(name = "app.feature.data-init", havingValue = "true")
- public class ConditionalDataInitRunner implements CommandLineRunner {
- @Override
- public void run(String... args) throws Exception {
- // 只有在启用数据初始化功能时才执行
- System.out.println("数据初始化功能已启用,开始初始化...");
- }
- }
4. 异步执行
- @Component
- public class AsyncRunner implements CommandLineRunner {
- @Async
- @Override
- public void run(String... args) throws Exception {
- // 异步执行的启动任务
- System.out.println("异步启动任务开始执行...");
- Thread.sleep(5000); // 模拟长时间运行的任务
- System.out.println("异步启动任务完成!");
- }
- }
总结
Spring Boot Runners是应用程序启动时执行初始化任务的强大工具。它们提供了一种干净、简单的方式来:
初始化数据
执行系统检查
预热缓存
显示自定义启动信息
执行其他启动时任务
选择合适的Runner类型取决于您的具体需求:
使用CommandLineRunner进行简单的参数处理
使用ApplicationRunner进行更复杂的命令行参数解析
记住使用@Order注解来控制多个Runners的执行顺序,并始终包含适当的错误处理。
