Spring解析之IoC:<bean>解析及Bean的注冊

前言
上一篇文章分析了Bean工廠的創(chuàng)建,其真正的實現(xiàn)類和核心為DefaultListableBeanFactory谬莹,XML配置文件是封裝成了Resource檩奠,由XmlBeanDefinitionReader進行了加載约素,最后在BeanDefinitionParserDelegate解析類中將XML根元素<beans>中的屬性進行了處理。現(xiàn)在所有的準備工作完成笆凌,正式進入我們嚴格意義上的“有效”標簽的解析

前面說到,默認情況下DefaultBeanDefinitionDocumentReader對于XML的前處理和后處理都為空實現(xiàn)士葫,真正的處理邏輯在parseBeanDefinitions(Element, DefaultBeanDefinitionDocumentReader)

圖1. DefaultBeanDefinitionDocumentReader的parseBeanDefinitions(Element, BeanDefinitionParserDelegate)

兩次判斷紅框內(nèi)代碼判斷根元素和其下所有子元素是否處在默認名稱空間定義內(nèi)乞而,來看一下怎么判斷的
圖2. BeanDefinitionParserDelegate中判斷是否默認名稱空間邏輯

根據(jù)獲得的節(jié)點名稱空間判斷如果為空或者等于常量BEANS_NAMESPACE_URI即為默認名稱空間,該常量的值為http://www.springframework.org/schema/beans慢显,正是XML最基礎(chǔ)的名稱空間爪模,回到圖1,根據(jù)當前節(jié)點是否屬于默認名稱空間下分別調(diào)用紅線處兩個不同的方法荚藻,目前我們只分析默認名稱空間下標簽解析流程
圖3. 解析默認標簽元素

我們根據(jù)判斷的四個常量可知屋灌,這里的默認標簽元素分別為<import><alias>应狱、<bean><beans>
圖4. DefaultBeanDefinitionDocumentReader的processBeanDefinition(Element, BeanDefinitionParserDelegate)

處理<bean>大體可分為兩步:1. 解析<bean>共郭;2. 注冊bean實例,與本文名稱契合的這兩部分并不是為了讓大家更好的理解Spring運行流程特意杜撰的疾呻,而是Spring內(nèi)部確實是這么劃分的除嘹,上圖中的兩行代碼就對應(yīng)了這兩個部分,我們先來第一部分
<bean>解析流程經(jīng)過一段方法調(diào)用走到解析委派類的parseBeanDefinitionElement(Element, BeanDefinition)
圖5. BeanDefinitionParserDelegate的parseBeanDefinitionElement(Element, BeanDefinition)

標注1解析<bean>的id和name屬性岸蜗,其中name屬性可以配置多個尉咕,會將多個name轉(zhuǎn)成alias數(shù)組。標注2判斷id屬性是否存在璃岳,如果存在賦值給beanName年缎,不存在用別名數(shù)組中第一個別名作為beanName的值,標注3方法判斷beanName或者別名數(shù)組中的別名沒有使用過铃慷,也就是說一個XML配置文件中同id或者同名不能重復(fù)注冊
圖6. BeanDefinitionParserDelegate的checkNameUniqueness(String, List<String>, Element)

也許有人產(chǎn)生疑問了单芜,在第一篇文章中命名說到可以存在同名的<bean>,這里怎么又不可以了呢枚冗?其實答案在保存使用過的名稱集合usedNames中寫的很清楚缓溅,在同一層次<beans>下的所有<bean>只能存在唯一不重復(fù)的ids/names,但是如果有多個XML配置文件赁温,兩個相同的ids/names出現(xiàn)在不同的配置文件中坛怪,這種情況是可以被允許的。初始化加載時usedNames內(nèi)沒有內(nèi)容股囊,每一次<bean>的解析都會向內(nèi)存儲對應(yīng)的id/name和所有的alias袜匿。回到圖5標注4創(chuàng)建出bean的實例
圖7. BeanDefinitionParserDelegate的parseBeanDefinitionElement(Element, String, BeanDefinition)

