Spring 的 bean 初始化過程

Spring 的 bean 初始化過程

前言

Spring 框架最基礎來說, 是一個 DI(依賴注入) 框架. 我們把我們的程序中用到的實體對象, 也就是我們常說的 bean, 放在 Spring 框架中維護, 當我們用到這些 bean 的時候, 可以在程序的任何地方來獲取.

Spring 封裝了復雜的邏輯, 留給了我們很多便利又簡單的方式來使用, 這正是我們使用 spring 框架的原因. 但是這也帶來了一些問題, 我們對內部幾乎一無所知, 當遇到復雜情況或者問題的時候, 往往無從下手. 而且, 隨著 spring 很多自動化配置的出現(xiàn), 和我們印象中的一些配置方式產生了沖突或重復, 即使在通常情況下能夠啟動運行, 我們也不敢說我們的配置絕對正確沒有問題. 這個時候, 我覺得我們應該多了解一些內部實現(xiàn), 這對于我們排查異常問題, 檢查項目正確性都很有幫助.

那么我們在使用時, 就涉及到兩個方面:

  1. 定義和注冊 bean
  2. 獲取 bean

我們從這兩個方面來講.

定義和注冊 bean

我們在使用 spring 的時候, 有這么幾種常用的定義 bean 的方式:

  1. xml 中來定義 bean 標簽
  2. 通過注解掃描, 比如 @Service 這種類.
  3. 定義 Configuration 類, 在類中提供 @Bean 的方法來定義.
  4. 使用純粹的 programmatically 的方式來定義和注冊.

前三種方式, 我們經常使用, 我們簡單看一下第四種方式是什么樣子的

// 新建一個工廠
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

// 新建一個 bean definition
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) BeanDefinitionBuilder
  .genericBeanDefinition(SomeService.class)
  .setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE)
  .getBeanDefinition();

// 注冊到工廠
factory.registerBeanDefinition("someService", beanDefinition);

// 自己定義一個 bean post processor. 作用是在 bean 初始化之后, 判斷這個 bean 如果實現(xiàn)了 ApplicationContextAware 接口, 就把 context 注冊進去..(先不要管 context 哪來的...例子嘛)
factory.addBeanPostProcessor(new BeanPostProcessor() {
  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof ApplicationContextAware) {
      GenericApplicationContext context = new GenericApplicationContext(factory);
      ((ApplicationContextAware) bean).setApplicationContext(context);
    }
    return bean;
  }
});

// 再注冊一個 bean post processor: AutowiredAnnotationBeanPostProcessor. 作用是搜索這個 bean 中的 @Autowired 注解, 生成注入依賴的信息.
AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
autowiredAnnotationBeanPostProcessor.setBeanFactory(factory);
factory.addBeanPostProcessor(autowiredAnnotationBeanPostProcessor);

// getBean() 時, 初始化
SomeService SomeService = factory.getBean("someService",SomeService.class);
SomeService.doSomething();

我們定義了一個 BeanDefinition, 注冊到了一個 BeanFactory 中; 然后從 BeanFactory 中獲得這個 bean, 調用這個 bean 當中的方法.

事實上, 定義 BeanDefinition 并注冊到 BeanFactory 的這個過程, 就是 bean 的定義和注冊 , 我們通常使用 spring 時并不是把一個 bean 的實例注冊, 而是一個 BeanDefinition.

而在第一次從 BeanFactory 當中 getBean() 時, 這個 bean 的實例才會真正生成. 這個部分我們在第二部分講.

BeanFactory

BeanFactory 是專門用來獲取 bean 的接口. 他又諸多的實現(xiàn), 其中 ConfigurableListableBeanFactory 是最常用的.

另外 Context 類也實現(xiàn)了 BeanFacotry 的接口, Context 是通過繼承和組合的方式對 BeanFactory 的一層封裝, 避免直接訪問到 BeanFactroy, 保證運行時不能隨意對 BeanFactroy進行修改. 除此之外還提供了更多的功能: 它不僅有 BeanFactory 管理 bean 池的功能, 還要負責環(huán)境配置管理, 生命周期管理, 復雜的初始化操作等.

BeanFactory 的接口成層次比較復雜, 我總結了下主要的有以下功能的接口

  1. BeanFactroy

    最基礎的 BeanFactory, 提供了根據 name , type 等獲取一個 bean 對象的能力

  2. ListableBeanFactory

    繼承 1, 額外提供了列出所有bean對象的能力

  3. HierarchialBeanFacotry

    繼承 1, 額外使了 factory 擁有一個 parent factory 的能力, 可以存在層級關系

  4. singletonbeanRegistry

    提供了單例對象緩存的能力

  5. AutowireCapableBeanFacory

    繼承 1, 提供了在創(chuàng)建對象時, 通過set方式給 bean 初始化 autowired 或者聲明了 dependon 的成員

  6. ConfigurableBeanFactory

    繼承 2 和 3, 給 factory 提供了很多配置的方法, 比如設置 BeanPostProcessor

  7. AliasRegistry

    支持了多個別名

