首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring Boot 啟動原始碼深度解析(一):SpringApplication 建構與初始化流程

     編輯:Java 綜合教學

Spring Boot 啟動原始碼深度解析(一):SpringApplication 初始化

Spring Boot 的核心入口是 SpringApplication.run()。本文基於 Spring Boot 1.5.x/2.x 原始碼,詳細剖析 SpringApplication 物件建構與初始化的完整流程。

一、啟動入口與整體流程

@SpringBootApplication
public class MySpringApplication {
    public static void main(String[] args) {
        SpringApplication.run(MySpringApplication.class, args);
    }
}

// 等價於:
SpringApplication app = new SpringApplication(MySpringApplication.class);
app.run(args);

二、建構子:initialize(sources) 核心邏輯

public SpringApplication(Object... sources) {
    initialize(sources);
}

private void initialize(Object[] sources) {
    // 1. 記錄主要配置來源(@SpringBootApplication 標註的類別)
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    
    // 2. 推斷是否為 Web 環境
    this.webEnvironment = deduceWebEnvironment();
    
    // 3. 載入 ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    
    // 4. 載入 ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(
        ApplicationListener.class));
    
    // 5. 推斷主類別
    this.mainApplicationClass = deduceMainApplicationClass();
}

三、五大核心初始化步驟詳細解析

1. 記錄配置來源

private final Set sources = new LinkedHashSet<>();
// 保持插入順序,去重複
// 典型值:[class com.example.MySpringApplication]

2. 推斷 Web 環境

private static final String[] WEB_ENVIRONMENT_CLASSES = {
    "javax.servlet.Servlet",
    "org.springframework.web.context.ConfigurableWebApplicationContext"
};

private boolean deduceWebEnvironment() {
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return false;  // 缺少任一類別即非 Web 環境
        }
    }
    return true;
}

判斷邏輯:Classpath 中同時存在 Servlet API 與 Spring Web Context 類別 → Web 環境(預設建立 AnnotationConfigEmbeddedWebApplicationContext),否則為非 Web 環境(建立 AnnotationConfigApplicationContext)。

3. 載入 ApplicationContextInitializer(4 個預設實作)

private  Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    
    // 1. 從 META-INF/spring.factories 讀取類別名稱
    Set names = new LinkedHashSet<>(
        SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    
    // 2. 實例化每個類別
    List instances = new ArrayList<>(names.size());
    for (String name : names) {
        Class instanceClass = ClassUtils.forName(name, classLoader);
        Constructor constructor = instanceClass.getConstructor(parameterTypes);
        T instance = (T) constructor.newInstance(args);
        instances.add(instance);
    }
    
    // 3. 依 @Order 排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

spring-boot-1.3.0.RELEASE.jar/META-INF/spring.factories 中的預設 4 個 Initializer:

Initializer功能Order
ConfigurationWarningsApplicationContextInitializer檢測配置問題並發出警告Ordered.LOWEST_PRECEDENCE
ContextIdApplicationContextInitializer設定 ApplicationContext IDOrdered.LOWEST_PRECEDENCE
DelegatingApplicationContextInitializer委派給 SpringApplication 的 initializersOrdered.LOWEST_PRECEDENCE
ServerPortInfoApplicationContextInitializer記錄伺服器埠資訊Ordered.LOWEST_PRECEDENCE

4. 載入 ApplicationListener(8 個預設實作)

同樣從 META-INF/spring.factories 載入:

Listener功能
ParentContextCloserApplicationListener關閉父上下文
FileEncodingApplicationListener設定檔案編碼
AnsiOutputApplicationListener控制台 ANSI 色彩輸出
ConfigFileApplicationListener載入 application.properties/yml(核心)
DelegatingApplicationListener事件委派
LiquibaseServiceLocatorApplicationListenerLiquibase 整合
ClasspathLoggingApplicationListener早期日誌系統初始化
LoggingApplicationListener日誌系統完整初始化

5. 推斷主類別

private Class deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement element : stackTrace) {
            if ("main".equals(element.getMethodName())) {
                return Class.forName(element.getClassName());
            }
        }
    } catch (ClassNotFoundException ex) {
        // 忽略
    }
    return null;
}

透過堆疊追蹤找到 main() 方法所在類別,用於 Banner 顯示、日誌配置等。

四、SpringApplication 初始化完成後的狀態

屬性值/狀態用途
sourcesLinkedHashSet<主類別>Bean 定義來源
webEnvironmenttrue/false決定 ApplicationContext 類型
initializersList<4個Initializer>Context 初始化擴充點
listenersList<8個Listener>啟動事件監聽
mainApplicationClassClass<主類別>Banner、日誌、主類別識別

五、關鍵設計模式與原理

  • SPI 機制:SpringFactoriesLoader 掃描所有 JAR 的 META-INF/spring.factories,實現零配置擴充
  • 延遲初始化:Web 環境判斷、主類別推斷均在建構子完成,非啟動關鍵路徑
  • 有序初始化:AnnotationAwareOrderComparator 確保 Initializer/Listener 執行順序
  • 模板方法:run() 定義啟動骨架,子類別可覆寫特定步驟

六、圖解:SpringApplication 建構時序

new SpringApplication(PrimarySource.class)
    │
    ├─▶ sources.add(PrimarySource.class)
    │
    ├─▶ deduceWebEnvironment() → true/false
    │       └─▶ ClassUtils.isPresent(Servlet.class) & ConfigurableWebApplicationContext.class
    │
    ├─▶ getSpringFactoriesInstances(ApplicationContextInitializer.class)
    │       └─▶ SpringFactoriesLoader.loadFactoryNames()
    │               └─▶ 掃描所有 JAR META-INF/spring.factories
    │                       └─▶ 讀取 key=ApplicationContextInitializer 的值
    │                               └─▶ 實例化 4 個 Initializer → 排序 → 存入 initializers
    │
    ├─▶ getSpringFactoriesInstances(ApplicationListener.class)
    │       └─▶ 同理載入 8 個 Listener → 存入 listeners
    │
    └─▶ deduceMainApplicationClass()
            └─▶ new RuntimeException().getStackTrace() → 找 main() 方法類別

理解 SpringApplication 初始化流程,是掌握 Spring Boot 自動配置、啟動擴充點(Initializer/Listener)、環境判斷等核心機制的基礎。下一篇將深入剖析 run() 方法的完整執行流程。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved