走進Spring中Bean的世界

以前十一假期,基於SpringBoot實現集成公司業務和通用封裝的starter,有點相似支付寶的Sofa-Boot。在實現過程當中,不斷優化的過程發現對源碼理解很差,starter很容易寫的不那麼「聰明」。因此就趁着假期一點點跟着源碼閱讀了起來,今天來分享一篇總結簡單Bean的生命週期。html

個人技術學習方法

我閱讀源碼的過程當中,一般分兩步走:跳出來,看全景;鑽進去,看本質。先了解Bean的生命週期圖對其有大致瞭解,該技術在大腦中有了一個「簡單」的構圖後。再閱讀相關源碼去確認本身在看全景時的大腦構圖是否合理。這個過程其實也適用於面對新環境,先熟悉本身所處項目的業務,再過技術棧與代碼相關細節。鑽進去看本質的過程每每是「煎熬的」,須要堅持與不斷的思考。git

Bean的生命週期圖

Bean建立過程的源碼閱讀索引

Spring源碼版本: 5.0.9.RELEASEgithub

源碼閱讀索引

DefaultListableBeanFactory
                            | preInstantiateSingletons(726)  -->  getBean(759);
                AbstractBeanFactory
                            | getBean(198)  -->  doGetBean(199)
                            | doGetBean(239)  -->  getSingleton(315)
                                                    DefaultSingletonBeanRegistry
                                                                | getSingleton(202)  -->  singletonFactory.getObject(222)
                            | getSingleton(315)  -->  createBean(317)
                AbstractAutowireCapableBeanFactory
                            | createBean(456)  -->  doCreateBean(495)
                            | doCreateBean(526)  -->  createBeanInstance(535)  return BeanWrapper
                            | doCreateBean(526)  -->  addSingletonFactory(566) 加入到三級緩存SingletonFactories
                            | doCreateBean(526)  -->  populateBean(572)
                            | doCreateBean(526)  -->  initializeBean(573)
                            | initializeBean(1678)  -->  invokeAwareMethods(1686)
                            | invokeAwareMethods(1709)  -->  ((BeanNameAware) bean).setBeanName(1712)
                            | invokeAwareMethods(1709)  -->  ((BeanClassLoaderAware) bean).setBeanClassLoader(1717)
                            | invokeAwareMethods(1709)  -->  ((BeanFactoryAware) bean).setBeanFactory(1721)
                            | initializeBean(1678)  -->  applyBeanPostProcessorsBeforeInitialization(1691)   這裏面一般調用的是AwareProcessor
                            | initializeBean(1678)  -->  invokeInitMethods(1695)
                            | invokeInitMethods(1695)  -->  ((InitializingBean) bean).afterPropertiesSet(1758)
                            | invokeInitMethods(1695)  -->  invokeCustomInitMethod(1767)
                            | initializeBean(1678)  -->  applyBeanPostProcessorsAfterInitialization(1703)
複製代碼

上面三個類之間的關係

上面的類繼承圖是我簡化過的,其實真正的Spring的BeanFacotory類泛化體系遠比此複雜多。spring

Bean的建立過程源碼分析

示例代碼

@Slf4j
@Component
public class ComponentA implements InitializingBean, DisposableBean, BeanFactoryAware, ApplicationContextAware {

    @Autowired
    private ComponentB componentB;

    @Value("${ca.test.val:default}")
    private String valA;

    @Override
    public void destroy() {
        log.info("$destroy()...");
    }

    @Override
    public void afterPropertiesSet() {
        log.info("$afterPropertiesSet()...");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("$setApplicationContext() applicationContext:{}", applicationContext);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        log.info("$setBeanFactory() beanFactory:{}", beanFactory);
    }

}
複製代碼
@Component
public class ComponentB {
    
