欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

CommandLineRunner最佳實踐小結(jié)

 更新時間:2025年08月15日 11:24:51   作者:全棧凱哥  
CommandLineRunner是Spring Boot框架提供的一個功能接口,用于Spring Boot應(yīng)用啟動完成后立即執(zhí)行特定的代碼邏輯,允許開發(fā)者在應(yīng)用程序完全啟動并準(zhǔn)備好接收請求之前執(zhí)行一些初始化任務(wù),下面通過實例代碼講解什么是CommandLineRunner以及CommandLineRunner用法,一起看看吧

1. CommandLineRunner基礎(chǔ)概念和背景

1.1 什么是CommandLineRunner?

CommandLineRunner是Spring Boot框架提供的一個功能接口,用于在Spring Boot應(yīng)用啟動完成后立即執(zhí)行特定的代碼邏輯。它允許開發(fā)者在應(yīng)用程序完全啟動并準(zhǔn)備好接收請求之前執(zhí)行一些初始化任務(wù)。

1.1.1 核心概念

  • 啟動時執(zhí)行:在Spring Boot應(yīng)用程序完全啟動后自動執(zhí)行
  • 單次執(zhí)行:每次應(yīng)用啟動時只執(zhí)行一次
  • 訪問命令行參數(shù):可以獲取應(yīng)用啟動時的命令行參數(shù)
  • 異常處理:如果執(zhí)行過程中出現(xiàn)異常,會阻止應(yīng)用正常啟動

1.1.2 接口定義

@FunctionalInterface
public interface CommandLineRunner {
    /**
     * 應(yīng)用啟動后執(zhí)行的回調(diào)方法
     * @param args 命令行參數(shù)數(shù)組
     * @throws Exception 如果執(zhí)行過程中出現(xiàn)錯誤
     */
    void run(String... args) throws Exception;
}

1.2 為什么需要CommandLineRunner?

在實際開發(fā)中,我們經(jīng)常需要在應(yīng)用啟動后執(zhí)行一些初始化工作:

/**
 * 常見的應(yīng)用啟動初始化需求
 */
public class InitializationNeeds {
    /**
     * 1. 數(shù)據(jù)庫初始化
     * - 創(chuàng)建默認(rèn)管理員賬戶
     * - 初始化基礎(chǔ)數(shù)據(jù)
     * - 執(zhí)行數(shù)據(jù)遷移腳本
     */
    public void databaseInitialization() {
        // 傳統(tǒng)做法的問題:
        // ? 在@PostConstruct中執(zhí)行 - 可能依賴項還未完全初始化
        // ? 在控制器中執(zhí)行 - 需要手動調(diào)用,不夠自動化
        // ? 在main方法中執(zhí)行 - Spring容器可能還未準(zhǔn)備好
        // ? 使用CommandLineRunner的優(yōu)勢:
        // - Spring容器完全啟動完成
        // - 所有Bean都已初始化
        // - 數(shù)據(jù)庫連接池已準(zhǔn)備就緒
    }
    /**
     * 2. 緩存預(yù)熱
     * - 預(yù)加載熱點數(shù)據(jù)到Redis
     * - 初始化本地緩存
     */
    public void cacheWarmup() {
        // 在應(yīng)用啟動時預(yù)加載數(shù)據(jù),提升首次訪問性能
    }
    /**
     * 3. 定時任務(wù)啟動
     * - 啟動后臺清理任務(wù)
     * - 初始化定時數(shù)據(jù)同步
     */
    public void scheduleTasksInitialization() {
        // 啟動各種后臺任務(wù)
    }
    /**
     * 4. 外部服務(wù)連接檢查
     * - 驗證第三方API連接
     * - 檢查消息隊列連接
     */
    public void externalServiceCheck() {
        // 確保外部依賴服務(wù)可用
    }
}

1.3 CommandLineRunner的特點

1.3.1 執(zhí)行時機

/**
 * Spring Boot應(yīng)用啟動流程中CommandLineRunner的位置
 */
public class SpringBootStartupFlow {
    public void startupSequence() {
        // 1. 創(chuàng)建SpringApplication
        // 2. 準(zhǔn)備Environment
        // 3. 創(chuàng)建ApplicationContext
        // 4. 準(zhǔn)備ApplicationContext
        // 5. 刷新ApplicationContext
        //    - 實例化所有單例Bean
        //    - 執(zhí)行@PostConstruct方法
        //    - 發(fā)布ContextRefreshedEvent事件
        // 6. 調(diào)用ApplicationRunner和CommandLineRunner ?? 這里!
        // 7. 發(fā)布ApplicationReadyEvent事件
        // 8. 應(yīng)用啟動完成,開始接收請求
    }
}

1.3.2 與ApplicationRunner的區(qū)別

/**
 * CommandLineRunner vs ApplicationRunner
 */
public class RunnerComparison {
    /**
     * CommandLineRunner接口
     */
    public class MyCommandLineRunner implements CommandLineRunner {
        @Override
        public void run(String... args) throws Exception {
            // 參數(shù):原始字符串?dāng)?shù)組
            // 例如:["--server.port=8080", "--spring.profiles.active=dev"]
            System.out.println("命令行參數(shù):" + Arrays.toString(args));
        }
    }
    /**
     * ApplicationRunner接口
     */
    public class MyApplicationRunner implements ApplicationRunner {
        @Override
        public void run(ApplicationArguments args) throws Exception {
            // 參數(shù):解析后的ApplicationArguments對象
            // 提供更方便的參數(shù)訪問方法
            System.out.println("選項參數(shù):" + args.getOptionNames());
            System.out.println("非選項參數(shù):" + args.getNonOptionArgs());
        }
    }
}

2. 環(huán)境搭建和項目結(jié)構(gòu)

2.1 Maven項目配置

2.1.1 基礎(chǔ)依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>commandlinerunner-demo</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    <name>CommandLineRunner Demo</name>
    <description>CommandLineRunner功能演示項目</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!-- Spring Boot核心依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- Web功能(如果需要) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 數(shù)據(jù)庫相關(guān)(如果需要) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- MySQL驅(qū)動 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- Redis支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- 測試依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Lombok(簡化代碼) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- JSON處理 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2.1.2 Gradle項目配置

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.example'
version = '1.0.0'
sourceCompatibility = '17'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
dependencies {
    // Spring Boot核心
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    // 數(shù)據(jù)庫
    runtimeOnly 'mysql:mysql-connector-java'
    // 工具類
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    // 測試
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
    useJUnitPlatform()
}

2.2 項目結(jié)構(gòu)

2.2.1 推薦的目錄結(jié)構(gòu)

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           ├── CommandLineRunnerDemoApplication.java
│   │           ├── runner/                    # CommandLineRunner實現(xiàn)
│   │           │   ├── DatabaseInitRunner.java
│   │           │   ├── CacheWarmupRunner.java
│   │           │   ├── SystemCheckRunner.java
│   │           │   └── DataMigrationRunner.java
│   │           ├── config/                    # 配置類
│   │           │   ├── DatabaseConfig.java
│   │           │   ├── RedisConfig.java
│   │           │   └── RunnerConfig.java
│   │           ├── service/                   # 業(yè)務(wù)服務(wù)
│   │           │   ├── UserService.java
│   │           │   ├── DataService.java
│   │           │   └── CacheService.java
│   │           ├── entity/                    # 實體類
│   │           │   ├── User.java
│   │           │   ├── Role.java
│   │           │   └── SystemConfig.java
│   │           ├── repository/                # 數(shù)據(jù)訪問層
│   │           │   ├── UserRepository.java
│   │           │   └── SystemConfigRepository.java
│   │           └── util/                      # 工具類
│   │               ├── CommandLineUtils.java
│   │               └── InitializationUtils.java
│   └── resources/
│       ├── application.yml                    # 主配置文件
│       ├── application-dev.yml               # 開發(fā)環(huán)境配置
│       ├── application-prod.yml              # 生產(chǎn)環(huán)境配置
│       ├── data/                             # 初始化數(shù)據(jù)
│       │   ├── init-data.sql
│       │   └── sample-data.json
│       └── static/                           # 靜態(tài)資源
└── test/
    └── java/
        └── com/
            └── example/
                ├── runner/                   # Runner測試
                │   ├── DatabaseInitRunnerTest.java
                │   └── CacheWarmupRunnerTest.java
                └── integration/              # 集成測試
                    └── ApplicationStartupTest.java

2.3 基礎(chǔ)配置文件

2.3.1 application.yml配置

# 應(yīng)用基礎(chǔ)配置
spring:
  application:
    name: commandlinerunner-demo
  # 數(shù)據(jù)源配置
  datasource:
    url: jdbc:mysql://localhost:3306/demo_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
  # JPA配置
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        format_sql: true
  # Redis配置
  data:
    redis:
      host: localhost
      port: 6379
      database: 0
      timeout: 2000ms
      jedis:
        pool:
          max-active: 8
          max-wait: -1ms
          max-idle: 8
          min-idle: 0