標注1將當前<bean>壓入成員變量parseState內(nèi)部棧的棧頂稚疹,該對象用于記錄解析到的一些重要標簽或者屬性對象居灯,當解析發(fā)生錯誤時就可以知道到底解析到哪個對象出現(xiàn)了問題祭务。標注2很明顯是解析classparent屬性,其中的最后一句會生成AbstractBeanDefinition的子類GenericBeanDefinition怪嫌,其中根據(jù)是否設(shè)置bean的類加載器決定該類中的變量beanClass保存的是className還是類的實例义锥。因為本文的例子中剛剛進入到解析XML和創(chuàng)建對應(yīng)BeanFactory的流程,此時類加載器為null岩灭,為了證明這點我特意debug截圖為證
圖8. 創(chuàng)建GenericBeanDefinition時debug圖

從上圖可以很清楚的看到此時classLoader = null拌倍,那么創(chuàng)建的GenericBeanDefinition中保存真實對象的變量beanClass保存的實際上是類的名稱而不是類的實例,記住這點非常關(guān)鍵否則下面沒法分析
圖7標注3parseBeanDefinitionAttributes(Element, String, BeanDefinition, AbstractBeanDefintion)根據(jù)spring不同的版本解析諸如scope噪径、abstract柱恤、lazy-init等屬性。標注4三個方法解析<meta>找爱、<lookup-method><replaced-method>子標簽梗顺,其中<meta>用于向BeanDefinition中設(shè)置任意key-value對;<lookup-method>通常用于設(shè)置方法返回bean的類型车摄;<replaced-method>使得一個類的某個方法可以被實現(xiàn)MethodReplacer接口的類的reimplement(Object, Method, Object[])代替寺谤。標注5中第一句就是大家熟悉的通過構(gòu)造器實現(xiàn)注入的解析方式,對應(yīng)的標簽為<constructor-arg>练般,代碼清單1

public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
  //    (1)
  String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
  String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
  String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
  //    (2)
  if (StringUtils.hasLength(indexAttr)) {
    try {
      int index = Integer.parseInt(indexAttr);
      if (index < 0) {
        error("'index' cannot be lower than 0", ele);
      }
      else {
        try {
          //    (3)
          this.parseState.push(new ConstructorArgumentEntry(index));
          //    (4)
          Object value = parsePropertyValue(ele, bd, null);
          ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
          if (StringUtils.hasLength(typeAttr)) {
            valueHolder.setType(typeAttr);
          }
          if (StringUtils.hasLength(nameAttr)) {
            valueHolder.setName(nameAttr);
          }
          valueHolder.setSource(extractSource(ele));
          //      (5)
          if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
            error("Ambiguous constructor-arg entries for index " + index, ele);
          }
          else {
            bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
          }
        }
        finally {
          this.parseState.pop();
        }
      }
    }
    catch (NumberFormatException ex) {
      error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
    }
  }
  else {
    try {
      //      (6)
      this.parseState.push(new ConstructorArgumentEntry());
      Object value = parsePropertyValue(ele, bd, null);
      ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
      if (StringUtils.hasLength(typeAttr)) {
        valueHolder.setType(typeAttr);
      }
      if (StringUtils.hasLength(nameAttr)) {
        valueHolder.setName(nameAttr);
      }
      valueHolder.setSource(extractSource(ele));
      bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
    }
    finally {
      this.parseState.pop();
    }
  }
}

我們可以根據(jù)參數(shù)的三種屬性之一進行注入矗漾,第一種為下標,初始從0開始薄料;第二種為根據(jù)參數(shù)類型敞贡,比如是java.lang.String還是java.lang.Integer,注意摄职,對于基本數(shù)據(jù)類型是無法通過構(gòu)造器注入的誊役,需要轉(zhuǎn)成基本數(shù)據(jù)類型的包裝數(shù)據(jù)類型;第三種是通過參數(shù)的名稱注入谷市,這種沒什么好說的蛔垢。只有采用第一種基于下標的注入方式才會進入到if中,標注3首先構(gòu)建出一個Entry的實現(xiàn)類ConstructorArgumentEntry迫悠,并壓入棧頂鹏漆,這里的Entry是變量parseState的一個內(nèi)部標記接口,只有實現(xiàn)該接口的對象才能被放入parseState內(nèi)部的棧內(nèi)
標注4主要做了兩件事:1. 處理<constructor-arg>中對應(yīng)的refvalue等屬性创泄;2. 解析<constructor-arg>內(nèi)部的子標簽艺玲,具體流程我們來看一下

圖9. BeanDefinitionParserDelegate的parsePropertyValue(Element, BeanDefinition, String)