    @Autowired
    private ComponentA componentA;

}
複製代碼
@SpringBootApplication
public class EasyUcApplication {
    public static void main(String[] args) {
         /**
         * 使用SpringBoot-2.0.5.RELEASE去啓動Spring容器,對應就是須要的Spring-5.0.9.RELEASE。
         */
        SpringApplication.run(EasyUcApplication.class, args);
    }
}
複製代碼

開始源碼閱讀

  • 按照源碼閱讀索引的入口,咱們查看下源碼

由於這塊是循環遍歷全部BeanDefinitionNamesF9找到上面咱們想要看到初始化過程的Bean。下文提到的全部F7F8F9分別表明進入方法內部、跳到下一行、跳到下一個斷點處。apache

  • F8按照索引目錄跟到759行 設計模式

  • F7進入此方法,進入到抽象類AbstractBeanFactory 緩存

  • F7進入doGetBean方法 bash

    進入到這個方法後,看下246行,它最終會進入到DefaultSigletonBeanRegistry中的getSingleton
    到這先認識下這三個緩存Map

    • singletonObjects: singleton object cache
    • earlySingletonObjects: Cache of singleton objects exposed in advance
    • singletonFactories: singleton object factory cache

    這三級緩存具體做用咱們在populate階段再分析架構

  • 返回到AbstractBeanFactorydoGetBean(246),F8接着跟到315行 app

    第二個參數是個函數式接口

  • F7跟進去,咱們看到到第222行調用了getObject()

  • getObject() F7跟進去其實調用的就是createBean

  • F7跟進去,到了AbstractAutowireCapableBeanFacotorycreateBean(456), 在這個方法內找到495行, F7進入此方法

    接下來纔開始正式進入到建立Bean的過程

建立Bean對象,加入第三級緩存中

  • 上一步咱們進入AbstractAutowireCapableBeanFacotorydoCreateBean(526)行,F8一直到535行,F7進入此方法。
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    /*省略代碼*/
    // 調用有Autowire的構造方式: 就是說構造方法的成員變量是須要注入值的。
    Class<?> beanClass = resolveBeanClass(mbd, beanName);
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    if (ctors != null ||
            mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
            mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {
        return autowireConstructor(beanName, mbd, ctors, args);
    }
    // 簡單的無參構造器:咱們這裏會走到這種方式
    return instantiateBean(beanName, mbd);  //1128
}
複製代碼
  • 接着F7進入到上面的instantiateBean(1128)
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
    /*省略代碼*/
    // 1.實例化對象
    Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
    // 2.建立該對象的BeanWrapper
    BeanWrapper bw = new BeanWrapperImpl(beanInstance);
    initBeanWrapper(bw);
    return bw;
}
複製代碼
  • 上面出現個BeanWrapper,它的做用是幹什麼的呢?

    • 咱們實例化對象的時候,實際上是調用構造器去反射建立對象。
    • 當咱們想對其屬性值賦值的時候,就可使用setPropertyValue去進行賦值,BeanWrapper提供一種能夠直接對屬性賦值的能力。以下面所示:
Class<?> clazz = Class.forName("cn.coderjia.easyuc.component.ComponentA");
    Constructor<?> ctor = clazz.getDeclaredConstructor();
    Object bean = ctor.newInstance();
    BeanWrapper beanWrapper = new BeanWrapperImpl(bean);
    beanWrapper.setPropertyValue("valA", "val");
複製代碼
  • 咱們一直F8返回AbstractAutowireCapableBeanFacotorydoCreateBean(535), 而後F8到559行。
  • 接着F8跟到566行
  • F7進入此方法,到了DefaultSingletonBeanRegistry
    咱們能夠發現這步是將對象建立的工廠函數緩存到singletonFactories,目的爲了解決循環引用的問題,那麼什麼是循環引用的狀況與如何解決的呢?且看下文分解。

