Spring 6 原始碼編譯和高效閱讀原始碼技巧分享

2022-12-12 12:02:03

一. 前言

Spring Boot 3 RELEASE版本於 2022年11月24日 正式釋出,相信已經有不少同學開始準備新版本的學習了,不過目前還不建議在實際專案中做升級,畢竟還有很多框架和中介軟體沒出適配版本。此次Spring Boot里程碑的升級也要求了最低JDK 17Spring Framework 6 ,其核心框架的 Spring 也在 2022年11月16日 迎來了從 5.3.x6.0.x 重大版本升級,藉著這個機會,寫一篇關於 Spring 6 原始碼編譯和如何高效閱讀 Spring 原始碼的教學。

二. 環境宣告

Spring原始碼編譯官方檔案:https://github.com/spring-projects/spring-framework/wiki/Build-from-Source

根據官方檔案描述, Spring 6 需要 JDK 17

基礎環境 版本 本地路徑
作業系統 Windows 11 -
Spring原始碼 6.0.2 D:\SourceCode\spring-framework
Java環境 JDK 17 D:\Java\jdk-17.0.3.1
編譯工具 Gradle 7.6 D:\softs\gradle-7.6
開發工具 IDEA 2022.2.3 -

三. JDK 安裝

1. 下載JDK17

下載連結: https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.exe

下載後靜默安裝即可,按需修改 JDK 路徑(D:\Java\jdk-17.0.3.1)

2. 設定環境變數(可忽略)

設定環境 JDK 環境變數非必須!考慮到大多數人因為老專案JAVA_HOME設定JDK8的情況,下文是通過設定 Gradle 指定 JDK 版本方式。

新增系統變數 JAVA_HOME = D:\Java\jdk-17.0.3.1

新增Path:%JAVA_HOME%\bin

驗證:java -version

四. Gradle 安裝

1. 下載Gradle

下載地址:https://gradle.org/releases

下載解壓到指定目錄(D:\softs\gradle-7.6)

2. 設定環境變數

新增系統變數:GRADLE_HOME=D:\softs\gradle-7.6

新增至Path路徑(%GRADLE_HOME%\bin)

檢視版本 gradle -v

3. 設定映象倉庫

在gradle安裝位置(D:\softs\gradle-7.6\init.d) 目錄下新建 init.gradle 檔案

參考阿里雲官方gradle設定指南:https://developer.aliyun.com/mvn/guide ,init.gradle 完整內容如下

allprojects {
    repositories {
        maven { url 'file:///D:/data/.m2/repository'} // 本地倉庫地址,如果沒有依次向下尋找
        maven { url "https://maven.aliyun.com/repository/public" }
        mavenLocal()
        mavenCentral()
    }
    buildscript {
        repositories {
            maven { url 'https://maven.aliyun.com/repository/public' }
            mavenLocal()
	    mavenCentral()
        }
    }
}

五. 原始碼編譯

1. 獲取Spring原始碼

不建議zip包方式下載原始碼,具體看官方issue:https://github.com/spring-projects/spring-framework/issues/24467

IDEA 選擇 File → New → Project from Version Control 輸入Spring原始碼倉庫地址:

地址 備註
Github https://github.com/spring-projects/spring-framework.git 速度慢
GitCode https://gitcode.net/mirrors/spring-projects/spring-framework.git 國內映象,速度極快

IDEA原始碼獲取完成之後,因為當前時間最新穩定版tag是v6.0.2版本 ,所以還需要進行分支切換:

git checkout -b v6.0.2
git pull origin v6.0.2

2. 環境設定

  • IDEA設定

    File → Settings → Build,Execution,Deployment → Build Tools → Gradle

  • build.gradle

    找到 repositories 設定節點,新增阿里雲映象倉庫地址

    maven { url "https://maven.aliyun.com/repository/public" } // 阿里雲映象倉庫
    

  • settings.gradle

    找到 repositories 設定節點,新增阿里雲映象倉庫地址

    maven { url "https://maven.aliyun.com/repository/public" } // 阿里雲映象倉庫
    

  • gradle.properties
    專案內 gradle.properties 組態檔新增java路徑

    org.gradle.java.home=D:\Java\jdk-17.0.3.1
    

3. 編譯步驟

在完成上述的原始碼匯入和相關設定之後,就可以進行原始碼編譯了。

參考IDEA匯入說明檔案 import-into-idea.md ,僅需三步:

  1. Precompile spring-oxm with ./gradlew :spring-oxm:compileTestJava

    Windows 環境 CMD 輸入 gradlew :spring-oxm:compileTestJava 先執行 spring-oxm 的預編譯

  2. Import into IntelliJ (File -> New -> Project from Existing Sources -> Navigate to directory -> Select build.gradle)

    File → New → Project from Existing Sources → Select File or Directory to import 選擇 build.gradle 點選 OK 完成編譯

  3. When prompted exclude the spring-aspects module (or after the import via File-> Project Structure -> Modules)