# 服務(wù)器配置
server:
  port: 8080
  servlet:
    context-path: /api
# 日志配置
logging:
  level:
    com.example: DEBUG
    org.springframework: INFO
    org.hibernate: INFO
  pattern:
    console: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n'
    file: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n'
  file:
    name: logs/application.log
# 自定義配置
app:
  runner:
    enabled: true
    database-init: true
    cache-warmup: true
    system-check: true
  initialization:
    admin-username: admin
    admin-password: admin123
    admin-email: admin@example.com

2.3.2 環(huán)境特定配置

# application-dev.yml (開發(fā)環(huán)境)
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true
app:
  runner:
    database-init: true
    cache-warmup: false  # 開發(fā)環(huán)境跳過緩存預(yù)熱
    system-check: false  # 開發(fā)環(huán)境跳過系統(tǒng)檢查
logging:
  level:
    com.example: DEBUG
---
# application-prod.yml (生產(chǎn)環(huán)境)
spring:
  datasource:
    url: jdbc:mysql://prod-db:3306/demo_prod?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
app:
  runner:
    database-init: false  # 生產(chǎn)環(huán)境通常不自動初始化
    cache-warmup: true
    system-check: true
logging:
  level:
    com.example: INFO
    root: WARN

3. CommandLineRunner基本用法

3.1 簡單實現(xiàn)方式

3.1.1 實現(xiàn)接口方式

package com.example.runner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
 * 基礎(chǔ)CommandLineRunner實現(xiàn)示例
 */
@Slf4j
@Component
public class BasicCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("=== BasicCommandLineRunner 開始執(zhí)行 ===");
        // 1. 打印啟動信息
        log.info("應(yīng)用程序啟動完成,開始執(zhí)行初始化任務(wù)");
        // 2. 處理命令行參數(shù)
        if (args.length > 0) {
            log.info("接收到命令行參數(shù):");
            for (int i = 0; i < args.length; i++) {
                log.info("  參數(shù)[{}]: {}", i, args[i]);
            }
        } else {
            log.info("沒有接收到命令行參數(shù)");
        }
        // 3. 執(zhí)行簡單的初始化邏輯
        performBasicInitialization();
        log.info("=== BasicCommandLineRunner 執(zhí)行完成 ===");
    }
    private void performBasicInitialization() {
        try {
            // 模擬一些初始化工作
            log.info("正在執(zhí)行基礎(chǔ)初始化...");
            Thread.sleep(1000); // 模擬耗時操作
            log.info("基礎(chǔ)初始化完成");
        } catch (InterruptedException e) {
            log.error("初始化過程被中斷", e);
            Thread.currentThread().interrupt();
        }
    }
}

3.1.2 Lambda表達(dá)式方式

package com.example.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 使用@Bean和Lambda表達(dá)式創(chuàng)建CommandLineRunner
 */
@Slf4j
@Configuration
public class RunnerConfig {
    /**
     * 簡單的Lambda方式
     */
    @Bean
    public CommandLineRunner simpleRunner() {
        return args -> {
            log.info("=== Lambda CommandLineRunner 執(zhí)行 ===");
            log.info("這是通過Lambda表達(dá)式創(chuàng)建的CommandLineRunner");
            // 執(zhí)行簡單任務(wù)
            printWelcomeMessage();
        };
    }
    /**
     * 帶參數(shù)處理的Lambda方式
     */
    @Bean
    public CommandLineRunner parameterProcessorRunner() {
        return args -> {
            log.info("=== 參數(shù)處理 CommandLineRunner ===");
            // 解析和處理命令行參數(shù)
            processCommandLineArguments(args);
        };
    }
    /**
     * 條件執(zhí)行的Runner
     */
    @Bean
    public CommandLineRunner conditionalRunner() {
        return args -> {
            // 根據(jù)參數(shù)決定是否執(zhí)行
            if (shouldExecuteConditionalLogic(args)) {
                log.info("=== 條件 CommandLineRunner 執(zhí)行 ===");
                executeConditionalLogic();
            } else {
                log.info("跳過條件執(zhí)行邏輯");
            }
        };
    }
    private void printWelcomeMessage() {
        log.info("歡迎使用CommandLineRunner演示應(yīng)用!");
        log.info("應(yīng)用已經(jīng)啟動并準(zhǔn)備就緒");
    }
    private void processCommandLineArguments(String[] args) {
        log.info("處理命令行參數(shù),共{}個參數(shù)", args.length);
        for (String arg : args) {
            if (arg.startsWith("--")) {
                // 處理選項參數(shù)
                handleOptionArgument(arg);
            } else {
                // 處理普通參數(shù)
                handleNormalArgument(arg);
            }
        }
    }
    private void handleOptionArgument(String arg) {
        log.info("處理選項參數(shù): {}", arg);
        if (arg.contains("=")) {
            String[] parts = arg.substring(2).split("=", 2);
            String key = parts[0];
            String value = parts.length > 1 ? parts[1] : "";
            log.info("選項: {} = {}", key, value);
        } else {
            log.info("布爾選項: {}", arg.substring(2));
        }
    }
    private void handleNormalArgument(String arg) {
        log.info("處理普通參數(shù): {}", arg);
    }
    private boolean shouldExecuteConditionalLogic(String[] args) {
        // 檢查是否有特定參數(shù)
        for (String arg : args) {
            if ("--skip-conditional".equals(arg)) {
                return false;
            }
        }
        return true;
    }
    private void executeConditionalLogic() {
        log.info("執(zhí)行條件邏輯...");
        // 執(zhí)行一些條件性的初始化工作
    }
}

3.2 依賴注入和服務(wù)調(diào)用

3.2.1 注入Spring服務(wù)

package com.example.runner;
import com.example.service.UserService;
import com.example.service.DataService;
import com.example.service.CacheService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
 * 演示依賴注入的CommandLineRunner
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ServiceAwareRunner implements CommandLineRunner {
    // 通過構(gòu)造函數(shù)注入依賴服務(wù)
    private final UserService userService;
    private final DataService dataService;
    private final CacheService cacheService;
    @Override
    public void run(String... args) throws Exception {
        log.info("=== ServiceAwareRunner 開始執(zhí)行 ===");
        try {
            // 1. 用戶服務(wù)初始化
            initializeUserService();
            // 2. 數(shù)據(jù)服務(wù)初始化
            initializeDataService();
            // 3. 緩存服務(wù)初始化
            initializeCacheService();
            log.info("所有服務(wù)初始化完成");
        } catch (Exception e) {
            log.error("服務(wù)初始化過程中出現(xiàn)錯誤", e);
            throw e; // 重新拋出異常,阻止應(yīng)用啟動
        }
        log.info("=== ServiceAwareRunner 執(zhí)行完成 ===");
    }
    private void initializeUserService() {
        log.info("初始化用戶服務(wù)...");
        // 檢查是否存在管理員用戶
        if (!userService.existsAdminUser()) {
            log.info("創(chuàng)建默認(rèn)管理員用戶");
            userService.createDefaultAdminUser();
        } else {
            log.info("管理員用戶已存在");
        }
        // 獲取用戶統(tǒng)計信息
        long userCount = userService.getUserCount();
        log.info("當(dāng)前系統(tǒng)用戶數(shù)量: {}", userCount);
    }
    private void initializeDataService() {
        log.info("初始化數(shù)據(jù)服務(wù)...");
        // 檢查數(shù)據(jù)庫連接
        if (dataService.isDatabaseConnected()) {
            log.info("數(shù)據(jù)庫連接正常");
            // 執(zhí)行數(shù)據(jù)遷移(如果需要)
            if (dataService.needsMigration()) {
                log.info("執(zhí)行數(shù)據(jù)遷移...");
                dataService.performMigration();
                log.info("數(shù)據(jù)遷移完成");
            }
        } else {
            log.error("數(shù)據(jù)庫連接失敗");
            throw new RuntimeException("無法連接到數(shù)據(jù)庫");
        }
    }
    private void initializeCacheService() {
        log.info("初始化緩存服務(wù)...");
        // 檢查Redis連接
        if (cacheService.isRedisConnected()) {
            log.info("Redis連接正常");
            // 清理過期緩存
            cacheService.clearExpiredCache();
            // 預(yù)熱重要緩存
            cacheService.preloadImportantData();
        } else {
            log.warn("Redis連接失敗,緩存功能將不可用");
            // 注意:這里只是警告,不阻止應(yīng)用啟動
        }
    }
}

3.2.2 使用@Value注入配置

package com.example.runner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
 * 演示配置注入的CommandLineRunner
 */
