技術面:SpringBoot(springboot的類加載和傳統的雙親委派有什么區別、如何按順序實例化Bean)
前言
在SpringBoot中,類加載機制與Java的傳統雙親委派類加載機制是有一定區別。主要體現在自定義類加載器與fat jar(可執行jar)的加載方式上。
Java的傳統雙親委派模型
Java傳統類加載機制,遵循雙親委派模型,核心規則:類加載請求優先由父類加載器處理,只有父加載器無法加載時才由子加載器嘗試。
1、JDK 1.8及更早版本采用如下層級結構:

2、從 JDK 9 引入模塊系統開始,是這樣的層級結構

這樣設計的主要目的是為了,避免重復加載核心類(如java.lang.String),確保安全性(防止用戶篡改核心類)。
SpringBoot的類加載器改造
改造原因
SpringBoot通過自定義類加載器LaunchedURLClassLoader打破了傳統雙親委派的嚴格層級,主要解決fat jar中嵌套jar的加載問題。
在SpringBoot中,使用打包構建工具時,無論是Maven還是Gradle,在lib/目錄中的第三方依賴是以JAR形式打入項目主JAR內的,默認會生成一個包含所有依賴項的fat jar。
目錄結構示例如下:
mySpringBootApp.jar
├── BOOT-INF
│ ├── classes(用戶代碼)
│ └── lib(依賴的第三方jar)
└── org.springframework.boot.loader
傳統的Java類加載機制,Application ClassLoader只能從外部classpath加載類,無法直接加載JAR包內嵌的其他JAR(fat jar),因此SpringBoot加入了自定義的類加載器。
主要做了哪些改造
SpringBoot使用LaunchedURLClassLoader(繼承自URLClassLoader)替代了ApplicationClassLoader,通過運行時動態生成jar路徑的URL來加載嵌套jar。
LaunchedURLClassLoader會先加載BOOT-INF/classes目錄下的應用類(優先于JDK類)。- 再加載
BOOT-INF/lib/目錄下的依賴JAR,LaunchedURLClassLoader會解析BOOT-INF/lib/下的每個jar,將其URL添加到類路徑中。 - 最后再交給父類加載器(即
ApplicationClassLoader)。
總結一下:為了加載嵌套在主JAR內部的fat jar,SpringBoot在類加載流程上做了改造,增加了LaunchedURLClassLoader類加載器,并且會先嘗試加載自身的類和依賴JAR,找不到要加載的類時,才交給父類加載器,從而對傳統的雙親委派模型進行了改造。
注意,LaunchedURLClassLoader 僅對 BOOT-INF/classes 和 BOOT-INF/lib 下的類采用“子加載器優先”策略,核心類庫仍嚴格遵循雙親委派,因此不會破壞 JDK 的安全模型。
擴展知識
在使用SpringBoot進行開發項目時,SpringBoot官方推薦我們使用熱部署的方式是使用 spring-boot-devtools 模塊。
其實SpringBoot的熱部署并不是真正意義上的“熱替換”,而是通過 雙類加載器機制 實現的“快速重啟”。
SpringBoot的“熱部署”主要實現原理如下:
- 雙 ClassLoader 架構:
Base ClassLoader:加載第三方 jar 包(不會頻繁變動)Restart ClassLoader:加載開發者自己寫的類(會頻繁變動)
- 文件變化監聽機制
DevTools啟動一個后臺線程,監聽classpath下.class文件的變化- 一旦檢測到變化,丟棄舊的
Restart ClassLoader - 重新創建一個新的
Restart ClassLoader,加載更新后的類 - 然后通過反射重新調用
main()方法,實現應用重啟
由于不需要重新加載第三方類(Base ClassLoader 不變),也不需要重新初始化整個 Spring 容器,重啟過程只涉及開發者代碼部分,節省大量時間。
雖然叫“熱部署”,但本質上是“部分重啟”,不是真正的 JVM 熱替換(如 JRebel 那樣)
SpringBoot如何指定在其他Bean之前實例化指定的Bean
Bean 實例化/初始化順序其實就是指“哪個 Bean 先被 new、哪個 @PostConstruct 先跑”。
目前有6種方式可以實現按照一定順序進行實例化Bean。
1、構造器依賴(最穩,無侵入)
Spring 保證一個 Bean 實例化之前,它依賴的 Bean 必須已實例化。
直接讓 BeanA 的構造器里需要 BeanB,或者 BeanA 里有一個非延遲的 BeanB 字段 + @Autowired。
如下代碼
@Configuration
public class Config {
@Bean
public B b() { return new B(); }
@Bean
public A a(B b) { // Spring 保證 b() 先跑
return new A(b);
}
}
使用這種方式,理解起來簡單,并且可靠性高,與具體的應用框架無關,但是也有一定的短板,就是按指定順序實例化的Bean,必須存在真實的依賴關系。
2、@DependsOn(顯式聲明,無真正依賴也適用)
雖然兩個 Bean 之間沒有構造器/字段依賴,但你仍想讓 BeanB 先實例化于BeanA。
這個時候就可以使用@DependsOn注解了,但是需要注意一點:@DependsOn 只能保證先實例化,不能保證先銷毀(銷毀順序用 DependentBean.destroyMethod 或 DisposableBeanAdapter)。
@DependsOn 僅控制 初始化順序;銷毀時 Spring 會按依賴關系的反向順序執行,因此若 B 依賴 A,則 B 先銷毀,A 后銷毀。
如下代碼:
@Configuration
public class Config {
@Bean
public B b() { return new B(); }
@Bean
@DependsOn("b") // 容器會先實例化 b,再實例化 a
public A a() { return new A(); }
}
直接將注解寫在類上也可以
@Component
@DependsOn("b")
public class A { }
3、@Order 或 Ordered(只影響“收集型”順序)
適用范圍:
@Bean方法返回的是 Collection 注入點(如List<X>、Map<String,X>)。CommandLineRunner / ApplicationRunner / Filter / Interceptor等“鏈式”擴展點。對普通的單例 Bean 實例化順序無效。
即使兩個單例 Bean 實現了 Ordered 接口,只要它們之間不存在“收集型注入”或“鏈式擴展點”,Spring 仍然不保證誰先實例化。
使用場景如下代碼示例,使用此注解的Bean都是實現了同一個接口的同類型。
@Component
@Order(1)
public class FirstRunner implements CommandLineRunner { ... }
@Component
@Order(2)
public class SecondRunner implements CommandLineRunner { ... }
4、BeanDefinitionRegistryPostProcessor(BeanFactoryPostProcessor 的擴展)
在容器刷新早期(所有 BeanDefinition 加載完、實例化之前)把定義順序調到自己想要的順序。
@Component
public class OrderBeanProcessor implements BeanFactoryPostProcessor {
// 讓某個 Bean 在普通 Bean 實例化之前提前實例化
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
PrimaryOrderBean bean = beanFactory.getBean(PrimaryOrderBean.class);
System.out.println(bean);
}
}
@Component
public class PrimaryOrderBean {
public PrimaryOrderBean() {
System.out.println("init primary order bean");
}
@Override
public String toString() {
return "PrimaryOrderBean{toString}";
}
}
此方式的風險:可讀性差,容易踩坑,除非寫框架,否則不建議。
5、 @AutoConfigureBefore / @AutoConfigureAfter(僅對 spring.factories 里的自動配置生效)
當在寫自己的 starter時,想讓 MyAutoConfiguration 在 DataSourceAutoConfiguration 之前/之后運行。
對普通 @Configuration 無效,也不會影響 Bean 實例化順序,只影響配置類解析順序。
@Configuration
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
public class MyAutoConfiguration { }
這兩個注解只對 META-INF/spring.factories 中注冊的 EnableAutoConfiguration 類生效;寫在普通 @Configuration 類上會被忽略。
6、實現 PriorityOrdered / Ordered(只影響“后處理”順序)
對普通 Bean 的“實例化順序”沒有任何影響,僅當 Spring 內部收集 BeanPostProcessor、FactoryPostProcessor 等擴展點時使用。
作者:紀莫
歡迎任何形式的轉載,但請務必注明出處。
限于本人水平,如果文章和代碼有表述不當之處,還請不吝賜教。
歡迎掃描二維碼關注公眾號:Jimoer
文章會同步到公眾號上面,大家一起成長,共同提升技術能力。
聲援博主:如果您覺得文章對您有幫助,可以點擊文章右下角【推薦】一下。
您的鼓勵是博主的最大動力!


SpringBoot的類加載和傳統的雙親委派有什么區別? SpringBoot如何按順序實例化Bean
浙公網安備 33010602011771號