淺析Spring如何控制Bean的加載順序
在大多數(shù)情況下,我們不需要手動控制 Bean 的加載順序,因?yàn)?Spring 的 IoC 容器足夠智能。
核心原則:依賴驅(qū)動加載
Spring IoC 容器會構(gòu)建一個依賴關(guān)系圖(Dependency Graph)。如果 Bean A 依賴于 Bean B(例如,A 的構(gòu)造函數(shù)需要一個 B 類型的參數(shù)),Spring 會保證在創(chuàng)建 Bean A 之前,Bean B 已經(jīng)被完全創(chuàng)建和初始化好了。
@Service public class ServiceA { private final ServiceB serviceB; // 因?yàn)?ServiceA 在構(gòu)造時需要 ServiceB, // Spring 保證 serviceB 會先被創(chuàng)建。 @Autowired public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; System.out.println("ServiceA 初始化了,此時 ServiceB 已經(jīng)可用。"); } } @Service public class ServiceB { public ServiceB() { System.out.println("ServiceB 初始化了。"); } } // 輸出結(jié)果: // ServiceB 初始化了。 // ServiceA 初始化了,此時 ServiceB 已經(jīng)可用。
但是,在某些特殊場景下,這種隱式的依賴關(guān)系可能不存在,我們需要強(qiáng)制一個特定的初始化順序。這時就需要手動控制。
手動控制 Bean 加載順序的方法
以下是幾種常用的手動控制方法。
方法 1:使用@DependsOn(最直接、最明確)
@DependsOn
注解可以直接聲明一個 Bean 在初始化之前,必須先初始化另一個Bean。這用于處理沒有直接依賴關(guān)系(即 A 類中沒有 B 類的字段引用),但存在邏輯上或“副作用”上的依賴(比如 B 必須先初始化數(shù)據(jù)庫表,A 才能去操作它)。
場景:一個 DatabaseInitializer
負(fù)責(zé)創(chuàng)建數(shù)據(jù)庫表,而一個 DataImporter
負(fù)責(zé)向這些表中導(dǎo)入數(shù)據(jù)。DataImporter
并沒有直接注入 DatabaseInitializer
,但它依賴于 DatabaseInitializer
的工作先完成。
// Bean A: 數(shù)據(jù)導(dǎo)入器 @Component @DependsOn("databaseInitializer") // <-- 關(guān)鍵點(diǎn) public class DataImporter { public DataImporter() { System.out.println("DataImporter: 開始導(dǎo)入數(shù)據(jù)...(此時數(shù)據(jù)庫表一定已創(chuàng)建)"); // ...導(dǎo)入邏輯... } } // Bean B: 數(shù)據(jù)庫初始化器 @Component("databaseInitializer") // 注意Bean的名字 public class DatabaseInitializer { public DatabaseInitializer() { System.out.println("DatabaseInitializer: 正在創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu)..."); // ...建表邏輯... } }
輸出順序保證是:
DatabaseInitializer: 正在創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu)...
DataImporter: 開始導(dǎo)入數(shù)據(jù)...(此時數(shù)據(jù)庫表一定已創(chuàng)建)
@DependsOn
可以接受一個字符串?dāng)?shù)組,來依賴多個 Bean:@DependsOn({"beanA", "beanB"})
。
方法 2:使用@EventListener監(jiān)聽容器事件(適用于后處理邏輯)
有時候,你需要的不是“在另一個 Bean 之前加載”,而是“在所有 Bean 都加載完畢之后,再執(zhí)行某些邏輯”。這對于緩存預(yù)熱、啟動后執(zhí)行一次性任務(wù)等場景非常有用。
你可以通過監(jiān)聽 ContextRefreshedEvent
來實(shí)現(xiàn)。這個事件會在 Spring 容器完成所有 Bean 的初始化和配置后發(fā)布。
場景:在應(yīng)用啟動后,需要將數(shù)據(jù)庫中的熱門商品預(yù)加載到 Redis 緩存中。
@Component public class CacheWarmer { private final ProductService productService; private final RedisTemplate<String, Object> redisTemplate; public CacheWarmer(ProductService productService, RedisTemplate<String, Object> redisTemplate) { this.productService = productService; this.redisTemplate = redisTemplate; } @EventListener(ContextRefreshedEvent.class) // <-- 關(guān)鍵點(diǎn) public void onApplicationEvent() { System.out.println("容器已啟動完畢,開始預(yù)熱緩存..."); // 此時,productService 和 redisTemplate 肯定已經(jīng)準(zhǔn)備就緒 List<Product> hotProducts = productService.findHotProducts(); redisTemplate.opsForValue().set("hot_products", hotProducts); System.out.println("緩存預(yù)熱完成!"); } }
這種方式保證了你的邏輯在整個應(yīng)用程序準(zhǔn)備就緒后才執(zhí)行,是一種非常安全和解耦的順序控制。
方法 3:在 Spring Boot 中使用@AutoConfigureAfter/@AutoConfigureBefore
這兩種注解是 Spring Boot 自動配置模塊專用的。它們用來控制**配置類(@Configuration
)**之間的加載順序,而不是普通的 Component Bean。
當(dāng)你編寫自己的 starter
或自動配置時,這個方法至關(guān)重要。
場景:你的自動配置 MyDataSourceAutoConfiguration
必須在 Spring Boot 的 DataSourceAutoConfiguration
之后執(zhí)行,以確保數(shù)據(jù)源已經(jīng)存在。
@Configuration @AutoConfigureAfter(DataSourceAutoConfiguration.class) // <-- 關(guān)鍵點(diǎn) public class MyCustomConfiguration { @Bean public MyService myService(DataSource dataSource) { // 因?yàn)橛?@AutoConfigureAfter,這里的 dataSource 肯定已經(jīng)被 // DataSourceAutoConfiguration 配置好了。 return new MyService(dataSource); } }
一個常見的誤區(qū):@Order
@Order
注解或 Ordered
接口完全不能控制 Bean 的加載(初始化)順序!
這是一個非常非常常見的誤解。
@Order
的作用是對集合中的元素進(jìn)行排序。當(dāng)你將多個相同接口的實(shí)現(xiàn)注入到一個 List
中時,@Order
用來決定它們在這個 List
中的順序。
例子:你有多個過濾器 Filter
,你想控制它們的執(zhí)行順序。
@Component @Order(1) // 序號小的優(yōu)先 class LoggingFilter implements Filter { ... } @Component @Order(2) class SecurityFilter implements Filter { ... } @Service public class MyProcessor { @Autowired private List<Filter> filters; // 注入一個Filter的List public void process() { // 因?yàn)?@Order,可以保證 LoggingFilter 在 SecurityFilter 之前執(zhí)行 filters.forEach(Filter::doFilter); } }
在這個例子中,@Order
決定了 filters
列表中的元素順序,但它不影響 LoggingFilter
和 SecurityFilter
這兩個 Bean 本身的初始化順序。它們的初始化順序仍然由 Spring 的依賴圖決定。
總結(jié)
方法 | 用途 | 優(yōu)點(diǎn) | 缺點(diǎn)/適用范圍 |
---|---|---|---|
隱式依賴 (構(gòu)造函數(shù)) | [首選] 定義 Bean 之間的直接依賴關(guān)系 | 最自然、最符合 IoC 思想,代碼清晰 | 無法處理沒有直接引用的“副作用”依賴 |
@DependsOn | [推薦] 強(qiáng)制指定初始化順序,處理“副作用”依賴 | 意圖明確,直接解決問題 | 引入了對 Bean 名字的字符串依賴,略有侵入性 |
@EventListener | 在所有 Bean 初始化后執(zhí)行邏輯 | 高度解耦,適用于應(yīng)用啟動后的任務(wù) | 不是控制 Bean 之間的順序,而是控制邏輯的執(zhí)行時機(jī) |
@AutoConfigure... | 控制 Spring Boot 自動配置類的順序 | Spring Boot 自動配置的標(biāo)準(zhǔn)方式 | 只對 @Configuration 類有效 |
@Order | [非加載順序] 對集合中的 Bean 進(jìn)行排序 | 控制業(yè)務(wù)邏輯的執(zhí)行順序 | 完全不能控制 Bean 的加載或初始化順序 |
最佳實(shí)踐:
- 95% 的情況,請使用構(gòu)造函數(shù)注入來表達(dá)依賴關(guān)系,讓 Spring 自動管理加載順序。這是最干凈、最可靠的方式。
- 當(dāng)你確實(shí)需要處理沒有直接引用的邏輯依賴時(如初始化任務(wù)),@DependsOn 是你的首選工具。
- 當(dāng)你需要在整個應(yīng)用啟動完成后執(zhí)行代碼時,@EventListener(ContextRefreshedEvent.class) 是最優(yōu)雅的方案。
- 永遠(yuǎn)不要試圖用 @Order 去控制 Bean 的加載順序。
到此這篇關(guān)于淺析Spring如何控制Bean的加載順序的文章就介紹到這了,更多相關(guān)Spring控制Bean加載順序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot集成shiro權(quán)限管理簡單實(shí)現(xiàn)
這篇文章主要介紹了springboot集成shiro權(quán)限管理簡單實(shí)現(xiàn),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-08-08Java線程池流程編排運(yùn)用實(shí)戰(zhàn)源碼
這篇文章主要介紹了Java線程池流程編排運(yùn)用實(shí)戰(zhàn)源碼,就在流程引擎的基礎(chǔ)上運(yùn)用?ThreadPoolExecutor,使用線程池實(shí)現(xiàn)?SpringBean?的異步執(zhí)行2022-03-03java中的Consumer、Supply如何實(shí)現(xiàn)多參數(shù)?
Java的Consumer接口只能接受一個參數(shù),但可以通過自定義接口、使用Tuple或嵌套結(jié)構(gòu)來實(shí)現(xiàn)對多個參數(shù)的處理,對于Supplier接口,它不能接受參數(shù),但可以通過自定義BiSupplier、結(jié)合Function或封裝參數(shù)為對象來實(shí)現(xiàn)對兩個參數(shù)并返回一個值的功能2024-11-11java算法題解LeetCode30包含min函數(shù)的棧實(shí)例
這篇文章主要為大家介紹了java算法題解LeetCode30包含min函數(shù)的棧實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01springboot如何為web層添加統(tǒng)一請求前綴
這篇文章主要介紹了springboot如何為web層添加統(tǒng)一請求前綴,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02Java中IO流 RandomAccessFile類實(shí)例詳解
這篇文章主要介紹了Java中IO流 RandomAccessFile類實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05SpringMVC訪問靜態(tài)資源的三種方式小結(jié)
這篇文章主要介紹了SpringMVC訪問靜態(tài)資源的三種方式小結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02