SmartInitializingSingleton源碼分析

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();

}

四脐往、主要功能

  1. bean已完全初始化后回調(diào)
    • 提供了一個(gè)回調(diào)機(jī)制休吠,允許單例bean在Spring容器中所有其他常規(guī)單例bean都已完全初始化之后,執(zhí)行某些特定的初始化操作业簿。具體來(lái)說(shuō)瘤礁,當(dāng)所有的單例bean都被實(shí)例化和初始化后,SmartInitializingSingleton接口中的afterSingletonsInstantiated()方法會(huì)被調(diào)用梅尤。

五柜思、最佳實(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)

  1. 避免復(fù)雜邏輯

    • SmartInitializingSingleton的設(shè)計(jì)是為了執(zhí)行初始化后的邏輯卓舵。避免在afterSingletonsInstantiated()方法中加入過(guò)于復(fù)雜的邏輯。
  2. 注意依賴(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)足。
  3. 避免早期初始化

    • 請(qǐng)確保不會(huì)意外地觸發(fā)其他bean的早期初始化肿嘲,尤其是在afterSingletonsInstantiated()方法中融击。早期初始化可能會(huì)導(dǎo)致不可預(yù)見(jiàn)的副作用。
  4. 限制范圍

    • SmartInitializingSingleton僅對(duì)常規(guī)單例bean起作用睦刃。對(duì)于在BeanFactory啟動(dòng)后按需延遲初始化或其他作用域的beans(如原型作用域)砚嘴,此回調(diào)不會(huì)被觸發(fā)。
  5. 異步任務(wù)

    • 如果我們的目的是啟動(dòng)或管理異步任務(wù)涩拙,最好使用Lifecycle接口或考慮其他Spring的啟動(dòng)監(jiān)聽(tīng)器际长,如ApplicationListener<ContextRefreshedEvent>Lifecycle為運(yùn)行時(shí)管理提供了一個(gè)更完善的模型兴泥。
  6. 確保冪等性

    • 如果有可能多次刷新應(yīng)用程序上下文(雖然在我看來(lái)這種情況基本上很少)工育,請(qǐng)確保afterSingletonsInstantiated()方法的實(shí)現(xiàn)是冪等的,即多次執(zhí)行與一次執(zhí)行產(chǎn)生的效果相同搓彻。
  7. InitializingBean@PostConstruct的區(qū)別

    • SmartInitializingSingletonInitializingBean@PostConstruct注解有區(qū)別如绸。后兩者是bean級(jí)別的初始化回調(diào),而SmartInitializingSingleton是容器級(jí)別的旭贬,確保在所有bean初始化之后才執(zhí)行怔接。
  8. 不要濫用

    • 只有在確實(shí)需要確保所有其他bean都初始化后才執(zhí)行某些操作時(shí),才應(yīng)使用SmartInitializingSingleton稀轨。如果不需要這種保證扼脐,考慮使用更標(biāo)準(zhǔn)的初始化回調(diào)。

八奋刽、總結(jié)