最終經過各種組合, 我們一般用到實現(xiàn)類就兩個

  1. StaticListableBeanFactory

    實現(xiàn) ListableBeanFactory, 是最簡單的 Beanfactory. 直接注冊 bean 實例到 factory 中的 map 中, 獲取時直接從 map 中返回. 如果我們自己寫的簡單項目需要做基本的依賴注入管理, 可以使用這一個.

  2. DefaultListableBeanFactory

    我們平常項目用到的, 通過 BeanDefinition 定義類, 在第一次 get 時實例化, 并在實例化過程中同時實例化依賴類, 并做屬性填充, 并執(zhí)行一些初始化前后的 processor.

    我們平常從 xml 中, 代碼注解中定義的 bean, 通過 XmlBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 生成 bean definition 注冊到 DefaultListableBeanFactory中.

我們就從 DefaultListableBeanFactory 來講初始化的過程, 如果想通過這個類來創(chuàng)建 bean , 就需要通過 xml配置, bean配置的方式生成 bean definition, 這是個怎樣的東西呢.

BeanDefinition

完全的定義了一個 bean 的實例化, 初始化方式.

我們可能會關心的內部屬性

parentName: String // bean 的名字

beanClassName: String // bean 類型名字

scope: String // 作用域, 默認的有 singleton 和 proto, 前者是我們常用的單例, 后者是每次新建一個 bean. 子類可以實現(xiàn)更多的scope, 比如 session, request.

lazyInit: boolean // 是否需要懶加載, 通常是由有context的應用來控制.

dependsOn: String[] // 依賴的 bean name, 跟是否要注入沒關系, 有時是因為要控制 bean 的初始化順序

autowireCandidate: boolean // 是否可以被 autowire, 默認true

primary: boolean // autowire 的優(yōu)先級, 如果有多個 candidate, 會選 primary

factoryBeanName: String // 如果實例需要由工廠創(chuàng)建, 工廠 bean 的 name.

factoryMethodName: String // 工廠 bean 的 類型.

constructorArgumentValues: ConstructorArgumentValues // 構造函數參數表

propertyValues: MutablePropertyValues // 屬性表

singleton: boolean 

prototype: boolean

abstract: boolean

role: int // bean 的定義者, 0: 由application定義, 即用戶定義的. 1: support, 框架支持模塊. 2: infrastructure, 框架的基礎組件

description: String

resourceDescription: String // 定義來源

originatingBeanDefinition: BeanDefinition // 加工后的 BeanDefinition 保存原始 BeanDefinition.

beanClass: Class<?> // load bean 的 class 之后, 把 class 類型保存這里

abstract: boolean

autowireMode: int // 可以被autowire按類型, 按名稱等

resolvedAutowireMode: int // 解決自己autowire屬性時用按類型注入, 還是按構造參數傳入

dependencyCheck: int // 依賴 check 策略, 全都 check, 還是只 check 引用類型, 還是只 check 簡單類型, 或者不 check

initMethodName: String // 自定義的初始化方法

destroyMethodName: String // 銷毀方法

synthetic: boolean // true 表示不需要多余的 實例化,初始化前后處理.

resource: Resource // 這個 bean definition 的 resource 引用

Bean的實例化過程

大概流程及說明, 以 singleton scope 的 bean 為例

  1. get bean from singleton cache

    首先從 singleton cache 獲取對象, 如果有, 說明初始化過, 直接返回, 如果沒有, 繼續(xù)

    1. if return bean, return

    2. if null, go on

  2. create merged definition

    找到 bean 的 definition (bd), 然后生成 merged bean definition (mbd). 這個主要是因為 bd 可以有 parent, 這個步驟的作用就是把 bd 和 它的 parent bd (如果有的話), 進行merge, 生成 mbd, 之后就要根據這個 mbd 來實例化和初始化 bean.

  3. check bean definition

    檢查 mbd , 把 mbd 中 dependsOn 的 bean 都先初始化.

  4. get singleton, if not found, then create bean:

    再次從 singleton cache 中獲取 bean, 跟第 1 步方法參數不同, 這一步如果沒有, 則會真正 create bean

    1. resolve class

      查找并 load 這個 bean 的 class

    2. resolveBeforeInstantiation

      在真正的實例化之前進行一次預先操作, 目的是給用戶一個機會來進行非正常的實例化, 用戶注入的 InstantiationAwareBeanPostProcessor 子類, 可以做一些 proxy, mock, 來取代真實的實例化返回, 如果沒有產生 bean, 則繼續(xù)往下走去正常的實例化階段.

      1. InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()
        1. if return bean, BeanPostProcessor.postProcessAfterInitialization(), then return bean.
        2. if return null, go on
    3. do create bean

      1. create instance

        真正的實例化, 調用 mbd 中定義的 factoryMethod, 或者類的構造方法, 來生成對象.

      2. MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition // 比如autowire等 注解注入 mbd

        對 mbd 進行初始化前的操作, 比如掃描 class 定義, 找到 @autowired 注解來生成注入信息存放在 mbd 中.

      3. add early singleton cache

        這一步主要是為了解決循環(huán)引用, 再把未初始化的 bean 的 reference 提供出來.

      4. populate bean

        填充屬性階段, properties values (pvs), 這一大步驟中的前三小步都是在構造 pvs, 并在最后一步 apply 進去

        1. InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()

        2. autowire

          把能夠 set 的的屬性寫入 pvs

        3. InstantiationAwareBeanPostProcessor.postProcessPropertyValues()

          把一些特殊屬性寫入, 比如沒有 set 的 autowired 屬性, 一些 @Value 的屬性

        4. apply property values

      5. initialize bean

        實例化完畢之后, 進行初始化.

        1. aware beanName, classLoader, beanFactory

          如果有需要, 把這三個特殊的對象放到 bean 里

        2. BeanPostProcessor.postProcessBeforeInitialization()

        3. init

          真正的初始化操作

          1. 如果 bean 是 InitializingBean, afterPropertiesSet()
          2. 調用自定義 init
        4. BeanPostProcessor.postProcessAfterInitialization()

      6. register disposable bean