注入Bean的屬性

  • 上小節咱們提出了疑問什麼是循環引用?咱們先來看一張圖。
    正如咱們的示例代碼,ComponetA依賴ComponentBComponentB又依賴於ComponentASpringBean的實例化Bean對象後,會對屬性值注入(populate),這樣就會出現如上圖的循環調用過程(instantiate A->populate ComponentB->instantiateB->populate ComponentA->instantiate A)。
  • 上面咱們分析了什麼是循環引用,接下來咱們先接着跟源碼。
    咱們上回書說到,將實例化後未初始化的Bean對象工廠放置到第三級緩存中。
  • F7進入到getEarlyBeanReference(566)
    理論上講,兩級緩存就夠了,對象實例化後放到earlySingletonObjects緩存中,在須要提早引用的地方取出就能夠解決循環引用的問題。可是第三級緩存存在的意義除了保存Bean外,還能夠在獲取的時候,對Bean能夠作前置處理(InstantiationAwareBeanPostProcessor),有這樣一種拓展的能力。
    簡單的理解就如上面圖中的過程: 建立Component A後,把Component A緩存起來,注入Component B屬性時,建立Component BComponent B須要注入Component A,從緩存把Component A取出來,完成Component B的建立全過程,在將Component B返回給Component AComponent A也可完成建立全過程。
  • F8跟到populateBean(572)
    按照咱們上面的解釋,這步是會去注入Componet B,而後天然而然須要建立Componet B。接下來看下是否是這樣。
  • F7進入此方法,到了AbstractAutowireCapableBeanFactorypopulateBean(1277)
  • F8一直往下跟1339行打個斷點,而後一直F9跳到bpAutowiredAnnotationBeanPostProcessor時候中止,F8跟到 1341行
  • F7進入此方法,來到CommonAnnotationBeanPostProcessormetadata.inject(318)
  • F7進入此方法,來到InjectionMetadatainject(81)
    寫到這咱們發現須要注入的屬性componetBvalA已經在checkoutedElements中,要對其進行值的注入。能夠發現@Autowired和@Value值就是在這階段對其賦值的。那麼componetBvalA何時加到checkoutedElements中的呢?
  • 先回退下,找到AbstractAutowireCapableBeanFactoryapplyMergedBeanDefinitionPostPorcessors(547)行,打個斷點
  • 從新啓動容器,F9一直跳到ComponetA
  • F7跟進,進入到AbstractAutowireCapableBeanFactoryapplyMergedBeanDefinitionPostPorcessors(1009)
  • 1011行打個斷點,而後F9一直跳到bpAutowiredAnnotationBeanPostProcessor
  • F7進入AutowiredAnnotationBeanPostProcessorpostProcessMergedBeanDefinition(231)
  • F8跟到232行,F7進入此方法,來到了AutowiredAnnotationBeanPostProcessorfindAutowiringMetadata(405)
  • F8跟到AutowiredAnnotationBeanPostProcessorbuildAutowiringMetadata(417)
  • F7進入此方法,來到了AutowiredAnnotationBeanPostProcessorbuildAutowiringMetadata(425)行,而後在433行打個斷點
  • F9一直跳到ComponetB或者valA
  • F7進入此方法,來到了AutowiredAnnotationBeanPostProcessorfindAutowiredAnnotation(480)
    跟到這咱們明白了,獲取到當前對象的全部field,而後找看看有沒有咱們「感興趣」的field,「感興趣」的註解是提早定義好的,放在autowiredAnnotationTypes中。
  • autowiredAnnotationTypes中都有哪些註解,咱們找到AutowiredAnnotationBeanPostProcessor的構造方法。
    咱們能夠看到「感興趣」的註解有@Autowired@Value@Inject,因此ComponetB或者valA會最終被放置到checkedElements中。
  • 接下來咱們回到InjectionMetadatainject(81),接着分析注入的過程
    經過上面分析咱們知道elementsToIterate一共有兩個屬性須要注入,分別是ComponetB或者valA
  • F7進入InjectionMetadatainject(90)行,來到AutowiredAnnotationBeanPostProcessorinject(570)
  • F8跟到583行,F7進入beanFactory.resolveDependency(583)行,來到了DefaultListableBeanFacotorydoResolveDependency(1062)
  • F7進入doResolveDependency(1062),來到DefaultListableBeanFacotory的1069行,接着在1135行打個斷點,F9跳到此位置
    咱們知道當前須要注入的一個屬性是ComponetB是個類,因此會走這進行初始化
  • F7進入descriptor.resolveCandidate(1135)行,來到DependencyDescriptorresolveCandidate(248)
  • F7進入beanFactory.getBean(251)行,一直跟到AbstractBeanFacotorydoGetBean(239)行,如下過程都是建立Componet B的過程,因此彙集在一塊兒,等Componet B中須要注入Component A時咱們在分開。
    到246行,其實就是到了上面循環依賴解決方案圖中第4步先從cache取出B的實例,顯然沒有那麼就接着按圖中走,第5步沒有B的示例去instantiate個示例。
    而後在317行打個斷點,F9跳到這
    F7進入,F8跟到doCreateBean(495)
    F7進入,F8跟到doCreateBean(572)
    咱們如今應該很清楚的知道,咱們到了解決循環依賴問題的第7步驟populate ComponentAF7進入populateBean方法。
  • populate ComponentA時,確定最終會去試圖建立ComponentA,也會回到AbstractBeanFacotrydoGetBean(239)
  • F7進入getSingleton(246)行,一直跟到DefaultSingletonBeanRegistrygetSingleton(176)行,F8跟到185行
    咱們到了解決循環依賴問題的第8步驟,從cache中取出Bean,並且這個Bean正好是咱們上文書所說的那樣,是實例化後的,未完成屬性注入和初始化的。
  • F8到最後doGetBean(392)
  • 接下來就是一直F8跟到AutowiredAnnotationBeanPostProcessor的inject(611)行
    這個時候就能夠發現,ComponetB建立完成,注入的是預先緩存好的示例化的ComponetA
  • 接下仍然一直F8跟回到AbstractAutowireCapableBeanFacotrydoCreateBean(573)
    到這能夠知道,ComponetB對於ComponetA已經注入完成,573行是執行ComponetB初始化操做,咱們主要看ComponetA的生命週期,因此能夠直接F9跳事後面過程回到ComponetApopulateBean
  • F9跳回到ComponetApopulateBean
    至此Component A完全完成初始化和屬性注入的過程,並且控制檯沒有任何輸出也符合咱們生命週期圖的預測。