@Slf4j
@Component
public class ConfigAwareRunner implements CommandLineRunner {
    // 注入應(yīng)用配置
    @Value("${spring.application.name}")
    private String applicationName;
    @Value("${server.port:8080}")
    private int serverPort;
    @Value("${app.runner.enabled:true}")
    private boolean runnerEnabled;
    @Value("${app.initialization.admin-username:admin}")
    private String adminUsername;
    @Value("${app.initialization.admin-password:}")
    private String adminPassword;
    @Value("${app.initialization.admin-email:admin@example.com}")
    private String adminEmail;
    // 注入環(huán)境變量
    @Value("${JAVA_HOME:#{null}}")
    private String javaHome;
    @Override
    public void run(String... args) throws Exception {
        if (!runnerEnabled) {
            log.info("ConfigAwareRunner已禁用,跳過執(zhí)行");
            return;
        }
        log.info("=== ConfigAwareRunner 開始執(zhí)行 ===");
        // 打印應(yīng)用配置信息
        printApplicationInfo();
        // 打印初始化配置
        printInitializationConfig();
        // 打印環(huán)境信息
        printEnvironmentInfo();
        log.info("=== ConfigAwareRunner 執(zhí)行完成 ===");
    }
    private void printApplicationInfo() {
        log.info("應(yīng)用信息:");
        log.info("  應(yīng)用名稱: {}", applicationName);
        log.info("  服務(wù)端口: {}", serverPort);
        log.info("  Runner啟用狀態(tài): {}", runnerEnabled);
    }
    private void printInitializationConfig() {
        log.info("初始化配置:");
        log.info("  管理員用戶名: {}", adminUsername);
        log.info("  管理員密碼: {}", maskPassword(adminPassword));
        log.info("  管理員郵箱: {}", adminEmail);
    }
    private void printEnvironmentInfo() {
        log.info("環(huán)境信息:");
        log.info("  Java Home: {}", javaHome != null ? javaHome : "未設(shè)置");
        log.info("  工作目錄: {}", System.getProperty("user.dir"));
        log.info("  Java版本: {}", System.getProperty("java.version"));
        log.info("  操作系統(tǒng): {} {}", 
            System.getProperty("os.name"), 
            System.getProperty("os.version"));
    }
    private String maskPassword(String password) {
        if (password == null || password.isEmpty()) {
            return "未設(shè)置";
        }
        return "*".repeat(password.length());
    }
}

3.3 多個Runner的執(zhí)行順序

3.3.1 使用@Order注解控制順序

package com.example.runner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * 第一個執(zhí)行的Runner - 系統(tǒng)檢查
 */
@Slf4j
@Component
@Order(1)
public class SystemCheckRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("=== [ORDER 1] SystemCheckRunner 開始執(zhí)行 ===");
        // 執(zhí)行系統(tǒng)檢查
        performSystemCheck();
        log.info("=== [ORDER 1] SystemCheckRunner 執(zhí)行完成 ===");
    }
    private void performSystemCheck() {
        log.info("執(zhí)行系統(tǒng)健康檢查...");
        // 檢查磁盤空間
        checkDiskSpace();
        // 檢查內(nèi)存使用
        checkMemoryUsage();
        // 檢查網(wǎng)絡(luò)連接
        checkNetworkConnectivity();
    }
    private void checkDiskSpace() {
        long freeSpace = new java.io.File("/").getFreeSpace();
        long totalSpace = new java.io.File("/").getTotalSpace();
        double usagePercent = ((double) (totalSpace - freeSpace) / totalSpace) * 100;
        log.info("磁盤使用率: {:.2f}%", usagePercent);
        if (usagePercent > 90) {
            log.warn("磁盤空間不足,使用率超過90%");
        }
    }
    private void checkMemoryUsage() {
        Runtime runtime = Runtime.getRuntime();
        long maxMemory = runtime.maxMemory();
        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        long usedMemory = totalMemory - freeMemory;
        log.info("內(nèi)存使用情況:");
        log.info("  最大內(nèi)存: {} MB", maxMemory / 1024 / 1024);
        log.info("  已分配內(nèi)存: {} MB", totalMemory / 1024 / 1024);
        log.info("  已使用內(nèi)存: {} MB", usedMemory / 1024 / 1024);
        log.info("  空閑內(nèi)存: {} MB", freeMemory / 1024 / 1024);
    }
    private void checkNetworkConnectivity() {
        // 簡單的網(wǎng)絡(luò)連接檢查
        try {
            java.net.InetAddress.getByName("www.google.com").isReachable(5000);
            log.info("網(wǎng)絡(luò)連接正常");
        } catch (Exception e) {
            log.warn("網(wǎng)絡(luò)連接檢查失敗: {}", e.getMessage());
        }
    }
}
/**
 * 第二個執(zhí)行的Runner - 數(shù)據(jù)庫初始化
 */
@Slf4j
@Component
@Order(2)
public class DatabaseInitRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("=== [ORDER 2] DatabaseInitRunner 開始執(zhí)行 ===");
        // 數(shù)據(jù)庫初始化邏輯
        initializeDatabase();
        log.info("=== [ORDER 2] DatabaseInitRunner 執(zhí)行完成 ===");
    }
    private void initializeDatabase() {
        log.info("初始化數(shù)據(jù)庫...");
        // 創(chuàng)建基礎(chǔ)數(shù)據(jù)表(如果不存在)
        createBaseTables();
        // 插入初始數(shù)據(jù)
        insertInitialData();
        // 創(chuàng)建索引
        createIndexes();
    }
    private void createBaseTables() {
        log.info("檢查并創(chuàng)建基礎(chǔ)數(shù)據(jù)表...");
        // 實際的表創(chuàng)建邏輯
    }
    private void insertInitialData() {
        log.info("插入初始數(shù)據(jù)...");
        // 實際的數(shù)據(jù)插入邏輯
    }
    private void createIndexes() {
        log.info("創(chuàng)建數(shù)據(jù)庫索引...");
        // 實際的索引創(chuàng)建邏輯
    }
}
/**
 * 第三個執(zhí)行的Runner - 緩存預(yù)熱
 */
@Slf4j
@Component
@Order(3)
public class CacheWarmupRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("=== [ORDER 3] CacheWarmupRunner 開始執(zhí)行 ===");
        // 緩存預(yù)熱邏輯
        warmupCache();
        log.info("=== [ORDER 3] CacheWarmupRunner 執(zhí)行完成 ===");
    }
    private void warmupCache() {
        log.info("開始緩存預(yù)熱...");
        // 預(yù)熱用戶數(shù)據(jù)緩存
        warmupUserCache();
        // 預(yù)熱配置數(shù)據(jù)緩存
        warmupConfigCache();
        // 預(yù)熱統(tǒng)計數(shù)據(jù)緩存
        warmupStatsCache();
    }
    private void warmupUserCache() {
        log.info("預(yù)熱用戶數(shù)據(jù)緩存...");
        // 實際的用戶緩存預(yù)熱邏輯
    }
    private void warmupConfigCache() {
        log.info("預(yù)熱配置數(shù)據(jù)緩存...");
        // 實際的配置緩存預(yù)熱邏輯
    }
    private void warmupStatsCache() {
        log.info("預(yù)熱統(tǒng)計數(shù)據(jù)緩存...");
        // 實際的統(tǒng)計緩存預(yù)熱邏輯
    }
}

4. 實際應(yīng)用場景詳解

4.1 數(shù)據(jù)庫初始化場景

4.1.1 基礎(chǔ)數(shù)據(jù)初始化

package com.example.runner;
import com.example.entity.User;
import com.example.entity.Role;
import com.example.entity.SystemConfig;
import com.example.repository.UserRepository;
import com.example.repository.RoleRepository;
import com.example.repository.SystemConfigRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
 * 數(shù)據(jù)庫基礎(chǔ)數(shù)據(jù)初始化Runner
 */