標注1解析出類型為Element,標簽名不為description也不為meta的子標簽鞠抑,引用指向subElement饭聚。標注2解析<constructor-arg>refvalue屬性,確保兩個屬性不能同時出現(xiàn)搁拙。標注3得到ref的值封裝成RuntimeBeanReference對象返回秒梳。標注4得到value的值封裝成TypeStringValue返回法绵。如果上面解析到<constructor-arg>的子標簽元素就會調(diào)用標注5的方法,該方法內(nèi)包含一些大家可能還比較熟悉的標簽酪碘,比如<list>朋譬、<map>等,感興趣的讀者可以繼續(xù)往下深入兴垦,這里就不做分析了
回到代碼清單1看標注5處此熬,上面說的在這個if中都是存在index屬性的,這里就是判斷解析到的index值是否已經(jīng)存在滑进,在ConstructorArgumentValues對象中持有一個Map<Integer, ValueHolder>集合,其中key即為下標募谎,value為下標對應(yīng)標簽值封裝的對象扶关,如果通過驗證就往map中塞入對應(yīng)的鍵值對。標注6處對應(yīng)著else的部分表示在沒有index屬性時解析<constructor-arg>的過程数冬,同樣調(diào)用parsePropertyValue(Element, BeanDefinition, String)方法节槐,封裝ValueHolder實例,根據(jù)屬性類型的不同進行不同的設(shè)置拐纱,之前存在index屬性的標簽對象存放在map中铜异,而不存在index屬性的標簽對象放在鏈表中
繞了一個大圈子終于分析完<constructor-arg>及其屬性、子標簽的解析流程秸架,回到圖7看標注5的第二句parsePropertyElements(Element, BeanDefinition)揍庄,該方法和解析其他標簽一開始的思路一致,都是遍歷所有子標簽东抹,判斷標簽名稱是否為property蚂子,是則進入parsePropertyElement(Element, BeanDefinition)
圖10. BeanDefinitionParserDelegate的parsePropertyElement(Element, BeanDefinition)

首先獲得<Property>必填屬性name的值,如若沒有記錄錯誤日志并拋出異常缭黔,對于一個BeanDefinition來說食茎,對象內(nèi)存在一個MutablePropertyValues對象,該對象中維護了一個List<PropertyValue>集合馏谨,該集合就保存了所有的<property>name-value/ref對别渔,既然如此,在解析的過程中必然要判斷集合中是否已經(jīng)存在同名稱的PropertyValue惧互,如果存在自然也會報錯哎媚,不存在就需要真正解析<property>,而解析的過程和解析<constructor-arg>調(diào)用的方法一樣壹哺,見圖9抄伍,與前者解析唯一不同的地方在于第三個參數(shù),對于<constructor-arg>來說propertyNamenull管宵,而<property>自然就是屬性name的值了截珍,解析并創(chuàng)建PropertyValue對象后攀甚,同樣要使用parseMetaElement(Element, BeanMetadataAttributeAccessor)解析內(nèi)嵌的<meta>標簽,最后塞入List<PropertyValue>
圖7標注5中最后一句用來解析<qualifer>岗喉,在注解注入大行其道的編程界秋度,相信也很少人使用這種方式進行注入操作,在這里就不展開分析了钱床,關(guān)于幾種注解注入的方式的源碼解析后面會有單獨文章分析荚斯。至此我們終于又回到了圖5解析<bean>大綱流程,看一看標注4和標注5之間的代碼做了什么查牌,如果我們在配置<bean>時既沒有填寫id事期,也沒有填寫name,Spring會為我們生成一個name纸颜,由于containingBean在這里為null兽泣,因此最終的流程會走到else內(nèi),最終使用BeanDefinitionReaderUtils來生成beanName
圖11. BeanDefinitionReaderUtils的generateBeanName(BeanDefinition, BeanDefinitionRegistry, boolean)