六. 測試案例

在完成上文 Spring 原始碼編譯之後,Congratulations ! 接下來新增一個範例模組來依賴工程中的其它 spring 模組做個簡單的測試。

1. 新增模組

File → Module 新增 spring-sample 範例模組

2. 新增依賴

spring-sample 模組下的 build.gradle 新增 spring-context 依賴,它是包含了 spring-corespring-bean 和 IoC容器等Spring 執行時上下文的依賴。

 api(project(":spring-context"))

3. 測試程式碼

程式碼結構

/**
 * 人介面
 */
public interface IPersonService {

 /**
  * 說
  */
 void speak();

}
/**
 * 中國人
 */
@Service
@Primary
public class ChineseService implements IPersonService {
 @Override
 public void speak() {
  System.out.println("我會說中文");
 }
}
/**
 * 美國人
 */
@Service
public class AmericanService implements IPersonService {
 @Override
 public void speak() {
  System.out.println("I can speak English");
 }
}
/**
 * 啟動測試類
 */
@ComponentScan("com.youlai.spring.sample.**")
public class SpringSampleApplication {

 public static void main(String[] args) {
  AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
    SpringSampleApplication.class
  );

  IPersonService personService = context.getBean(IPersonService.class);
  personService.speak();
 }
}

4. 測試結果

image-20221210232239371

七. 原始碼閱讀

本章節就基於編譯好的 Spring 原始碼環境進行原始碼偵錯,為了方便下面就基於上章節的測試案例來對 getBean 原始碼流程分析,後續會更新出 Spring 原始碼閱讀系列文章。

1. getBean 原始碼

  • 快速定位: 通過 Debug (F7)可以很清晰看到詳細的呼叫棧

image-20221210230131704

getBean時序圖

  • 深入概念原理

    時序圖反映了在getBean()呼叫鏈中 DefaultListableBeanFactory 承擔著核心角色,甚至可以說是 Spring 最核心的一個 BeanFacory 實現 ,也被稱為 Spring 的 「發動機」,所以其重要性是學習 Spring 原始碼的必修課。

    DefaultListableBeanFactory : 可列舉的Bean工廠。

​ 通過類註釋我們可以瞭解到:DefaultListableBeanFactory 是一個成熟的bean工廠;包含了 bean 定義後設資料(beanDefinitionMap),提供了Bean定義的註冊和獲取方法;管理已存在的Bean範例,而不是基於Bean定義去建立新範例。

2. todo

​ 後續更新 Spring 6 原始碼閱讀系列 @有來技術。

八. 問題整理

在編譯過程中,因環境不同每個人可能遇到的問題也都不同,但是總結出來的都是沒按照官方檔案要求或者自己粗心所致,下面就總結編譯中遇到常見的問題,也希望大家在留言區把自己遇到問題記錄下。

1. 問題一

  • 報錯詳情

    D:\SourceCode\spring-framework>gradlew :spring-oxm:compileTestJava
    
    > Task :buildSrc:compileJava FAILED
    D:\SourceCode\spring-framework\buildSrc\src\main\java\org\springframework\build\KotlinConventions.java:44: 錯誤: 找不到符號
                    freeCompilerArgs.addAll(List.of("-Xsuppress-version-warnings", "-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn"));
                                                ^
      符號:   方法 of(java.lang.String,java.lang.String,java.lang.String)
      位置: 介面 java.util.List
    1 個錯誤
    
    FAILURE: Build failed with an exception.
    
  • 解決方案

    gradlew :spring-oxm:compileTestJava info 檢視使用 JDK 的版本是不是17,如果不是請在組態檔 gradle.properties 新增:

    org.gradle.java.home=D:\Java\jdk-17.0.3.1
    

2. todo

​ 歡迎大家留言區補充或提問~

九. 結語

本篇從 Spring 6 編譯依賴的基礎環境搭建(JDK17和Gradle)開始、根據官方檔案編譯原始碼、在工程新增範例模組測試、以及最後通過對getBean的原始碼偵錯,繪製時序圖和類註釋輔助手段來掌握高效閱讀Spring原始碼技巧。還有一點需要提醒,一定要帶著一個明確的目的去看原始碼,不要被動式的為了學習而學習,不然很容易在知識的海洋裡嗆水。最後預祝大家編譯成功,掌握到屬於自己高效閱讀原始碼的方式。

持續更新~

附. 原始碼

Spring 6 編譯原始碼倉庫地址: https://gitee.com/youlaiorg/spring-framework