@Slf4j
@Component
@Order(10) // 確保在系統(tǒng)檢查之后執(zhí)行
@RequiredArgsConstructor
public class DatabaseInitializationRunner implements CommandLineRunner {
    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    private final SystemConfigRepository systemConfigRepository;
    private final PasswordEncoder passwordEncoder;
    @Value("${app.initialization.admin-username:admin}")
    private String adminUsername;
    @Value("${app.initialization.admin-password:admin123}")
    private String adminPassword;
    @Value("${app.initialization.admin-email:admin@example.com}")
    private String adminEmail;
    @Value("${app.runner.database-init:true}")
    private boolean enableDatabaseInit;
    @Override
    public void run(String... args) throws Exception {
        if (!enableDatabaseInit) {
            log.info("數(shù)據(jù)庫初始化已禁用,跳過執(zhí)行");
            return;
        }
        log.info("=== 數(shù)據(jù)庫初始化開始 ===");
        try {
            // 1. 初始化角色數(shù)據(jù)
            initializeRoles();
            // 2. 初始化管理員用戶
            initializeAdminUser();
            // 3. 初始化系統(tǒng)配置
            initializeSystemConfigs();
            // 4. 執(zhí)行數(shù)據(jù)驗證
            validateInitializedData();
            log.info("數(shù)據(jù)庫初始化完成");
        } catch (Exception e) {
            log.error("數(shù)據(jù)庫初始化失敗", e);
            throw new RuntimeException("數(shù)據(jù)庫初始化失敗", e);
        }
        log.info("=== 數(shù)據(jù)庫初始化結(jié)束 ===");
    }
    @Transactional
    private void initializeRoles() {
        log.info("初始化角色數(shù)據(jù)...");
        // 定義基礎(chǔ)角色
        String[][] baseRoles = {
            {"ADMIN", "系統(tǒng)管理員", "擁有系統(tǒng)所有權(quán)限"},
            {"USER", "普通用戶", "基礎(chǔ)用戶權(quán)限"},
            {"MODERATOR", "版主", "內(nèi)容管理權(quán)限"},
            {"VIEWER", "訪客", "只讀權(quán)限"}
        };
        for (String[] roleData : baseRoles) {
            String roleName = roleData[0];
            String displayName = roleData[1];
            String description = roleData[2];
            if (!roleRepository.existsByName(roleName)) {
                Role role = new Role();
                role.setName(roleName);
                role.setDisplayName(displayName);
                role.setDescription(description);
                role.setCreatedAt(java.time.LocalDateTime.now());
                roleRepository.save(role);
                log.info("創(chuàng)建角色: {} - {}", roleName, displayName);
            } else {
                log.debug("角色已存在: {}", roleName);
            }
        }
        log.info("角色數(shù)據(jù)初始化完成");
    }
    @Transactional
    private void initializeAdminUser() {
        log.info("初始化管理員用戶...");
        // 檢查管理員用戶是否存在
        if (!userRepository.existsByUsername(adminUsername)) {
            // 獲取管理員角色
            Role adminRole = roleRepository.findByName("ADMIN")
                .orElseThrow(() -> new RuntimeException("管理員角色不存在"));
            // 創(chuàng)建管理員用戶
            User adminUser = new User();
            adminUser.setUsername(adminUsername);
            adminUser.setEmail(adminEmail);
            adminUser.setPassword(passwordEncoder.encode(adminPassword));
            adminUser.setEnabled(true);
            adminUser.setAccountNonExpired(true);
            adminUser.setAccountNonLocked(true);
            adminUser.setCredentialsNonExpired(true);
            adminUser.setCreatedAt(java.time.LocalDateTime.now());
            adminUser.getRoles().add(adminRole);
            userRepository.save(adminUser);
            log.info("創(chuàng)建管理員用戶: {} ({})", adminUsername, adminEmail);
            // 安全起見,不在日志中顯示密碼
            log.info("管理員用戶創(chuàng)建完成,請及時修改默認(rèn)密碼");
        } else {
            log.info("管理員用戶已存在: {}", adminUsername);
        }
    }
    @Transactional
    private void initializeSystemConfigs() {
        log.info("初始化系統(tǒng)配置...");
        // 定義系統(tǒng)配置項
        String[][] configs = {
            {"system.name", "系統(tǒng)名稱", "CommandLineRunner演示系統(tǒng)"},
            {"system.version", "系統(tǒng)版本", "1.0.0"},
            {"system.maintenance", "維護(hù)模式", "false"},
            {"user.registration.enabled", "用戶注冊開關(guān)", "true"},
            {"user.email.verification.required", "郵箱驗證要求", "true"},
            {"cache.expiry.user", "用戶緩存過期時間(秒)", "3600"},
            {"cache.expiry.config", "配置緩存過期時間(秒)", "1800"},
            {"file.upload.max-size", "文件上傳最大大小(MB)", "10"},
            {"session.timeout", "會話超時時間(分鐘)", "30"}
        };
        for (String[] configData : configs) {
            String key = configData[0];
            String description = configData[1];
            String defaultValue = configData[2];
            if (!systemConfigRepository.existsByConfigKey(key)) {
                SystemConfig config = new SystemConfig();
                config.setConfigKey(key);
                config.setConfigValue(defaultValue);
                config.setDescription(description);
                config.setCreatedAt(java.time.LocalDateTime.now());
                config.setUpdatedAt(java.time.LocalDateTime.now());
                systemConfigRepository.save(config);
                log.debug("創(chuàng)建系統(tǒng)配置: {} = {}", key, defaultValue);
            }
        }
        log.info("系統(tǒng)配置初始化完成");
    }
    private void validateInitializedData() {
        log.info("驗證初始化數(shù)據(jù)...");
        // 驗證角色數(shù)據(jù)
        long roleCount = roleRepository.count();
        log.info("系統(tǒng)角色數(shù)量: {}", roleCount);
        // 驗證用戶數(shù)據(jù)
        long userCount = userRepository.count();
        log.info("系統(tǒng)用戶數(shù)量: {}", userCount);
        // 驗證管理員用戶
        boolean adminExists = userRepository.existsByUsername(adminUsername);
        log.info("管理員用戶存在: {}", adminExists);
        // 驗證系統(tǒng)配置
        long configCount = systemConfigRepository.count();
        log.info("系統(tǒng)配置項數(shù)量: {}", configCount);
        // 檢查關(guān)鍵配置
        validateEssentialConfigs();
        log.info("數(shù)據(jù)驗證完成");
    }
    private void validateEssentialConfigs() {
        String[] essentialKeys = {
            "system.name",
            "system.version", 
            "user.registration.enabled"
        };
        for (String key : essentialKeys) {
            boolean exists = systemConfigRepository.existsByConfigKey(key);
            if (!exists) {
                log.error("關(guān)鍵配置項缺失: {}", key);
                throw new RuntimeException("關(guān)鍵配置項缺失: " + key);
            }
        }
        log.debug("關(guān)鍵配置項驗證通過");
    }
}

4.1.2 數(shù)據(jù)遷移場景

package com.example.runner;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
import javax.sql.DataSource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
/**
 * 數(shù)據(jù)庫遷移Runner
 */
@Slf4j
@Component
@Order(5) // 在數(shù)據(jù)庫初始化之前執(zhí)行
@RequiredArgsConstructor
public class DataMigrationRunner implements CommandLineRunner {
    private final DataSource dataSource;
    private final JdbcTemplate jdbcTemplate;
    @Override
    public void run(String... args) throws Exception {
        log.info("=== 數(shù)據(jù)庫遷移開始 ===");
        try {
            // 1. 檢查數(shù)據(jù)庫版本
            String currentVersion = getCurrentDatabaseVersion();
            log.info("當(dāng)前數(shù)據(jù)庫版本: {}", currentVersion);
            // 2. 執(zhí)行遷移腳本
            executemigrations(currentVersion);
            // 3. 更新版本信息
            updateDatabaseVersion();
            log.info("數(shù)據(jù)庫遷移完成");
        } catch (Exception e) {
            log.error("數(shù)據(jù)庫遷移失敗", e);
            throw e;
        }
        log.info("=== 數(shù)據(jù)庫遷移結(jié)束 ===");
    }
    private String getCurrentDatabaseVersion() {
        try {
            // 檢查版本表是否存在
            if (!tableExists("schema_version")) {
                log.info("版本表不存在,創(chuàng)建版本表");
                createVersionTable();
                return "0.0.0";
            }
            // 查詢當(dāng)前版本
            String version = jdbcTemplate.queryForObject(
                "SELECT version FROM schema_version ORDER BY applied_at DESC LIMIT 1",
                String.class
            );
            return version != null ? version : "0.0.0";
        } catch (Exception e) {
            log.warn("獲取數(shù)據(jù)庫版本失敗,假設(shè)為初始版本", e);
            return "0.0.0";
        }
    }
    private boolean tableExists(String tableName) {
        try (Connection connection = dataSource.getConnection()) {
            DatabaseMetaData metaData = connection.getMetaData();
            ResultSet tables = metaData.getTables(null, null, tableName.toUpperCase(), null);
            return tables.next();
        } catch (Exception e) {
            log.error("檢查表存在性失敗: {}", tableName, e);
            return false;
        }
    }
    private void createVersionTable() {
        String sql = """
            CREATE TABLE schema_version (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                version VARCHAR(20) NOT NULL,
                description VARCHAR(255),
                script_name VARCHAR(100),
                applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_version (version),
                INDEX idx_applied_at (applied_at)
            )
        """;
        jdbcTemplate.execute(sql);
        log.info("版本表創(chuàng)建完成");
    }
    private void executemigrations(String currentVersion) {
        // 定義遷移腳本
        Migration[] migrations = {
            new Migration("1.0.0", "初始數(shù)據(jù)庫結(jié)構(gòu)", "V1_0_0__initial_schema.sql"),
            new Migration("1.1.0", "添加用戶擴展信息表", "V1_1_0__add_user_profile.sql"),
            new Migration("1.2.0", "添加日志記錄表", "V1_2_0__add_audit_log.sql"),
            new Migration("1.3.0", "優(yōu)化索引結(jié)構(gòu)", "V1_3_0__optimize_indexes.sql")
        };
        for (Migration migration : migrations) {
            if (shouldExecuteMigration(currentVersion, migration.getVersion())) {
                executeMigration(migration);
            }
        }
    }
    private boolean shouldExecuteMigration(String currentVersion, String migrationVersion) {
        // 簡單的版本比較邏輯
        return compareVersions(migrationVersion, currentVersion) > 0;
    }
    private int compareVersions(String version1, String version2) {
        String[] v1Parts = version1.split("\\.");
        String[] v2Parts = version2.split("\\.");
        int maxLength = Math.max(v1Parts.length, v2Parts.length);
        for (int i = 0; i < maxLength; i++) {
            int v1Part = i < v1Parts.length ? Integer.parseInt(v1Parts[i]) : 0;
            int v2Part = i < v2Parts.length ? Integer.parseInt(v2Parts[i]) : 0;
            if (v1Part != v2Part) {
                return Integer.compare(v1Part, v2Part);
            }
        }
        return 0;
    }
    private void executeMigration(Migration migration) {
        log.info("執(zhí)行遷移: {} - {}", migration.getVersion(), migration.getDescription());
        try {
            // 讀取遷移腳本
            String script = loadMigrationScript(migration.getScriptName());
            // 執(zhí)行腳本
            String[] statements = script.split(";");
            for (String statement : statements) {
                statement = statement.trim();
                if (!statement.isEmpty()) {
                    jdbcTemplate.execute(statement);
                }
            }
            // 記錄遷移歷史
            recordMigration(migration);
            log.info("遷移完成: {}", migration.getVersion());
        } catch (Exception e) {
            log.error("遷移失敗: {}", migration.getVersion(), e);
            throw new RuntimeException("遷移失敗: " + migration.getVersion(), e);
        }
    }
    private String loadMigrationScript(String scriptName) throws IOException {
        ClassPathResource resource = new ClassPathResource("db/migration/" + scriptName);
        if (!resource.exists()) {
            throw new RuntimeException("遷移腳本不存在: " + scriptName);
        }
        byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
        return new String(bytes, StandardCharsets.UTF_8);
    }
    private void recordMigration(Migration migration) {
        jdbcTemplate.update(
            "INSERT INTO schema_version (version, description, script_name) VALUES (?, ?, ?)",
            migration.getVersion(),
            migration.getDescription(),
            migration.getScriptName()
        );
    }
    private void updateDatabaseVersion() {
        // 獲取最新版本
        String latestVersion = jdbcTemplate.queryForObject(
            "SELECT version FROM schema_version ORDER BY applied_at DESC LIMIT 1",
            String.class
        );
        log.info("數(shù)據(jù)庫版本已更新至: {}", latestVersion);
    }
    /**
     * 遷移信息類
     */
    private static class Migration {
        private final String version;
        private final String description;
        private final String scriptName;
        public Migration(String version, String description, String scriptName) {
            this.version = version;
            this.description = description;
            this.scriptName = scriptName;
        }
        public String getVersion() { return version; }
        public String getDescription() { return description; }
        public String getScriptName() { return scriptName; }
    }
}