我們在這個過程中能做的

  1. InstantiationAwareBeanPostProcessor

    實現(xiàn)這個接口的類, 可以在類的實例化階段, 做一些操作.

    這個接口有三個方法:

    1. Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)

      在實例化之前進行一些操作, 目的是提供一個機會由這個方法來實例化對象. 比如 proxy, mock

    2. boolean postProcessAfterInstantiation(Object bean, String beanName)

      bean set屬性前執(zhí)行, 如果返回false, 將會跳過屬性處理. 這個一般都是返回true.

    3. PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)

      屬性構造完成, apply 到對象前執(zhí)行, 傳入 pvs 并返回 pvs, 有機會對 pvs 進行處理. 比如 required 檢測在這一步.

  2. BeanPostProcessor

    這個接口有兩個方法:

    1. Object postProcessBeforeInitialization(Object bean, String beanName)

      在初始化之前 傳入 bean 并返回 bean, 但返回的 bean 可以被包裝或者替換

    2. Object postProcessAfterInitialization(Object bean, String beanName)

      同上, 也可以做一些 bean 初始化完成后的回調. 比如可以監(jiān)聽每一個 bean 的初始化時機.

交流

歡迎加入群 661035226速兔,gradle贩毕,spring亏镰,activiti 交流

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末荐操,一起剝皮案震驚了整個濱河市资昧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌膊存,老刑警劉巖遥倦,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碉考,居然都是意外死亡塌计,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門侯谁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夺荒,“玉大人,你說我怎么就攤上這事良蒸〖级螅” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵嫩痰,是天一觀的道長剿吻。 經常有香客問我,道長串纺,這世上最難降的妖魔是什么丽旅? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮纺棺,結果婚禮上榄笙,老公的妹妹穿的比我還像新娘。我一直安慰自己祷蝌,他們只是感情好茅撞,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巨朦,像睡著了一般米丘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糊啡,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天拄查,我揣著相機與錄音,去河邊找鬼棚蓄。 笑死堕扶,一個胖子當著我的面吹牛碍脏,可吹牛的內容都是我干的。 我是一名探鬼主播稍算,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼典尾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了邪蛔?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤扎狱,失蹤者是張志新(化名)和其女友劉穎侧到,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體淤击,經...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡匠抗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了污抬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汞贸。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖印机,靈堂內的尸體忽然破棺而出矢腻,到底是詐尸還是另有隱情,我是刑警寧澤射赛,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布多柑,位于F島的核電站,受9級特大地震影響楣责,放射性物質發(fā)生泄漏竣灌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一秆麸、第九天 我趴在偏房一處隱蔽的房頂上張望初嘹。 院中可真熱鬧,春花似錦沮趣、人聲如沸屯烦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漫贞。三九已至,卻和暖如春育叁,著一層夾襖步出監(jiān)牢的瞬間迅脐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工豪嗽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谴蔑,地道東北人豌骏。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像隐锭,于是被迫代替她去往敵國和親窃躲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內容

  • 文章作者:Tyan博客:noahsnail.com 3.5 Bean scopes When you create...
    SnailTyan閱讀 1,879評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理钦睡,服務發(fā)現(xiàn)蒂窒,斷路器,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 11月6號荞怒,周一 今晚無月
    二月謹閱讀 137評論 0 0
  • “有錢了不起啊”這樣一個簡單的一句話也能讓笑來老師引發(fā)深入的思考洒琢。“有XX了不起啊”是一個找借口的萬能句型褐桌,當我們...
    lanlana閱讀 288評論 0 0
  • 中秋節(jié)那天衰抑,我跟家人一起去了經常去的那家飯店。那家飯店是縣城里做得比較不錯的了荧嵌,我也比較喜歡那里的飯菜呛踊,經常在那家...
    下筆如神閱讀 1,748評論 0 0