首先獲得BeanDefinition中的class name胁孙,因為上面的步驟已經(jīng)解析過<bean class="">唠倦,此時必然有值,再有第三個參數(shù)isInnerBean = false(至于為什么請讀者順著流程走一次便知)涮较,因此最后形成返回值的公式即為while中的generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;稠鼻,其中的常量值為#,舉個例子狂票,如果<bean class = "com.xiaomi.Student/>"候齿,那么最終生成的beanName即為com.xiaomi.Student#0。將解析的所有這一些按圖5標注5代碼封裝成BeanDefinitionHolder后標志著<bean>全部解析完
下面我們開始Bean實例的注冊流程進入圖3第二個下劃線代碼內(nèi)闺属,在正式分析之前我們需要先看幾個重要的成員變量毛肋,第一篇文章中曾經(jīng)說過BeanFactory的核心實現(xiàn)類為DefaultListableBeanFactory,在該類中有幾個非常重要的成員變量
圖12. DefaultListableBeanFactory中成員變量

先來說兩個現(xiàn)在要用到的屋剑,beanDefinitionMapbeanDefinitionNames润匙,前者保存有id/beanName-bean實例鍵值對,后者保存所有的beanName
圖13. DefaultListableBeanFactory的registerBeanDefinition(String, BeanDefinition)

注冊bean的邏輯遠遠沒有解析<bean>的復(fù)雜唉匾,上圖中的紅框內(nèi)即為核心代碼孕讳。首先從beanDefinitionMap根據(jù)beanName查找是否存在對應(yīng)的實例,如果存在判斷是否開啟了實例覆蓋標識巍膘,沒有開啟拋出異常厂财,再將beanName和對應(yīng)實例放入beanDefinitionMap中,最后一句的作用是清除所有beanName所屬實例及其衍生類的本地緩存信息

后記
即便有意隱去的非重點流程峡懈,<bean>解析及注冊的調(diào)用關(guān)系依然很深璃饱,我們宏觀上只需要記住經(jīng)過本文對應(yīng)的處理,Spring解析了所有XML配置文件肪康,生成的bean工廠荚恶,但此時bean工廠中<bean>對應(yīng)的實例并沒有真正創(chuàng)建撩穿。回想Spring解析之IoC:XML配置文件的加載及BeanFactory的創(chuàng)建中圖5谒撼,Spring初始化的總綱食寡,現(xiàn)在也才走到第二步,漫漫長路剛剛開始廓潜,繼續(xù)努力吧抵皱!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辩蛋,隨后出現(xiàn)的幾起案子呻畸,更是在濱河造成了極大的恐慌,老刑警劉巖悼院,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件擂错,死亡現(xiàn)場離奇詭異,居然都是意外死亡樱蛤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門剑鞍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昨凡,“玉大人,你說我怎么就攤上這事蚁署”慵梗” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵光戈,是天一觀的道長哪痰。 經(jīng)常有香客問我,道長久妆,這世上最難降的妖魔是什么晌杰? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮筷弦,結(jié)果婚禮上肋演,老公的妹妹穿的比我還像新娘。我一直安慰自己烂琴,他們只是感情好爹殊,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奸绷,像睡著了一般梗夸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上号醉,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天反症,我揣著相機與錄音辛块,去河邊找鬼。 笑死惰帽,一個胖子當著我的面吹牛憨降,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播该酗,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼授药,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了呜魄?” 一聲冷哼從身側(cè)響起悔叽,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爵嗅,沒想到半個月后娇澎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡睹晒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年趟庄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伪很。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡戚啥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锉试,到底是詐尸還是另有隱情猫十,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布呆盖,位于F島的核電站拖云,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏应又。R本人自食惡果不足惜宙项,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望株扛。 院中可真熱鬧杉允,春花似錦、人聲如沸席里。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奖磁。三九已至改基,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背秕狰。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工稠腊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸣哀。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓架忌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親我衬。 傳聞我的和親對象是個殘疾皇子叹放,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)挠羔,斷路器井仰,智...
    卡卡羅2017閱讀 134,707評論 18 139
  • 文章作者:Tyan博客:noahsnail.com 3.4 Dependencies A typical ente...
    SnailTyan閱讀 4,172評論 2 7
  • 文章作者:Tyan博客:noahsnail.com 3.4 依賴 標準企業(yè)應(yīng)用不會由一個對象(或Spring用語中...
    SnailTyan閱讀 1,189評論 0 1
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,859評論 6 342
  • 快樂太難,太心酸破加,漸漸發(fā)現(xiàn)孤單俱恶,原來,愛情真那么難范舀。 猶記得合是,那是高中的一天,他從她的教室經(jīng)過锭环,看見他的剎那聪全,好像...
    一顆小星星HF閱讀 472評論 0 0