4.2 緩存預(yù)熱場景

4.2.1 Redis緩存預(yù)熱

package com.example.runner;
import com.example.service.CacheService;
import com.example.service.UserService;
import com.example.service.ConfigService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
 * Redis緩存預(yù)熱Runner
 */
@Slf4j
@Component
@Order(20) // 在數(shù)據(jù)庫初始化后執(zhí)行
@RequiredArgsConstructor
public class CacheWarmupRunner implements CommandLineRunner {
    private final RedisTemplate<String, Object> redisTemplate;
    private final UserService userService;
    private final ConfigService configService;
    private final CacheService cacheService;
    private final ObjectMapper objectMapper;
    // 用于異步預(yù)熱的線程池
    private final Executor warmupExecutor = Executors.newFixedThreadPool(5);
    @Override
    public void run(String... args) throws Exception {
        log.info("=== 緩存預(yù)熱開始 ===");
        try {
            // 1. 檢查Redis連接
            if (!checkRedisConnection()) {
                log.warn("Redis連接失敗,跳過緩存預(yù)熱");
                return;
            }
            // 2. 清理過期緩存
            cleanupExpiredCache();
            // 3. 并行預(yù)熱各種緩存
            CompletableFuture<Void> userCacheWarmup = warmupUserCache();
            CompletableFuture<Void> configCacheWarmup = warmupConfigCache();
            CompletableFuture<Void> staticDataWarmup = warmupStaticData();
            // 4. 等待所有預(yù)熱任務(wù)完成
            CompletableFuture.allOf(userCacheWarmup, configCacheWarmup, staticDataWarmup)
                .get(); // 等待完成
            // 5. 驗證緩存預(yù)熱結(jié)果
            validateWarmupResults();
            log.info("緩存預(yù)熱完成");
        } catch (Exception e) {
            log.error("緩存預(yù)熱失敗", e);
            // 緩存預(yù)熱失敗不應(yīng)該阻止應(yīng)用啟動
            log.warn("緩存預(yù)熱失敗,應(yīng)用將在沒有緩存的情況下啟動");
        }
        log.info("=== 緩存預(yù)熱結(jié)束 ===");
    }
    private boolean checkRedisConnection() {
        try {
            redisTemplate.getConnectionFactory().getConnection().ping();
            log.info("Redis連接正常");
            return true;
        } catch (Exception e) {
            log.error("Redis連接檢查失敗", e);
            return false;
        }
    }
    private void cleanupExpiredCache() {
        log.info("清理過期緩存...");
        try {
            // 獲取所有緩存鍵
            var keys = redisTemplate.keys("cache:*");
            if (keys != null && !keys.isEmpty()) {
                log.info("發(fā)現(xiàn){}個緩存鍵", keys.size());
                // 檢查并刪除過期鍵
                int expiredCount = 0;
                for (String key : keys) {
                    Long expire = redisTemplate.getExpire(key);
                    if (expire != null && expire == -2) { // -2表示鍵不存在或已過期
                        redisTemplate.delete(key);
                        expiredCount++;
                    }
                }
                log.info("清理了{(lán)}個過期緩存", expiredCount);
            }
        } catch (Exception e) {
            log.warn("清理過期緩存失敗", e);
        }
    }
    private CompletableFuture<Void> warmupUserCache() {
        return CompletableFuture.runAsync(() -> {
            log.info("開始預(yù)熱用戶緩存...");
            try {
                // 1. 預(yù)熱活躍用戶數(shù)據(jù)
                List<Long> activeUserIds = userService.getActiveUserIds();
                log.info("預(yù)熱{}個活躍用戶緩存", activeUserIds.size());
                for (Long userId : activeUserIds) {
                    try {
                        var user = userService.getUserById(userId);
                        if (user != null) {
                            String cacheKey = "cache:user:" + userId;
                            redisTemplate.opsForValue().set(
                                cacheKey, 
                                user, 
                                Duration.ofHours(1)
                            );
                        }
                    } catch (Exception e) {
                        log.warn("預(yù)熱用戶緩存失敗: userId={}", userId, e);
                    }
                }
                // 2. 預(yù)熱用戶統(tǒng)計數(shù)據(jù)
                warmupUserStatistics();
                log.info("用戶緩存預(yù)熱完成");
            } catch (Exception e) {
                log.error("用戶緩存預(yù)熱失敗", e);
            }
        }, warmupExecutor);
    }
    private void warmupUserStatistics() {
        try {
            // 預(yù)熱用戶統(tǒng)計信息
            Map<String, Object> userStats = userService.getUserStatistics();
            redisTemplate.opsForValue().set(
                "cache:user:statistics", 
                userStats, 
                Duration.ofMinutes(30)
            );
            // 預(yù)熱在線用戶數(shù)量
            Long onlineUserCount = userService.getOnlineUserCount();
            redisTemplate.opsForValue().set(
                "cache:user:online-count", 
                onlineUserCount, 
                Duration.ofMinutes(5)
            );
            log.debug("用戶統(tǒng)計緩存預(yù)熱完成");
        } catch (Exception e) {
            log.warn("用戶統(tǒng)計緩存預(yù)熱失敗", e);
        }
    }
    private CompletableFuture<Void> warmupConfigCache() {
        return CompletableFuture.runAsync(() -> {
            log.info("開始預(yù)熱配置緩存...");
            try {
                // 1. 預(yù)熱系統(tǒng)配置
                Map<String, String> systemConfigs = configService.getAllSystemConfigs();
                redisTemplate.opsForValue().set(
                    "cache:config:system", 
                    systemConfigs, 
                    Duration.ofHours(2)
                );
                // 2. 預(yù)熱應(yīng)用配置
                Map<String, Object> appConfigs = configService.getApplicationConfigs();
                redisTemplate.opsForValue().set(
                    "cache:config:application", 
                    appConfigs, 
                    Duration.ofHours(1)
                );
                // 3. 預(yù)熱特性開關(guān)配置
                Map<String, Boolean> featureFlags = configService.getFeatureFlags();
                redisTemplate.opsForValue().set(
                    "cache:config:features", 
                    featureFlags, 
                    Duration.ofMinutes(30)
                );
                log.info("配置緩存預(yù)熱完成");
            } catch (Exception e) {
                log.error("配置緩存預(yù)熱失敗", e);
            }
        }, warmupExecutor);
    }
    private CompletableFuture<Void> warmupStaticData() {
        return CompletableFuture.runAsync(() -> {
            log.info("開始預(yù)熱靜態(tài)數(shù)據(jù)緩存...");
            try {
                // 1. 預(yù)熱地區(qū)數(shù)據(jù)
                warmupRegionData();
                // 2. 預(yù)熱字典數(shù)據(jù)
                warmupDictionaryData();
                // 3. 預(yù)熱菜單數(shù)據(jù)
                warmupMenuData();
                log.info("靜態(tài)數(shù)據(jù)緩存預(yù)熱完成");
            } catch (Exception e) {
                log.error("靜態(tài)數(shù)據(jù)緩存預(yù)熱失敗", e);
            }
        }, warmupExecutor);
    }
    private void warmupRegionData() {
        try {
            // 預(yù)熱省市區(qū)數(shù)據(jù)
            var regions = configService.getAllRegions();
            redisTemplate.opsForValue().set(
                "cache:static:regions", 
                regions, 
                Duration.ofDays(1) // 地區(qū)數(shù)據(jù)變化較少,緩存1天
            );
            log.debug("地區(qū)數(shù)據(jù)緩存預(yù)熱完成");
        } catch (Exception e) {
            log.warn("地區(qū)數(shù)據(jù)緩存預(yù)熱失敗", e);
        }
    }
    private void warmupDictionaryData() {
        try {
            // 預(yù)熱數(shù)據(jù)字典
            var dictionaries = configService.getAllDictionaries();
            for (Map.Entry<String, Object> entry : dictionaries.entrySet()) {
                String cacheKey = "cache:dict:" + entry.getKey();
                redisTemplate.opsForValue().set(
                    cacheKey, 
                    entry.getValue(), 
                    Duration.ofHours(4)
                );
            }
            log.debug("字典數(shù)據(jù)緩存預(yù)熱完成,預(yù)熱{}項", dictionaries.size());
        } catch (Exception e) {
            log.warn("字典數(shù)據(jù)緩存預(yù)熱失敗", e);
        }
    }
    private void warmupMenuData() {
        try {
            // 預(yù)熱菜單數(shù)據(jù)
            var menus = configService.getSystemMenus();
            redisTemplate.opsForValue().set(
                "cache:static:menus", 
                menus, 
                Duration.ofHours(2)
            );
            log.debug("菜單數(shù)據(jù)緩存預(yù)熱完成");
        } catch (Exception e) {
            log.warn("菜單數(shù)據(jù)緩存預(yù)熱失敗", e);
        }
    }
    private void validateWarmupResults() {
        log.info("驗證緩存預(yù)熱結(jié)果...");
        int successCount = 0;
        int totalCount = 0;
        // 檢查關(guān)鍵緩存是否存在
        String[] keysToCheck = {
            "cache:config:system",
            "cache:config:application",
            "cache:user:statistics",
            "cache:static:regions"
        };
        for (String key : keysToCheck) {
            totalCount++;
            if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
                successCount++;
                log.debug("緩存鍵存在: {}", key);
            } else {
                log.warn("緩存鍵不存在: {}", key);
            }
        }
        double successRate = (double) successCount / totalCount * 100;
        log.info("緩存預(yù)熱成功率: {:.1f}% ({}/{})", successRate, successCount, totalCount);
        if (successRate < 50) {
            log.warn("緩存預(yù)熱成功率過低,可能影響應(yīng)用性能");
        }
    }
}