最佳實(shí)踐總結(jié)

  1. 啟動(dòng)入口

    • 在示例的啟動(dòng)類(lèi)SmartInitializingSingletonApplication中瓦侮,我們使用了AnnotationConfigApplicationContext來(lái)加載和初始化Spring容器艰赞。我們?yōu)樯舷挛奶峁┝艘粋€(gè)Java配置類(lèi)MyConfiguration,該類(lèi)定義了應(yīng)該由Spring掃描和管理的bean肚吏。
  2. 配置

    • MyConfiguration類(lèi)中方妖,我們使用@Bean注解顯式地定義了MySmartInitializingSingleton這個(gè)bean。這確保了MySmartInitializingSingleton被Spring容器管理并在適當(dāng)?shù)臅r(shí)機(jī)執(zhí)行罚攀。
  3. 實(shí)現(xiàn)SmartInitializingSingleton接口

    • MySmartInitializingSingleton實(shí)現(xiàn)了SmartInitializingSingleton接口党觅。當(dāng)所有其他的單例bean都被完全初始化后,afterSingletonsInstantiated()方法被調(diào)用坞生。在這個(gè)方法中仔役,我們啟動(dòng)了MyService類(lèi)中定義的定時(shí)任務(wù)。
  4. 定時(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等沛厨。
  5. 結(jié)果

    • 啟動(dòng)示例應(yīng)用后,可以觀察到每隔2秒在控制臺(tái)上都會(huì)輸出格式化的當(dāng)前時(shí)間后跟著"hello world"這樣的消息摔认,證明定時(shí)任務(wù)已經(jīng)成功啟動(dòng)并在運(yùn)行逆皮。

源碼分析總結(jié)

  1. 應(yīng)用啟動(dòng)

    • 一切從SmartInitializingSingletonApplication的主函數(shù)開(kāi)始,其中初始化了AnnotationConfigApplicationContext参袱,這是Spring用于Java注解配置的上下文电谣。
  2. AnnotationConfigApplicationContext構(gòu)造函數(shù)

    • AnnotationConfigApplicationContext的構(gòu)造函數(shù)中,執(zhí)行了三個(gè)主要步驟抹蚀,其中最關(guān)鍵的是refresh()方法剿牺。
  3. 執(zhí)行refresh方法

    • refresh()方法是Spring上下文刷新的核心。在這里环壤,重點(diǎn)是finishBeanFactoryInitialization(beanFactory)晒来,它負(fù)責(zé)實(shí)例化所有剩余的非懶加載單例Bean。
  4. 完成BeanFactory初始化

    • finishBeanFactoryInitialization方法中郑现,為了完成上述任務(wù)湃崩,它進(jìn)一步調(diào)用了beanFactory.preInstantiateSingletons()
  5. 預(yù)實(shí)例化單例

    • 這步是最關(guān)鍵的接箫。在DefaultListableBeanFactorypreInstantiateSingletons方法中攒读,所有單例beans都被實(shí)例化。緊接著辛友,為那些實(shí)現(xiàn)了SmartInitializingSingleton接口的beans觸發(fā)了afterSingletonsInstantiated回調(diào)整陌,確保這些回調(diào)在所有其他單例beans完全實(shí)例化后才被執(zhí)行。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市泌辫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌九默,老刑警劉巖震放,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異驼修,居然都是意外死亡殿遂,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)乙各,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)墨礁,“玉大人,你說(shuō)我怎么就攤上這事耳峦《骶玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蹲坷,是天一觀的道長(zhǎng)驶乾。 經(jīng)常有香客問(wèn)我,道長(zhǎng)循签,這世上最難降的妖魔是什么级乐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮县匠,結(jié)果婚禮上风科,老公的妹妹穿的比我還像新娘。我一直安慰自己乞旦,他們只是感情好贼穆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著杆查,像睡著了一般扮惦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亲桦,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天崖蜜,我揣著相機(jī)與錄音,去河邊找鬼客峭。 笑死豫领,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舔琅。 我是一名探鬼主播等恐,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了课蔬?” 一聲冷哼從身側(cè)響起囱稽,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎二跋,沒(méi)想到半個(gè)月后战惊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扎即,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年吞获,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谚鄙。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡各拷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闷营,到底是詐尸還是另有隱情烤黍,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布粮坞,位于F島的核電站蚊荣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏莫杈。R本人自食惡果不足惜互例,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筝闹。 院中可真熱鬧媳叨,春花似錦、人聲如沸关顷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)议双。三九已至痘番,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間平痰,已是汗流浹背汞舱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宗雇,地道東北人昂芜。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像赔蒲,于是被迫代替她去往敵國(guó)和親泌神。 傳聞我的和親對(duì)象是個(gè)殘疾皇子良漱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容