Spring Boot 類加載流程分析
Java 類加載基礎(chǔ)
類加載器層次結(jié)構(gòu)
Java 虛擬機(jī)使用雙親委派模型(Parent Delegation Model)來加載類:
Bootstrap ClassLoader (啟動類加載器)
↓
Extension/Platform ClassLoader (擴(kuò)展/平臺類加載器)
↓
Application/System ClassLoader (應(yīng)用/系統(tǒng)類加載器)
雙親委派機(jī)制
雙親委派模型的工作原理:
- 當(dāng)一個(gè)類加載器收到類加載請求時(shí),它首先不會自己去嘗試加載這個(gè)類
- 而是把這個(gè)請求委派給父類加載器去完成
- 每一個(gè)層次的類加載器都是如此
- 只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)加載請求(找不到所需的類)時(shí),子加載器才會嘗試自己去加載
protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
// 1. 檢查是否已經(jīng)加載
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 2. 委派給父類加載器
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父類加載器無法加載
}
if (c == null) {
// 3. 自己嘗試加載
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}Spring Boot 類加載架構(gòu)
為什么需要特殊的類加載器?
Spring Boot 應(yīng)用通常打包為可執(zhí)行的 JAR(Fat JAR 或 Uber JAR),這種 JAR 包含了:
- 應(yīng)用代碼
- 所有依賴的第三方庫
- Spring Boot 自身的類
傳統(tǒng)的 JAR 加載機(jī)制無法正確處理這種嵌套的 JAR 結(jié)構(gòu),因此 Spring Boot 實(shí)現(xiàn)了自定義的類加載器。
Spring Boot 啟動流程
java -jar app.jar
↓
JarLauncher.main()
↓
創(chuàng)建 LaunchedURLClassLoader
↓
設(shè)置 ContextClassLoader
↓
調(diào)用應(yīng)用程序的 Main 方法
核心類
Spring Boot 類加載相關(guān)的核心類位于 org.springframework.boot.loader 包:
| 類名 | 作用 |
|---|---|
JarLauncher | 可執(zhí)行 JAR 的啟動器 |
WarLauncher | 可執(zhí)行 WAR 的啟動器 |
LaunchedURLClassLoader | Spring Boot 自定義的類加載器 |
JarFile | 封裝 JAR 文件訪問 |
JarFileRegister | JAR 文件注冊表 |
Fat JAR 結(jié)構(gòu)
目錄結(jié)構(gòu)
Spring Boot 可執(zhí)行 JAR 的內(nèi)部結(jié)構(gòu):
app.jar ├── BOOT-INF/ │ ├── classes/ # 應(yīng)用類文件 │ │ └── com/example/ │ │ └── Application.class │ └── lib/ # 依賴庫 │ ├── spring-boot-2.7.0.jar │ ├── spring-core-5.3.20.jar │ └── ... ├── META-INF/ │ ├── MANIFEST.MF # 清單文件 │ └── spring.factories # Spring 配置 ├── org/springframework/boot/loader/ # Spring Boot Loader 類 │ ├── JarLauncher.class │ ├── LaunchedURLClassLoader.class │ └── ... └── [其他資源]
MANIFEST.MF 關(guān)鍵配置
Manifest-Version: 1.0 Start-Class: com.example.Application Main-Class: org.springframework.boot.loader.JarLauncher Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Spring-Boot-Version: 2.7.0
關(guān)鍵屬性說明:
Main-Class: 指向 Spring Boot 的啟動器類Start-Class: 實(shí)際的應(yīng)用程序主類Spring-Boot-Classes: 應(yīng)用類所在目錄Spring-Boot-Lib: 依賴庫所在目錄
LaunchedURLClassLoader 詳解
繼承關(guān)系
java.lang.ClassLoader
↓
java.net.URLClassLoader
↓
org.springframework.boot.loader.LaunchedURLClassLoader
核心特性
1. 打破雙親委派
LaunchedURLClassLoader 在某些情況下會打破傳統(tǒng)的雙親委派模型:
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 1. 檢查是否已經(jīng)加載
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
// 2. 對于某些包,優(yōu)先從 BOOT-INF 加載
if (isEligibleForOverride(name)) {
try {
loadedClass = findClass(name);
if (resolve) {
resolveClass(loadedClass);
}
return loadedClass;
} catch (ClassNotFoundException e) {
// 繼續(xù)嘗試父加載器
}
}
// 3. 使用標(biāo)準(zhǔn)的雙親委派
return super.loadClass(name, resolve);
}
return loadedClass;
}2. URL 處理
LaunchedURLClassLoader 能夠處理嵌套 JAR 的 URL:
jar:file:/path/to/app.jar!/BOOT-INF/lib/spring-core-5.3.20.jar!/
這種 URL 格式表示:
- 外層 JAR:
file:/path/to/app.jar - 內(nèi)層 JAR:
BOOT-INF/lib/spring-core-5.3.20.jar
3. 資源加載
@Override
public URL findResource(String name) {
// 優(yōu)先從 BOOT-INF/classes 查找
URL url = findResourceInBootInf(name);
if (url != null) {
return url;
}
// 然后從 BOOT-INF/lib 查找
return findResourceInDependencies(name);
}類加載優(yōu)先級
Spring Boot 類加載器的加載優(yōu)先級:
Bootstrap ClassLoader (JDK 核心類) LaunchedURLClassLoader (BOOT-INF/classes) LaunchedURLClassLoader (BOOT-INF/lib) Extension/Platform ClassLoader Application ClassLoader (其他)
類加載委托模型
Spring Boot 的委托策略
Spring Boot 并非完全打破雙親委派,而是在特定場景下進(jìn)行調(diào)整:
正常情況(遵循雙親委派): Application ClassLoader → LaunchedURLClassLoader → Bootstrap ClassLoader 特殊情況(打破雙親委派): 對于 Spring Boot 應(yīng)用類,LaunchedURLClassLoader 優(yōu)先加載
加載決策流程
收到類加載請求
↓
是否是 JDK 核心類?
↓ 是 → Bootstrap ClassLoader 加載
↓ 否
是否是 Spring Boot Loader 類?
↓ 是 → Bootstrap ClassLoader 加載
↓ 否
是否在 BOOT-INF/classes 中?
↓ 是 → LaunchedURLClassLoader 加載
↓ 否
是否在 BOOT-INF/lib 中?
↓ 是 → LaunchedURLClassLoader 加載
↓ 否
委托給父類加載器
類隔離
Spring Boot 的類加載機(jī)制實(shí)現(xiàn)了類隔離:
- 應(yīng)用類:
BOOT-INF/classes/ - 依賴類:
BOOT-INF/lib/ - 系統(tǒng)類:
rt.jar,jce.jar等
這種隔離確保了:
- 應(yīng)用類不會被系統(tǒng)類覆蓋
- 不同版本的依賴可以共存
- 類加載沖突最小化
常見問題與解決方案
1. ClassNotFoundException
問題現(xiàn)象:
java.lang.ClassNotFoundException: com.example.MyClass
可能原因:
- 類文件不在
BOOT-INF/classes/中 - 依賴 JAR 不在
BOOT-INF/lib/中 - MANIFEST.MF 配置錯(cuò)誤
解決方案:
<!-- Maven 確保正確打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>2. NoClassDefFoundError
問題現(xiàn)象:
java.lang.NoClassDefFoundError: org/springframework/core/ResolvableTypeProvider
可能原因:
- 依賴版本沖突
- 類加載順序問題
- 傳遞依賴未正確包含
解決方案:
<!-- 排除沖突依賴 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>some-lib</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>3. ClassCastException
問題現(xiàn)象:
java.lang.ClassCastException: com.example.MyClass cannot be cast to com.example.MyClass
可能原因:
- 同一個(gè)類被不同的類加載器加載
- 類加載器隔離導(dǎo)致類型不匹配
解決方案:
// 確保使用正確的類加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.MyClass");
4. NoSuchMethodError
問題現(xiàn)象:
java.lang.NoSuchMethodError: org.springframework.util.StringUtils.isEmpty(Ljava/lang/String;)Z
可能原因:
- 依賴版本不匹配
- 方法簽名在不同版本中發(fā)生變化
解決方案:
<!-- 使用 dependencyManagement 統(tǒng)一版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.20</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>5. 資源文件找不到
問題現(xiàn)象:
InputStream is = getClass().getResourceAsStream("/config.properties"); // 返回 null
可能原因:
- 資源文件打包位置不正確
- 使用了錯(cuò)誤的類加載器
解決方案:
// 使用正確的加載方式
InputStream is = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream("config.properties");
// 或者使用 ClassPathResource
Resource resource = new ClassPathResource("config.properties");
InputStream is = resource.getInputStream();最佳實(shí)踐
1. 依賴管理
<!-- 使用 Spring Boot BOM 統(tǒng)一版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>2. 避免類沖突
<!-- 排除不需要的傳遞依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>3. 自定義類加載器
public class CustomClassLoader extends LaunchedURLClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 自定義加載邏輯
if (name.startsWith("com.custom.")) {
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
clazz = findClass(name);
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
return super.loadClass(name, resolve);
}
}4. 資源加載最佳實(shí)踐
@Service
public class ResourceService {
// 推薦:使用 Resource 抽象
@Value("classpath:config.properties")
private Resource configResource;
public void loadConfig() throws IOException {
Properties props = new Properties();
try (InputStream is = configResource.getInputStream()) {
props.load(is);
}
}
// 推薦:使用 ClassPathResource
public Resource loadResource(String path) {
return new ClassPathResource(path);
}
}5. 調(diào)試類加載問題
@Component
public class ClassLoaderDebugger implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
System.out.println("Context ClassLoader: " + classLoader);
System.out.println("Parent ClassLoader: " + classLoader.getParent());
// 打印類路徑
if (classLoader instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) classLoader).getURLs();
System.out.println("Classpath URLs:");
for (URL url : urls) {
System.out.println(" " + url);
}
}
}
}6. JVM 參數(shù)配置
# 啟用類加載詳細(xì)日志 java -verbose:class -jar app.jar # 設(shè)置類加載器調(diào)試 java -Djava.security.debug=classloader -jar app.jar # 指定自定義類加載器 java -Djava.system.class.loader=com.example.CustomClassLoader -jar app.jar
總結(jié)
Spring Boot 的類加載機(jī)制是其可執(zhí)行 JAR 功能的核心:
- LaunchedURLClassLoader 是核心類加載器,能夠處理嵌套 JAR 結(jié)構(gòu)
- Fat JAR 結(jié)構(gòu) 將應(yīng)用類和依賴類組織在
BOOT-INF目錄下 - 類加載策略 在保持雙親委派的同時(shí),針對 Spring Boot 應(yīng)用進(jìn)行了優(yōu)化
- 類隔離 確保了應(yīng)用類和依賴類的正確加載順序
理解 Spring Boot 的類加載機(jī)制有助于:
- 解決類加載相關(guān)的各種問題
- 優(yōu)化應(yīng)用啟動性能
- 實(shí)現(xiàn)更復(fù)雜的應(yīng)用架構(gòu)
- 進(jìn)行有效的故障排查
到此這篇關(guān)于Spring Boot 類加載流程分析的文章就介紹到這了,更多相關(guān)springboot類加載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot項(xiàng)目啟動報(bào)錯(cuò)"找不到或無法加載主類"的解決方法
- SpringBoot項(xiàng)目啟動錯(cuò)誤:找不到或無法加載主類的幾種解決方法
- 解決springboot運(yùn)行出現(xiàn)錯(cuò)誤:找不到或無法加載主類com.xxxx.xxxx.Application問題
- SpringBoot項(xiàng)目啟動錯(cuò)誤:找不到或無法加載主類的三種解決方法
- SpringBoot控制配置類加載順序方式
- springboot的類加載器(org.springframework.boot.loader)過程詳解
- 詳解springBoot啟動時(shí)找不到或無法加載主類解決辦法
相關(guān)文章
SpringBoot集成JWT實(shí)現(xiàn)token驗(yàn)證的流程
Json web token (JWT), 是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標(biāo)準(zhǔn)((RFC 7519).這篇文章主要介紹了SpringBoot集成JWT實(shí)現(xiàn)token驗(yàn)證,需要的朋友可以參考下2020-01-01
java.lang.ArrayIndexOutOfBoundsException數(shù)組越界異常問題解決
這篇文章主要給大家介紹了關(guān)于java.lang.ArrayIndexOutOfBoundsException數(shù)組越界異常問題解決的相關(guān)資料,數(shù)組越界訪問是一個(gè)非常嚴(yán)重的問題,文中通過圖文將解決的辦法介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01
在SpringBoot中使用jwt實(shí)現(xiàn)token身份認(rèn)證的實(shí)例代碼
你還不會在SpringBoot中使用jwt實(shí)現(xiàn)token身份認(rèn)證嗎,本文小編就給大家詳細(xì)的介紹一下在SpringBoot中使用jwt實(shí)現(xiàn)token身份認(rèn)證的實(shí)例代碼,感興趣的同學(xué)可以自己動手試一試2023-09-09
由ArrayList來深入理解Java中的fail-fast機(jī)制
fail-fast俗稱快速失敗,是在多線程進(jìn)行迭代操作時(shí)產(chǎn)生沖突的一種異常拋出機(jī)制,下面我們就由ArrayList來深入理解Java中的fail-fast機(jī)制.2016-05-05
基于SpringBoot框架實(shí)現(xiàn)文件上傳下載分享功能
在當(dāng)今的Web應(yīng)用開發(fā)中,文件上傳與下載功能是極為常見且重要的需求,無論是用戶上傳頭像、分享文檔,還是系統(tǒng)生成報(bào)告供用戶下載,都離不開這一功能模塊,SpringBoot作為一款流行的Java開發(fā)框架,為我們提供了簡潔高效的方式來實(shí)現(xiàn)文件上傳與下載,需要的朋友可以參考下2025-06-06