4.3 外部服務(wù)檢查場景

4.3.1 第三方服務(wù)連接檢查

package com.example.runner;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.ResourceAccessException;
import javax.sql.DataSource;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.sql.Connection;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
 * 外部服務(wù)連接檢查Runner
 */
@Slf4j
@Component
@Order(1) // 最先執(zhí)行,確保基礎(chǔ)服務(wù)可用
@RequiredArgsConstructor
public class ExternalServiceCheckRunner implements CommandLineRunner {
    private final DataSource dataSource;
    private final RestTemplate restTemplate;
    @Value("${app.external-services.payment-api.url:}")
    private String paymentApiUrl;
    @Value("${app.external-services.email-service.url:}")
    private String emailServiceUrl;
    @Value("${app.external-services.redis.host:localhost}")
    private String redisHost;
    @Value("${app.external-services.redis.port:6379}")
    private int redisPort;
    @Value("${app.runner.system-check:true}")
    private boolean enableSystemCheck;
    @Override
    public void run(String... args) throws Exception {
        if (!enableSystemCheck) {
            log.info("系統(tǒng)檢查已禁用,跳過執(zhí)行");
            return;
        }
        log.info("=== 外部服務(wù)連接檢查開始 ===");
        List<ServiceCheckResult> results = new ArrayList<>();
        try {
            // 1. 數(shù)據(jù)庫連接檢查
            results.add(checkDatabaseConnection());
            // 2. Redis連接檢查
            results.add(checkRedisConnection());
            // 3. 第三方API檢查
            results.addAll(checkExternalAPIs());
            // 4. 分析檢查結(jié)果
            analyzeCheckResults(results);
        } catch (Exception e) {
            log.error("服務(wù)檢查過程中出現(xiàn)異常", e);
            throw e;
        }
        log.info("=== 外部服務(wù)連接檢查結(jié)束 ===");
    }
    private ServiceCheckResult checkDatabaseConnection() {
        log.info("檢查數(shù)據(jù)庫連接...");
        ServiceCheckResult result = new ServiceCheckResult("數(shù)據(jù)庫", "Database");
        LocalDateTime startTime = LocalDateTime.now();
        try {
            // 嘗試獲取數(shù)據(jù)庫連接
            try (Connection connection = dataSource.getConnection()) {
                if (connection.isValid(5)) {
                    Duration responseTime = Duration.between(startTime, LocalDateTime.now());
                    result.setSuccess(true);
                    result.setResponseTime(responseTime);
                    result.setMessage("數(shù)據(jù)庫連接正常");
                    // 獲取數(shù)據(jù)庫信息
                    String dbUrl = connection.getMetaData().getURL();
                    String dbProduct = connection.getMetaData().getDatabaseProductName();
                    String dbVersion = connection.getMetaData().getDatabaseProductVersion();
                    result.setDetails(String.format("URL: %s, 產(chǎn)品: %s, 版本: %s", 
                        dbUrl, dbProduct, dbVersion));
                    log.info("數(shù)據(jù)庫連接成功 - {} ({}ms)", dbProduct, responseTime.toMillis());
                } else {
                    result.setSuccess(false);
                    result.setMessage("數(shù)據(jù)庫連接無效");
                    log.error("數(shù)據(jù)庫連接無效");
                }
            }
        } catch (Exception e) {
            result.setSuccess(false);
            result.setMessage("數(shù)據(jù)庫連接失敗: " + e.getMessage());
            result.setError(e);
            log.error("數(shù)據(jù)庫連接失敗", e);
        }
        return result;
    }
    private ServiceCheckResult checkRedisConnection() {
        log.info("檢查Redis連接...");
        ServiceCheckResult result = new ServiceCheckResult("Redis緩存", "Redis");
        LocalDateTime startTime = LocalDateTime.now();
        try {
            // 使用Socket測試Redis連接
            try (Socket socket = new Socket()) {
                socket.connect(new InetSocketAddress(redisHost, redisPort), 5000);
                Duration responseTime = Duration.between(startTime, LocalDateTime.now());
                result.setSuccess(true);
                result.setResponseTime(responseTime);
                result.setMessage("Redis連接正常");
                result.setDetails(String.format("主機: %s, 端口: %d", redisHost, redisPort));
                log.info("Redis連接成功 - {}:{} ({}ms)", redisHost, redisPort, responseTime.toMillis());
            }
        } catch (Exception e) {
            result.setSuccess(false);
            result.setMessage("Redis連接失敗: " + e.getMessage());
            result.setError(e);
            log.error("Redis連接失敗", e);
        }
        return result;
    }
    private List<ServiceCheckResult> checkExternalAPIs() {
        log.info("檢查外部API服務(wù)...");
        List<ServiceCheckResult> results = new ArrayList<>();
        // 并行檢查多個API服務(wù)
        CompletableFuture<ServiceCheckResult> paymentCheck = checkApiService(
            "支付服務(wù)", "PaymentAPI", paymentApiUrl + "/health"
        );
        CompletableFuture<ServiceCheckResult> emailCheck = checkApiService(
            "郵件服務(wù)", "EmailService", emailServiceUrl + "/status"
        );
        try {
            // 等待所有檢查完成,設(shè)置超時時間
            results.add(paymentCheck.get(10, TimeUnit.SECONDS));
            results.add(emailCheck.get(10, TimeUnit.SECONDS));
        } catch (Exception e) {
            log.error("API服務(wù)檢查超時或失敗", e);
        }
        return results;
    }
    private CompletableFuture<ServiceCheckResult> checkApiService(String serviceName, String serviceType, String url) {
        return CompletableFuture.supplyAsync(() -> {
            if (url == null || url.isEmpty()) {
                ServiceCheckResult result = new ServiceCheckResult(serviceName, serviceType);
                result.setSuccess(false);
                result.setMessage("服務(wù)URL未配置");
                log.warn("{} URL未配置,跳過檢查", serviceName);
                return result;
            }
            log.debug("檢查{}服務(wù): {}", serviceName, url);
            ServiceCheckResult result = new ServiceCheckResult(serviceName, serviceType);
            LocalDateTime startTime = LocalDateTime.now();
            try {
                ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
                Duration responseTime = Duration.between(startTime, LocalDateTime.now());
                if (response.getStatusCode() == HttpStatus.OK) {
                    result.setSuccess(true);
                    result.setResponseTime(responseTime);
                    result.setMessage("服務(wù)響應(yīng)正常");
                    result.setDetails(String.format("狀態(tài)碼: %s, 響應(yīng)時間: %dms", 
                        response.getStatusCode(), responseTime.toMillis()));
                    log.info("{}服務(wù)連接成功 ({}ms)", serviceName, responseTime.toMillis());
                } else {
                    result.setSuccess(false);
                    result.setMessage("服務(wù)響應(yīng)異常: " + response.getStatusCode());
                    log.warn("{}服務(wù)響應(yīng)異常: {}", serviceName, response.getStatusCode());
                }
            } catch (ResourceAccessException e) {
                result.setSuccess(false);
                result.setMessage("服務(wù)連接超時或拒絕: " + e.getMessage());
                result.setError(e);
                log.error("{}服務(wù)連接失敗", serviceName, e);
            } catch (Exception e) {
                result.setSuccess(false);
                result.setMessage("服務(wù)檢查失敗: " + e.getMessage());
                result.setError(e);
                log.error("{}服務(wù)檢查失敗", serviceName, e);
            }
            return result;
        });
    }
    private void analyzeCheckResults(List<ServiceCheckResult> results) {
        log.info("=== 服務(wù)檢查結(jié)果分析 ===");
        int totalServices = results.size();
        int successfulServices = 0;
        int criticalFailures = 0;
        for (ServiceCheckResult result : results) {
            if (result.isSuccess()) {
                successfulServices++;
                log.info("? {} - {} ({}ms)", 
                    result.getServiceName(), 
                    result.getMessage(),
                    result.getResponseTime() != null ? result.getResponseTime().toMillis() : 0);
            } else {
                log.error("? {} - {}", result.getServiceName(), result.getMessage());
                // 檢查是否為關(guān)鍵服務(wù)
                if (isCriticalService(result.getServiceType())) {
                    criticalFailures++;
                }
            }
        }
        double successRate = (double) successfulServices / totalServices * 100;
        log.info("服務(wù)檢查完成: 成功率 {:.1f}% ({}/{})", successRate, successfulServices, totalServices);
        // 處理關(guān)鍵服務(wù)失敗
        if (criticalFailures > 0) {
            String errorMessage = String.format("關(guān)鍵服務(wù)檢查失敗,共%d個服務(wù)不可用", criticalFailures);
            log.error(errorMessage);
            // 根據(jù)配置決定是否阻止應(yīng)用啟動
            boolean failOnCriticalError = true; // 可以通過配置控制
            if (failOnCriticalError) {
                throw new RuntimeException(errorMessage);
            }
        } else if (successRate < 50) {
            log.warn("服務(wù)可用性較低,應(yīng)用可能無法正常工作");
        }
    }
    private boolean isCriticalService(String serviceType) {
        // 定義關(guān)鍵服務(wù)類型
        return "Database".equals(serviceType) || "Redis".equals(serviceType);
    }
    /**
     * 服務(wù)檢查結(jié)果類
     */
    private static class ServiceCheckResult {
        private final String serviceName;
        private final String serviceType;
        private boolean success;
        private Duration responseTime;
        private String message;
        private String details;
        private Exception error;
        public ServiceCheckResult(String serviceName, String serviceType) {
            this.serviceName = serviceName;
            this.serviceType = serviceType;
        }
        // Getters and Setters
        public String getServiceName() { return serviceName; }
        public String getServiceType() { return serviceType; }
        public boolean isSuccess() { return success; }
        public void setSuccess(boolean success) { this.success = success; }
        public Duration getResponseTime() { return responseTime; }
        public void setResponseTime(Duration responseTime) { this.responseTime = responseTime; }
        public String getMessage() { return message; }
        public void setMessage(String message) { this.message = message; }
        public String getDetails() { return details; }
        public void setDetails(String details) { this.details = details; }
        public Exception getError() { return error; }
        public void setError(Exception error) { this.error = error; }
    }
}

