SmartInitializingSingleton
一、基本信息
?? 作者 - Lex ?? 博客 - 我的CSDN ?? 文章目錄 - 所有文章 ?? 源碼地址 - SmartInitializingSingleton源碼
二鸽捻、接口描述
SmartInitializingSingleton
接口呼巴,用于bean初始化,當(dāng)所有單例bean都已完全初始化后御蒲,用此接口進(jìn)行回調(diào)衣赶。
三、接口源碼
SmartInitializingSingleton
是 Spring 框架自 4.1 版本開(kāi)始引入的一個(gè)核心接口厚满。其中afterSingletonsInstantiated()
方法會(huì)在單例預(yù)實(shí)例化階段結(jié)束時(shí)被調(diào)用府瞄。它保證所有常規(guī)的單例beans在此時(shí)已經(jīng)被創(chuàng)建和初始化。
/**
* 在BeanFactory啟動(dòng)時(shí)碘箍,單例預(yù)實(shí)例化階段結(jié)束后觸發(fā)的回調(diào)接口遵馆。
* 單例beans可以實(shí)現(xiàn)此接口,以在常規(guī)單例實(shí)例化算法后執(zhí)行某些初始化丰榴,
* 避免因意外的早期初始化(例如货邓,從ListableBeanFactory#getBeansOfType調(diào)用)引起的副作用。
* 在這方面四濒,它是InitializingBean的替代品换况,后者在bean的本地構(gòu)建階段結(jié)束時(shí)被觸發(fā)。
*
* 這個(gè)回調(diào)變種與org.springframework.context.event.ContextRefreshedEvent有些類(lèi)似盗蟆,
* 但不需要實(shí)現(xiàn)org.springframework.context.ApplicationListener戈二,
* 也不需要在上下文層次結(jié)構(gòu)中過(guò)濾上下文引用等。它還意味著僅依賴(lài)于beans包喳资,
* 并由單獨(dú)的ListableBeanFactory實(shí)現(xiàn)尊重觉吭,不僅僅在org.springframework.context.ApplicationContext環(huán)境中。
*
* 注意: 如果我們打算開(kāi)始/管理異步任務(wù)仆邓,最好實(shí)現(xiàn)org.springframework.context.Lifecycle鲜滩,
* 它提供了一個(gè)更豐富的運(yùn)行時(shí)管理模型伴鳖,并允許分階段啟動(dòng)/關(guān)閉。
*
* @author Juergen Hoeller
* @since 4.1
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons()
*/
public interface SmartInitializingSingleton {
/**
* 在單例預(yù)實(shí)例化階段的末尾調(diào)用绒北,
* 保證所有常規(guī)單例beans已經(jīng)創(chuàng)建黎侈。在此方法中的
* ListableBeanFactory#getBeansOfType調(diào)用不會(huì)在引導(dǎo)期間引起意外的副作用。
* 注意: 此回調(diào)不會(huì)為在BeanFactory啟動(dòng)后按需延遲初始化的單例beans觸發(fā)闷游,
* 也不會(huì)觸發(fā)任何其他bean范圍峻汉。僅為具有預(yù)期引導(dǎo)語(yǔ)義的beans小心使用它。
*/
void afterSingletonsInstantiated();
}
四脐往、主要功能
-
bean已完全初始化后回調(diào)
- 提供了一個(gè)回調(diào)機(jī)制休吠,允許單例bean在Spring容器中所有其他常規(guī)單例bean都已完全初始化之后,執(zhí)行某些特定的初始化操作业簿。具體來(lái)說(shuō)瘤礁,當(dāng)所有的單例bean都被實(shí)例化和初始化后,
SmartInitializingSingleton
接口中的afterSingletonsInstantiated()
方法會(huì)被調(diào)用梅尤。
- 提供了一個(gè)回調(diào)機(jī)制休吠,允許單例bean在Spring容器中所有其他常規(guī)單例bean都已完全初始化之后,執(zhí)行某些特定的初始化操作业簿。具體來(lái)說(shuō)瘤礁,當(dāng)所有的單例bean都被實(shí)例化和初始化后,
五柜思、最佳實(shí)踐
首先來(lái)看看啟動(dòng)類(lèi)入口,上下文環(huán)境使用AnnotationConfigApplicationContext
(此類(lèi)是使用Java注解來(lái)配置Spring容器的方式)巷燥,構(gòu)造參數(shù)我們給定了一個(gè)MyConfiguration
組件類(lèi)赡盘。
public class SmartInitializingSingletonApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
}
}
這里使用@Bean
注解,定義了一個(gè)Bean缰揪,是為了確保 MySmartInitializingSingleton
被 Spring 容器執(zhí)行
@Configuration
@ComponentScan("com.xcs.spring.service")
public class MyConfiguration {
@Bean
public static MySmartInitializingSingleton mySmartInitializingSingleton(){
return new MySmartInitializingSingleton();
}
}
MySmartInitializingSingleton
類(lèi)中陨享,在所有其他的單例bean被完全初始化后,然后在afterSingletonsInstantiated()
方法會(huì)啟動(dòng)MyService
類(lèi)中定義的定時(shí)任務(wù)钝腺。
public class MySmartInitializingSingleton implements SmartInitializingSingleton {
@Autowired
private MyService myService;
@Override
public void afterSingletonsInstantiated() {
myService.startScheduledTask();
}
}
MyService
定義了一個(gè)定時(shí)任務(wù)抛姑,該任務(wù)會(huì)每隔2秒打印出當(dāng)前的日期時(shí)間和"hello world"消息。其中MySmartInitializingSingleton
會(huì)在所有的單例bean完全初始化后艳狐,調(diào)用startScheduledTask()
方法定硝,從而啟動(dòng)定時(shí)任務(wù)。
@Service
public class MyService {
/**
* 這里使用了Java的Timer來(lái)模擬定時(shí)任務(wù)毫目。在實(shí)際應(yīng)用中喷斋,可能會(huì)使用更復(fù)雜的調(diào)度機(jī)制。
*/
public void startScheduledTask() {
new java.util.Timer().schedule(
new java.util.TimerTask() {
@Override
public void run() {
System.out.println(getDate() + " hello world ");
}
},
0,
2000
);
}
/**
* 獲取當(dāng)前時(shí)間
*
* @return
*/
public String getDate() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return now.format(formatter);
}
}
運(yùn)行結(jié)果發(fā)現(xiàn)蒜茴,MySmartInitializingSingleton
成功地在所有其他的單例bean初始化后啟動(dòng)了MyService
中的定時(shí)任務(wù)。我們的實(shí)現(xiàn)是正確的浆西,每隔2秒都會(huì)產(chǎn)生下述輸出粉私。
2023-09-27 10:41:36 hello world
2023-09-27 10:41:38 hello world
2023-09-27 10:41:40 hello world
2023-09-27 10:41:42 hello world
2023-09-27 10:41:44 hello world
六、時(shí)序圖
sequenceDiagram
Title: SmartInitializingSingleton時(shí)序圖
participant SmartInitializingSingletonApplication
participant AnnotationConfigApplicationContext
participant AbstractApplicationContext
participant DefaultListableBeanFactory
participant MySmartInitializingSingleton
SmartInitializingSingletonApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)<br>創(chuàng)建上下文
AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()<br>刷新上下文
AbstractApplicationContext->>AbstractApplicationContext:finishBeanFactoryInitialization(beanFactory)<br>初始化Bean工廠
AbstractApplicationContext->>DefaultListableBeanFactory:preInstantiateSingletons()<br>實(shí)例化單例
DefaultListableBeanFactory->>MySmartInitializingSingleton:afterSingletonsInstantiated()<br>所有單例初始化
七近零、源碼分析
public class SmartInitializingSingletonApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
}
}
在org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext
構(gòu)造函數(shù)中诺核,執(zhí)行了三個(gè)步驟抄肖,我們重點(diǎn)關(guān)注refresh()
方法
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
refresh();
}
在org.springframework.context.support.AbstractApplicationContext#refresh
方法中我們重點(diǎn)關(guān)注一下finishBeanFactoryInitialization(beanFactory)
這方法會(huì)對(duì)實(shí)例化所有剩余非懶加載的單列Bean對(duì)象,其他方法不是本次源碼閱讀的重點(diǎn)暫時(shí)忽略窖杀。
@Override
public void refresh() throws BeansException, IllegalStateException {
// ... [代碼部分省略以簡(jiǎn)化]
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// ... [代碼部分省略以簡(jiǎn)化]
}
在org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
方法中漓摩,會(huì)繼續(xù)調(diào)用DefaultListableBeanFactory
類(lèi)中的preInstantiateSingletons
方法來(lái)完成所有剩余非懶加載的單列Bean對(duì)象。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// ... [代碼部分省略以簡(jiǎn)化]
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
在org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
方法中入客,實(shí)現(xiàn)SmartInitializingSingleton
接口的beans在所有其他的單例beans完全實(shí)例化后才會(huì)觸發(fā)其afterSingletonsInstantiated
方法管毙,從而確保了初始化的正確時(shí)序。
@Override
public void preInstantiateSingletons() throws BeansException {
// ... [代碼部分省略以簡(jiǎn)化]
// 觸發(fā)所有SmartInitializingSingleton bean的初始化后回調(diào)桌硫。夭咬。。
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
// ... [代碼部分省略以簡(jiǎn)化]
smartSingleton.afterSingletonsInstantiated();
// ... [代碼部分省略以簡(jiǎn)化]
}
}
}
八铆隘、注意事項(xiàng)
-
避免復(fù)雜邏輯:
-
SmartInitializingSingleton
的設(shè)計(jì)是為了執(zhí)行初始化后的邏輯卓舵。避免在afterSingletonsInstantiated()
方法中加入過(guò)于復(fù)雜的邏輯。
-
-
注意依賴(lài)關(guān)系
- 當(dāng)
afterSingletonsInstantiated()
被調(diào)用時(shí)膀钠,所有的常規(guī)單例bean都已經(jīng)被初始化掏湾。但請(qǐng)確保在這個(gè)方法中調(diào)用的任何bean已經(jīng)完全初始化并且所有的依賴(lài)都被滿(mǎn)足。
- 當(dāng)
-
避免早期初始化
- 請(qǐng)確保不會(huì)意外地觸發(fā)其他bean的早期初始化肿嘲,尤其是在
afterSingletonsInstantiated()
方法中融击。早期初始化可能會(huì)導(dǎo)致不可預(yù)見(jiàn)的副作用。
- 請(qǐng)確保不會(huì)意外地觸發(fā)其他bean的早期初始化肿嘲,尤其是在
-
限制范圍
-
SmartInitializingSingleton
僅對(duì)常規(guī)單例bean起作用睦刃。對(duì)于在BeanFactory
啟動(dòng)后按需延遲初始化或其他作用域的beans(如原型作用域)砚嘴,此回調(diào)不會(huì)被觸發(fā)。
-
-
異步任務(wù)
- 如果我們的目的是啟動(dòng)或管理異步任務(wù)涩拙,最好使用
Lifecycle
接口或考慮其他Spring的啟動(dòng)監(jiān)聽(tīng)器际长,如ApplicationListener<ContextRefreshedEvent>
。Lifecycle
為運(yùn)行時(shí)管理提供了一個(gè)更完善的模型兴泥。
- 如果我們的目的是啟動(dòng)或管理異步任務(wù)涩拙,最好使用
-
確保冪等性
- 如果有可能多次刷新應(yīng)用程序上下文(雖然在我看來(lái)這種情況基本上很少)工育,請(qǐng)確保
afterSingletonsInstantiated()
方法的實(shí)現(xiàn)是冪等的,即多次執(zhí)行與一次執(zhí)行產(chǎn)生的效果相同搓彻。
- 如果有可能多次刷新應(yīng)用程序上下文(雖然在我看來(lái)這種情況基本上很少)工育,請(qǐng)確保
-
與
InitializingBean
和@PostConstruct
的區(qū)別-
SmartInitializingSingleton
和InitializingBean
或@PostConstruct
注解有區(qū)別如绸。后兩者是bean級(jí)別的初始化回調(diào),而SmartInitializingSingleton
是容器級(jí)別的旭贬,確保在所有bean初始化之后才執(zhí)行怔接。
-
-
不要濫用
- 只有在確實(shí)需要確保所有其他bean都初始化后才執(zhí)行某些操作時(shí),才應(yīng)使用
SmartInitializingSingleton
稀轨。如果不需要這種保證扼脐,考慮使用更標(biāo)準(zhǔn)的初始化回調(diào)。
- 只有在確實(shí)需要確保所有其他bean都初始化后才執(zhí)行某些操作時(shí),才應(yīng)使用
八奋刽、總結(jié)
最佳實(shí)踐總結(jié)
-
啟動(dòng)入口
- 在示例的啟動(dòng)類(lèi)
SmartInitializingSingletonApplication
中瓦侮,我們使用了AnnotationConfigApplicationContext
來(lái)加載和初始化Spring容器艰赞。我們?yōu)樯舷挛奶峁┝艘粋€(gè)Java配置類(lèi)MyConfiguration
,該類(lèi)定義了應(yīng)該由Spring掃描和管理的bean肚吏。
- 在示例的啟動(dòng)類(lèi)
-
配置
- 在
MyConfiguration
類(lèi)中方妖,我們使用@Bean
注解顯式地定義了MySmartInitializingSingleton
這個(gè)bean。這確保了MySmartInitializingSingleton
被Spring容器管理并在適當(dāng)?shù)臅r(shí)機(jī)執(zhí)行罚攀。
- 在
-
實(shí)現(xiàn)SmartInitializingSingleton接口
-
MySmartInitializingSingleton
實(shí)現(xiàn)了SmartInitializingSingleton
接口党觅。當(dāng)所有其他的單例bean都被完全初始化后,afterSingletonsInstantiated()
方法被調(diào)用坞生。在這個(gè)方法中仔役,我們啟動(dòng)了MyService
類(lèi)中定義的定時(shí)任務(wù)。
-
-
定時(shí)任務(wù)
-
MyService
中定義了一個(gè)使用Java的Timer
模擬的定時(shí)任務(wù)是己。這個(gè)任務(wù)會(huì)每隔2秒打印當(dāng)前時(shí)間和"hello world"這個(gè)消息又兵。在實(shí)際應(yīng)用中,可能會(huì)使用更復(fù)雜的調(diào)度機(jī)制卒废,如Spring的TaskScheduler
或Quartz等沛厨。
-
-
結(jié)果
- 啟動(dòng)示例應(yīng)用后,可以觀察到每隔2秒在控制臺(tái)上都會(huì)輸出格式化的當(dāng)前時(shí)間后跟著"hello world"這樣的消息摔认,證明定時(shí)任務(wù)已經(jīng)成功啟動(dòng)并在運(yùn)行逆皮。
源碼分析總結(jié)
-
應(yīng)用啟動(dòng)
- 一切從
SmartInitializingSingletonApplication
的主函數(shù)開(kāi)始,其中初始化了AnnotationConfigApplicationContext
参袱,這是Spring用于Java注解配置的上下文电谣。
- 一切從
-
AnnotationConfigApplicationContext構(gòu)造函數(shù)
- 在
AnnotationConfigApplicationContext
的構(gòu)造函數(shù)中,執(zhí)行了三個(gè)主要步驟抹蚀,其中最關(guān)鍵的是refresh()
方法剿牺。
- 在
-
執(zhí)行refresh方法
-
refresh()
方法是Spring上下文刷新的核心。在這里环壤,重點(diǎn)是finishBeanFactoryInitialization(beanFactory)
晒来,它負(fù)責(zé)實(shí)例化所有剩余的非懶加載單例Bean。
-
-
完成BeanFactory初始化
- 在
finishBeanFactoryInitialization
方法中郑现,為了完成上述任務(wù)湃崩,它進(jìn)一步調(diào)用了beanFactory.preInstantiateSingletons()
。
- 在
-
預(yù)實(shí)例化單例
- 這步是最關(guān)鍵的接箫。在
DefaultListableBeanFactory
的preInstantiateSingletons
方法中攒读,所有單例beans都被實(shí)例化。緊接著辛友,為那些實(shí)現(xiàn)了SmartInitializingSingleton
接口的beans觸發(fā)了afterSingletonsInstantiated
回調(diào)整陌,確保這些回調(diào)在所有其他單例beans完全實(shí)例化后才被執(zhí)行。
- 這步是最關(guān)鍵的接箫。在