初始化方法的前置處理

  • F7進入AbstractAutowireCapableBeanFacotrydoCreateBean(573)行來到initializeBean(1678)
  • F7進入invokeAwareMethods(1686)
    咱們的示例代碼ComponetA implements BeanFactoryAware, 因此這塊會回調setBeanFactory
  • F8執行完invokeAwareMethods
    符合咱們生命週期圖的預期,打印了$setBeanFactory()...
  • F8跳回到initializeBean(1691)
  • F7跟進去,到了AbstractAutowireCapableBeanFacotryapplyBeanPostProcessorsBeforeInitialization(411)
    跟到416行時候能夠發現兩個事情,第一個是這裏面一般執行的是*AwareProcessor且是在invokeAwareMethods後面執行的,符合咱們生命週期圖,第二個是ApplicationContextAwareProcessor是默認給咱們全部bean都建立的*AwareProcessor,咱們來看下這個ApplicationContextAwareProcessor是幹什麼的。
  • F7進入applyBeanPostProcessorsBeforeInitialization(416)行,來到了ApplicationContextAwareProcessorpostProcessBeforeInitialization(79)行,F8一直跟到invokeAwareInterfaces(96)
  • F7進入invokeAwareInterfaces(96)行,來到了invokeAwareInterfaces(102)
    原來還會幫咱們默認加ApplicationContextAwareProcessor,若是咱們實現了這些接口,它就會回調。咱們的示例代碼中ComponentA implements ApplicationContextAware,因此應該會執行ComponentA的setApplicationContext
  • F8跟到invokeAwareInterfaces(102)這個方法的最後,看下打印結果。
    符合咱們直接執行的*Aware接口,生命週期要早於*AwareProcessor