5. 測試CommandLineRunner

5.1 單元測試

5.1.1 基礎(chǔ)測試方法

package com.example.runner;
import com.example.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
/**
 * CommandLineRunner單元測試示例
 */
@ExtendWith(MockitoExtension.class)
@TestPropertySource(properties = {
    "app.runner.database-init=true",
    "app.initialization.admin-username=testadmin"
})
class DatabaseInitializationRunnerTest {
    @Mock
    private UserService userService;
    private DatabaseInitializationRunner runner;
    @BeforeEach
    void setUp() {
        runner = new DatabaseInitializationRunner(userService);
    }
    @Test
    void testRunWithEnabledInitialization() throws Exception {
        // 模擬管理員用戶不存在
        when(userService.existsAdminUser()).thenReturn(false);
        // 執(zhí)行Runner
        runner.run("--spring.profiles.active=test");
        // 驗證是否創(chuàng)建了管理員用戶
        verify(userService, times(1)).createDefaultAdminUser();
    }
    @Test
    void testRunWithExistingAdmin() throws Exception {
        // 模擬管理員用戶已存在
        when(userService.existsAdminUser()).thenReturn(true);
        // 執(zhí)行Runner
        runner.run();
        // 驗證沒有創(chuàng)建新的管理員用戶
        verify(userService, never()).createDefaultAdminUser();
    }
    @Test
    void testRunWithServiceException() {
        // 模擬服務(wù)異常
        when(userService.existsAdminUser()).thenThrow(new RuntimeException("數(shù)據(jù)庫連接失敗"));
        // 驗證異常被正確拋出
        assertThrows(RuntimeException.class, () -> runner.run());
    }
}

5.2 集成測試

5.2.1 完整應(yīng)用啟動測試

package com.example.integration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
/**
 * 應(yīng)用啟動集成測試
 */
@SpringBootTest
@ActiveProfiles("test")
@TestPropertySource(properties = {
    "app.runner.enabled=true",
    "app.runner.database-init=false", // 測試時禁用數(shù)據(jù)庫初始化
    "app.runner.cache-warmup=false"   // 測試時禁用緩存預(yù)熱
})
class ApplicationStartupIntegrationTest {
    @Test
    void contextLoads() {
        // 測試應(yīng)用能夠正常啟動
        // Spring Boot會自動執(zhí)行所有CommandLineRunner
    }
}

6. 最佳實踐

6.1 設(shè)計原則

6.1.1 單一職責(zé)原則

// ? 好的做法:每個Runner負(fù)責(zé)單一職責(zé)
@Component
@Order(1)
public class DatabaseInitRunner implements CommandLineRunner {
    // 只負(fù)責(zé)數(shù)據(jù)庫初始化
}
@Component  
@Order(2)
public class CacheWarmupRunner implements CommandLineRunner {
    // 只負(fù)責(zé)緩存預(yù)熱
}
// ? 壞的做法:一個Runner做太多事情
@Component
public class MegaRunner implements CommandLineRunner {
    public void run(String... args) {
        initDatabase();     // 數(shù)據(jù)庫初始化
        warmupCache();      // 緩存預(yù)熱  
        checkServices();    // 服務(wù)檢查
        sendNotifications(); // 發(fā)送通知
        // ... 更多職責(zé)
    }
}

6.1.2 異常處理策略

@Component
public class RobustRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        try {
            // 關(guān)鍵操作:失敗應(yīng)該阻止應(yīng)用啟動
            performCriticalInitialization();
        } catch (Exception e) {
            log.error("關(guān)鍵初始化失敗", e);
            throw e; // 重新拋出異常
        }
        try {
            // 非關(guān)鍵操作:失敗不應(yīng)該阻止應(yīng)用啟動
            performOptionalInitialization();
        } catch (Exception e) {
            log.warn("可選初始化失敗,繼續(xù)啟動", e);
            // 不重新拋出異常
        }
    }
}

6.2 性能優(yōu)化

6.2.1 異步執(zhí)行

@Component
public class AsyncInitRunner implements CommandLineRunner {
    @Async("taskExecutor")
    public CompletableFuture<Void> asyncInitialization() {
        return CompletableFuture.runAsync(() -> {
            // 異步執(zhí)行的初始化邏輯
            log.info("異步初始化開始");
            performHeavyInitialization();
            log.info("異步初始化完成");
        });
    }
    @Override
    public void run(String... args) throws Exception {
        // 啟動異步任務(wù)但不等待完成
        asyncInitialization();
        log.info("主初始化完成,異步任務(wù)在后臺繼續(xù)執(zhí)行");
    }
}

6.3 配置管理

@Component
@ConditionalOnProperty(
    name = "app.runner.data-init.enabled", 
    havingValue = "true", 
    matchIfMissing = true
)
public class ConditionalRunner implements CommandLineRunner {
    @Value("${app.runner.data-init.batch-size:1000}")
    private int batchSize;
    @Override
    public void run(String... args) throws Exception {
        // 根據(jù)配置執(zhí)行初始化
    }
}

7. 常見問題和解決方案

7.1 常見錯誤

7.1.1 依賴注入問題

// ? 問題:依賴項可能未完全初始化
@Component
public class EarlyRunner implements CommandLineRunner {
    @Autowired
    private SomeService someService; // 可能還未準(zhǔn)備好
    @Override
    public void run(String... args) throws Exception {
        someService.doSomething(); // 可能失敗
    }
}
// ? 解決方案:使用構(gòu)造函數(shù)注入和檢查
@Component
@RequiredArgsConstructor
public class SafeRunner implements CommandLineRunner {
    private final SomeService someService;
    @Override
    public void run(String... args) throws Exception {
        if (someService == null) {
            log.error("SomeService未注入");
            return;
        }
        someService.doSomething();
    }
}

7.1.2 執(zhí)行時間過長

// ? 解決方案:添加超時控制和進(jìn)度監(jiān)控
@Component
public class TimeoutAwareRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        long startTime = System.currentTimeMillis();
        long timeoutMs = 60000; // 60秒超時
        try {
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                performLongRunningTask();
            });
            future.get(timeoutMs, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            log.error("初始化超時,耗時超過{}ms", timeoutMs);
            throw new RuntimeException("初始化超時", e);
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            log.info("初始化耗時: {}ms", duration);
        }
    }
}

7.2 調(diào)試技巧

7.2.1 添加詳細(xì)日志

@Component
public class DebuggableRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("=== Runner開始執(zhí)行 ===");
        log.info("命令行參數(shù): {}", Arrays.toString(args));
        log.info("當(dāng)前時間: {}", LocalDateTime.now());
        log.info("JVM內(nèi)存信息: 最大{}MB, 已用{}MB", 
            Runtime.getRuntime().maxMemory() / 1024 / 1024,
            (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024);
        try {
            performInitialization();
            log.info("初始化成功完成");
        } catch (Exception e) {
            log.error("初始化失敗: {}", e.getMessage(), e);
            throw e;
        } finally {
            log.info("=== Runner執(zhí)行結(jié)束 ===");
        }
    }
}

8. 總結(jié)和建議

8.1 CommandLineRunner適用場景

? 適合使用CommandLineRunner的場景:
- 應(yīng)用啟動后的一次性初始化任務(wù)
- 數(shù)據(jù)庫基礎(chǔ)數(shù)據(jù)初始化
- 緩存預(yù)熱
- 系統(tǒng)健康檢查
- 配置驗證
- 外部服務(wù)連接測試
- 數(shù)據(jù)遷移任務(wù)
? 不適合使用CommandLineRunner的場景:
- 需要在Bean初始化過程中執(zhí)行的邏輯(應(yīng)使用@PostConstruct)
- 定期執(zhí)行的任務(wù)(應(yīng)使用@Scheduled)
- 請求處理邏輯(應(yīng)在Controller中處理)
- 復(fù)雜的業(yè)務(wù)流程(應(yīng)在Service中實現(xiàn))

8.2 與其他初始化方式對比

初始化方式對比:
方式                    | 執(zhí)行時機              | 適用場景
-----------------------|---------------------|------------------------
@PostConstruct         | Bean初始化后         | 單個Bean的初始化
CommandLineRunner      | 應(yīng)用完全啟動后        | 全局初始化任務(wù)
ApplicationRunner      | 應(yīng)用完全啟動后        | 需要解析命令行參數(shù)
InitializingBean       | Bean屬性設(shè)置后        | Bean級別的初始化驗證
ApplicationListener    | 特定事件發(fā)生時        | 事件驅(qū)動的初始化
@EventListener         | 特定事件發(fā)生時        | 注解方式的事件監(jiān)聽

8.3 最佳實踐總結(jié)

  1. 職責(zé)分離:每個Runner只負(fù)責(zé)一個特定的初始化任務(wù)
  2. 順序控制:使用@Order注解明確執(zhí)行順序
  3. 異常處理:區(qū)分關(guān)鍵和非關(guān)鍵操作的異常處理策略
  4. 配置驅(qū)動:通過配置控制Runner的啟用和行為
  5. 日志記錄:添加詳細(xì)的執(zhí)行日志便于調(diào)試
  6. 性能考慮:對于耗時操作考慮異步執(zhí)行
  7. 測試覆蓋:編寫單元測試和集成測試
  8. 監(jiān)控告警:對關(guān)鍵初始化任務(wù)添加監(jiān)控

8.4 技術(shù)選型建議

// 新項目推薦使用ApplicationRunner(參數(shù)處理更友好)
@Component
public class ModernRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 更方便的參數(shù)處理
        if (args.containsOption("debug")) {
            enableDebugMode();
        }
    }
}
// 但CommandLineRunner仍然適用于簡單場景
@Component  
public class SimpleRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        // 簡單直接的初始化邏輯
        performBasicInitialization();
    }
}

8.5 未來發(fā)展趨勢

隨著Spring Boot的發(fā)展,CommandLineRunner的使用趨勢:

  1. 云原生支持:更好地支持容器化部署場景
  2. 健康檢查集成:與Spring Boot Actuator更緊密集成
  3. 監(jiān)控指標(biāo):提供更豐富的執(zhí)行指標(biāo)
  4. 異步優(yōu)化:更好的異步執(zhí)行支持
  5. 配置管理:更靈活的條件執(zhí)行機制

到此這篇關(guān)于CommandLineRunner最佳實踐小結(jié)的文章就介紹到這了,更多相關(guān)CommandLineRunner用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java并發(fā)統(tǒng)計變量值偏差原因及解決方案

    Java并發(fā)統(tǒng)計變量值偏差原因及解決方案

    這篇文章主要介紹了Java并發(fā)統(tǒng)計變量值偏差原因及解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • Java面試為何阿里強制要求不在foreach里執(zhí)行刪除操作

    Java面試為何阿里強制要求不在foreach里執(zhí)行刪除操作

    那天,小二去阿里面試,面試官老王一上來就甩給了他一道面試題:為什么阿里的 Java 開發(fā)手冊里會強制不要在 foreach 里進(jìn)行元素的刪除操作
    2021-11-11
  • Java中Object和內(nèi)部類舉例詳解

    Java中Object和內(nèi)部類舉例詳解

    這篇文章主要介紹了Java中Object和內(nèi)部類的相關(guān)資料,Object類是Java中所有類的父類,提供了toString、hashCode和equals等方法,內(nèi)部類分為實例內(nèi)部類、靜態(tài)內(nèi)部類、匿名內(nèi)部類和局部內(nèi)部類,需要的朋友可以參考下
    2025-05-05
  • Spring Boot 訪問安全之認(rèn)證和鑒權(quán)詳解

    Spring Boot 訪問安全之認(rèn)證和鑒權(quán)詳解

    這篇文章主要介紹了Spring Boot 訪問安全之認(rèn)證和鑒權(quán),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java 實戰(zhàn)項目之在線點餐系統(tǒng)的實現(xiàn)流程

    Java 實戰(zhàn)項目之在線點餐系統(tǒng)的實現(xiàn)流程

    讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實現(xiàn)在線點餐系統(tǒng),大家可以在過程中查缺補漏,提升水平
    2021-11-11
  • Java對象創(chuàng)建內(nèi)存案例解析

    Java對象創(chuàng)建內(nèi)存案例解析

    這篇文章主要介紹了Java對象創(chuàng)建內(nèi)存案例解析,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • 一篇文章徹底理解SpringIOC、DI

    一篇文章徹底理解SpringIOC、DI

    這篇文章主要給大家介紹了關(guān)于對SpringIOC、DI的理解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • 詳解SpringMVC @RequestBody接收J(rèn)son對象字符串

    詳解SpringMVC @RequestBody接收J(rèn)son對象字符串

    這篇文章主要介紹了詳解SpringMVC @RequestBody接收J(rèn)son對象字符串,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01
  • 解決@RequestBody接收json對象報錯415的問題

    解決@RequestBody接收json對象報錯415的問題

    這篇文章主要介紹了解決@RequestBody接收json對象報錯415的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Java?8函數(shù)式接口之Consumer用法示例詳解

    Java?8函數(shù)式接口之Consumer用法示例詳解

    這篇文章主要為大家介紹了Java?8函數(shù)式接口之Consumer用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07

最新評論