全部屬性注入結束

  • AbstractAutowireCapableBeanFacotryinitializeBean(1678)中的invokeInitMethods(1695)行,F7進入此方法,再F8跟到invokeInitMethods(1758)
  • F8執行afterPropertiesSet,由於ComponentA implements InitializingBean,因此控制檯確定會打印$afterPropertiesSet()...,以下圖所示:

開始init

  • F8下面的1767行就是執行,目標init方法

建立結束

  • F8跟回到AbstractAutowireCapableBeanFacotryapplyBeanPostProcessorsAfterInitialization(1703)
    這個過程就是調用初始化的後置處理器和前置處理器過程相似。
  • F8跟到AbstractAutowireCaplableBeanFacotrydoCreateBean(614)
    Bean註冊爲DisposableBean會隨着容器銷燬而銷燬。
  • F8一直跟回到DefaultSingletonBeanRegistrygetSingleton(248)
  • F7進入到此方法,看看建立Bean的最後一步
    將bean從二級緩存earlySingletonObjects轉存到以及緩存singletonObjects,以上過程咱們完成了Bean的整個建立過程,Spirng之因此處理的這麼「鬆散」(低耦合),其實就是在Bean整個生命週期過程當中,提供給用戶更好的拓展能力。結合本身的業務場景,靈活定製。

Bean的銷燬過程源碼分析

  • 先來了解下Runtime.getRuntime().addShutdownHook(Thread hook),寫段測試代碼。
    運行結果:
    當main方法執行結束,至關於System.exit(0)退出,就會回調是添加的Hook ThreadSpring也是這樣作的。
  • 理解Spring的鉤子線程,找到SpringApplicationrun(333)行,進入refreshContext(333)行,來到SpringApplication的415行,F7進入此方法,來到AbstractApplicationContextregisterShutdownHook(930)
    這段代碼與咱們的示例代碼相似,這個鉤子當系統退出時調用doClose()方法,這段小內容還要碎碎念個事兒,SpringBoot咱們知道他幫咱們自動裝配了不少配置,其實本質是在spring.factories定義好須要自動裝配的類,把定義好的類包裝成Spring須要的BeanDefinition,剩下的事兒就交給Spring就行了。固然SpringBoot還有本身完整的事件發佈體系(觀察者模式),讓整個SpringBoot的代碼結構看起來很是清晰。這些話雖然有些跑題,可是這就是我爲何搞SpringBoot starter卻進入了Spring的世界。
  • 一個不得不說的點
    不知道你有沒有想過一個問題,上面的main方法爲何執行完不退出。我相信你看完這篇文章(JVM退出問題),就會找到答案。
  • 回到正題,你知道有非後臺線程存在,只能使用System.exit(0)或者Runtime.getRuntime().exit(0),那麼咱們稍作修改代碼。
  • 在從新啓動以前,咱們在AbstractApplicationContextdoClose(1017)行打個斷點
  • 一直F7跟進到DefaultSingletonBeanRegistrydestroySingletons(491)行,在504行打個斷點。咱們上文說到ComponentA註冊爲DisposableBeanF9跳到ComponetAF7進入此方法,F8跟到543行,
  • F7進入此方法,F8跟到destroyBean(571)行並F8執行

總結

整篇文章不只是想從表面瞭解Bean的世界,而是要走進它的世界。我在寫這篇文章的時候,常常問本身這地方你懂了嗎?答案模棱兩可就是不懂,就須要去仔細品味源碼。讀源碼的做用究竟是什麼?它會讓你見識到一個頂級項目總體的架構,應用了哪些設計模式,一些問題的處理方式等等。這樣在平常工做中就能夠「借鑑」,讓本身的代碼是充滿着「溫度」的好代碼。

相關資料

相關文章
相關標籤/搜索