Spring IOC和Spring AOP的實(shí)現(xiàn)原理(源碼主線流程)

Spring IOC和Spring AOP的實(shí)現(xiàn)原理(源碼主線流程)

Spring IOC

一、容器初始化

? 容器的初始化首先是在對(duì)應(yīng)的構(gòu)造器中進(jìn)行三痰,在applicationContext的實(shí)現(xiàn)類構(gòu)造器中,首先對(duì)參數(shù)路徑中的${}進(jìn)行了處理窜管,用系統(tǒng)變量替換(setConfigLocations方法)然后調(diào)用refresh方法(這個(gè)就是最核心的容器初始化方法)酒觅。

1、Resource定位:

? 在refresh方法中調(diào)用obtainFreshBeanFactory方法告訴子類刷新beanfactory(其中是調(diào)用refreshBeanFactory刷新后getBeanFactory獲取刷新后的factory返回)微峰。在刷新過(guò)程refreshBeanFactory中如果factory已經(jīng)有了要消除再新建factory,其中l(wèi)oadBeanDefinitions是加載bean定義的方法抒钱。

? 在loadBeanDefinitions方法中創(chuàng)建了BeanDefinitionReader的實(shí)現(xiàn)類調(diào)用其loadBeanDefinitions方法(這個(gè)方法是重載方法蜓肆,參數(shù)有為Resource的也有為String路徑的,getConfigResources方法(默認(rèn)返回null谋币,子類重寫仗扬,如ClassPathXmlApplicationContext類)和getConfigLocations方法獲得Resource集合和資源路徑集合(一般一個(gè)為空,一般是將容器的參數(shù)path設(shè)定為configLocations蕾额,ClassPathXmlApplicationContext有一種構(gòu)造器是不設(shè)定configLocations而是直接用參數(shù)path生成ClassPathResource集合設(shè)定為configResources)分別進(jìn)行l(wèi)oad早芭,實(shí)際上以路徑為參數(shù)的重載方法在定位完Resource也會(huì)調(diào)用以resource為參數(shù)的loadBeanDefinitions來(lái)解析載入BeanDefinition,這個(gè)是第二步在下面介紹)诅蝶。

? 在BeanDefinitionReader的loadBeanDefinitions(path參數(shù))方法中根據(jù)ResourceLoader類型以兩種方式加載(如果是ant正則表達(dá)式方式的(如PathMatchingResourcePatternResolver)一個(gè)路徑定位多個(gè)resource或者默認(rèn)方式(applicationContext繼承的是DefaultResourceLoader實(shí)現(xiàn)方式)定位一個(gè)resource)退个,分別調(diào)用ResourceLoader的getResource(以/開(kāi)頭的構(gòu)建ClassPathContextResource,以classpath開(kāi)頭的去掉classpath構(gòu)建ClassPathResource调炬,如果都不是的嘗試構(gòu)建UrlResource,如果構(gòu)建失敗就調(diào)用getResourceByPath這個(gè)具體applicationContext實(shí)現(xiàn)類里重寫的方法構(gòu)建特定Resource语盈,如FileSystemXmlApplicationContext就是FileSystemResource)或getResources(PathMatchingResourcePatternResolver的正則方式這里不詳細(xì)描述)完成Resource定位。

2缰泡、從Resource中解析和載入BeanDefinition:

? 同樣在BeanDefinitionReader的loadBeanDefinitions中調(diào)用完resourceLoader的getResource獲取Resource后將resource作為參數(shù)調(diào)用自己(BeanDefinitionReader)的loadBeanDefinitions(是一個(gè)接口方法給子類實(shí)現(xiàn)刀荒,因?yàn)椴煌膔eader加載resource的方式不同)載入BeanDefinition。

? 例如XmlBeanDefinitionReader是對(duì)XML文件的IO操作,(將現(xiàn)在要處理的Resource加入當(dāng)前線程正在處理(ThreadLocal)的Resource集合中)首先從resource中拿出InputStream封裝成InputSource調(diào)用自身的doLoadBeanDefinitions方法棘钞。

? doLoadBeanDefinitions方法中調(diào)用doLoadDocument方法封裝成Document-----是用validationMode(默認(rèn)是自動(dòng)校驗(yàn)方式缠借,意思是如果沒(méi)有顯示定義校驗(yàn)的方式就用XSD方式)和DocumentLoader(XmlBeanDefinitionReader中默認(rèn)的是DefaultDocumentLoader)等參數(shù)調(diào)用DocumentLoader的loadDocument方法將Resource封裝成Document類(具體封裝方式不做介紹,有興趣的可以自己了解一下宜猜,用的是builder模式做的)調(diào)用registerBeanDefinitions方法解析載入bean泼返。

? registerBeanDefinitions方法是用BeanDefinitionDocumentReader的registerBeanDefinitions具體解析Document(樹(shù)形結(jié)構(gòu),從root(就是beans標(biāo)簽)開(kāi)始往下解析)中每個(gè)element各個(gè)標(biāo)簽的解析和載入宝恶。其中如果是bean標(biāo)簽BeabDefinitionParserDelegate的parseBeanDefinitionElement方法對(duì)XML元素的信息按照spring的bean的規(guī)則進(jìn)行解析(property的解析符隙,當(dāng)中value和ref解析方式不同趴捅,如果是value構(gòu)建TypedStringValue, 如果ref的話構(gòu)建RuntimeBeanReference,這個(gè)在之后依賴注入的時(shí)候用到霹疫,還有id拱绑,name,等屬性的解析)得到的BeanDefinition的封裝BeanDefinitionHolder(包括BeanDefinition,beanName(這里是標(biāo)識(shí)符的意思丽蝎,如果有id猎拨,id做標(biāo)識(shí)符,沒(méi)有id屠阻,name屬性中第一個(gè)別名做標(biāo)識(shí)符)和別名列表(name屬性中的內(nèi)容红省,如果沒(méi)有id,name中第一個(gè)不作為別名而是標(biāo)識(shí)符))來(lái)進(jìn)行下一步bean的注冊(cè)(BeanDefinitionReaderUtils.registerBeanDefinition)国觉。

? 其他如import吧恃,alias等標(biāo)簽自行看源碼理解。

3麻诀、BeanDefinition在IOC容器的注冊(cè)

? BeanDefinitionReaderUtils.registerBeanDefinition用BeanDefinitionRegistry(DefaultListableBeanFactory)的registerBeanDefinition方法注冊(cè)beanName和BeanDefinition(就是把beanName加入到一個(gè)已經(jīng)注冊(cè)的bean的beanName的Set中痕寓,然后put到beanName對(duì)應(yīng)BeanDefinition的map中,其中如果不允許覆蓋并且有同名beanName要報(bào)錯(cuò))蝇闭。再用BeanDefinitionRegistry的registerAlias方法注冊(cè)beanName和別名列表(put到一個(gè)beanName對(duì)應(yīng)alias的map中呻率,其中如果有alias跟beanName相同的要移除)。

二呻引、IOC容器依賴注入

1礼仗、getBean第一次調(diào)用lazy-init的bean

? 是以BeanFactory的getBean方法為入口觸發(fā)的(實(shí)現(xiàn)在AbstractBeanFactory實(shí)現(xiàn)類中)。如果是單例會(huì)緩存起來(lái)只加載一次逻悠,如果是FactoryBean這種特殊的bean會(huì)把這個(gè)bean的實(shí)例傳入getObjectForBeanInstance方法獲得FactoryBean產(chǎn)生的bean(調(diào)用FactoryBean的getObject方法元践,這就是自定義的FactoryBean要重寫的方法,AOP也是這個(gè)原理童谒,詳情見(jiàn)下方AOP分析)卢厂。在第一次載入時(shí)要先判斷這個(gè)BeanDefinition在當(dāng)前BeanFactory有沒(méi)有,沒(méi)有就從雙親BeanFactory中找惠啄,一直遞歸慎恒。

? 找到后要驗(yàn)證是否存在遞歸依賴,有則報(bào)錯(cuò)無(wú)則設(shè)置當(dāng)前bean依賴bean的依賴關(guān)系到兩個(gè)map中(一個(gè)是被依賴map撵渡,一個(gè)是依賴map)融柬,其中:? (1)如果是單例第一次載入就調(diào)用getSingleton方法(方法中回調(diào)了參數(shù)中ObjectFactory的getObject方法,這里重寫了這個(gè)方法調(diào)用createBean)獲得實(shí)例用getObjectForBeanInstance獲得FactoryBean產(chǎn)生的bean(如果它是FactoryBean的話)趋距。? (2)如果是prototype加載調(diào)用createBean后調(diào)用getObjectForBeanInstance粒氧。? (3)如果是其他scope類型:request、session和global session,這三種就用scope.get獲取實(shí)例(和getSingleton類似回調(diào)重寫的getObject也就是調(diào)用createBean)后調(diào)用getObjectForBeanInstance节腐。

? 最后如果getBean指定了requiredType要檢驗(yàn)獲取的bean能不能轉(zhuǎn)化成指定的類型不能的話就報(bào)錯(cuò)外盯。

? createBean方法就是生成bean的方法并對(duì)一些比如init-method屬性摘盆、后置處理器等一些初始化進(jìn)行了處理。方法中在實(shí)例化之前判斷是否有post-processor饱苟,如果有這樣的processor則短路指定bean的創(chuàng)建孩擂,直接返回一個(gè)proxy而不是指定的bean(這種processor可以指定生成一個(gè)其他類型的對(duì)象)沒(méi)有的話用doCreateBean創(chuàng)建bean返回。

? doCreateBean是用createBeanInstance生成BeanWrapper(包裝bean)之后用populateBean向其中的bean完成依賴bean的注入(autowire等)箱熬。

? createBeanInstance創(chuàng)建beanWrapper時(shí)分三類進(jìn)行處理:? (1)如果有工廠方法类垦,調(diào)用instantiateUsingFactoryMethod創(chuàng)建。? (2)如果是構(gòu)造器注入的方式調(diào)用autowireConstructor城须。? (3)簡(jiǎn)單方式調(diào)用instantiateBean蚤认。調(diào)用的是策略類(默認(rèn)SimpleInstantiationStrategy)的instantiate而其中又是通過(guò)bean方法是否有跟IOC容器同名的(會(huì)被覆蓋)來(lái)分兩類處理(沒(méi)同名方法的從BeanDefinition中拿出class直接用jdk的反射拿構(gòu)造器來(lái)newinstance一個(gè)實(shí)例,如果有同名的則是用CGLIB的方式來(lái)new一個(gè)實(shí)例)糕伐。

? populateBean為生成的bean依賴注入砰琢,先對(duì)非簡(jiǎn)單類型屬性有autowire的進(jìn)行處理,判斷這個(gè)屬性在之前解析載入beanDefinition時(shí)property里有沒(méi)有良瞧,有的話進(jìn)行g(shù)etBean初始化后放入PropertyValue集合中(這個(gè)就是propertyname和value的封裝)氯析,然后更新依賴map,再對(duì)非autowire的或一般屬性進(jìn)行注入莺褒,有要轉(zhuǎn)化的要經(jīng)過(guò)valueResolver的轉(zhuǎn)化(如果是RuntimeBeanReference之前載入時(shí)XML中配置是ref的就getBean(如果在雙親BeanFactory中就從雙親中取)獲得后也放到PropertyValue集合中,也要更新依賴map)雪情。最后再注入到bean中遵岩,這里說(shuō)的注入其實(shí)真實(shí)發(fā)生在最后的BeanWraper的setPropertyValue(propertyValue集合)方法,具體實(shí)現(xiàn)就是通過(guò)反射的方式獲得setter方法賦值巡通。

2尘执、lazy-init==false初始化(只對(duì)singleton,也是默認(rèn)方式)

? 在refresh方法中的finishBeanFactoryInitialization方法中進(jìn)行初始化(實(shí)際也是調(diào)用getBean方法)宴凉。

Spring AOP

? ProxyFacotryBean是FacotryBean的一種實(shí)現(xiàn)誊锭,F(xiàn)acotryBean要產(chǎn)生bean都要重寫getObject方法,而ProxyFacotryBean這里的這個(gè)getObject正是為代理做了準(zhǔn)備并返回代理對(duì)象。首先用initializeAdvisorChain(第一次去取代理對(duì)象時(shí)初始化一遍)初始化Advisor鏈后對(duì)于singleton和prototype進(jìn)行區(qū)分生成對(duì)應(yīng)的proxy弥锄。

1丧靡、初始化Advisor鏈

? initializeAdvisorChain初始化Advisor鏈?zhǔn)潜闅vProxyFacotryBean中配置的interceptorNames,如果結(jié)尾有通配符只能是ListableBeanFacotory來(lái)加載否則報(bào)錯(cuò)籽暇,去掉結(jié)尾通配符*后調(diào)用addGlobalAdvosor(這個(gè)是獲取ListableBeanFacotory的所有g(shù)lobalAdvisorNames和globalInterceptorNames温治,分別遍歷用getBean(beanName)獲取advice,把其中符合通配符格式的advice調(diào)用addAdvisorOnChainCreation封裝成advicsor后添加到Advisor鏈戒悠,如果結(jié)尾沒(méi)有通配符的情況下無(wú)論是singleton還是prototype在獲得advice后都要用addAdvisorOnChainCreation方法注冊(cè)到advisor鏈上熬荆。

? addAdvisorOnChainCreation用namedBeanToAdvisor方法把a(bǔ)dvice包裝成advisor,判斷如果advice是單例singleton的話是用AdvisorAdapterRegistry(默認(rèn)DefaultAdvisorAdapterRegistry單例)wrap方法判斷如果這個(gè)advice是MethodInterceptor或者AdvisorAdapterRegistry三種固定的adapter(before,afterreturning,throws)如果任一adapter支持的話(支持不支持就是在具體的adapter中判斷advice是不是這個(gè)adapter對(duì)應(yīng)具體的advice類的子類)就封裝成DefaultPointcutAdvisor返回绸狐。如果是prototype的話不獲取getBean卤恳,而是直接用name包裝成PrototypePlaceholderAdvisor累盗。

2、生成代理類

? 以singleton為例突琳,singleton代理的生成getSingletonInstance方法若债。是用AopProxyFactory(在構(gòu)造器中設(shè)定了默認(rèn)的DefaultAopProxyFactory)的createAopProxy方法根據(jù)ProxyFacotryBean中配置的target判斷是否是個(gè)接口(實(shí)際上不是這么簡(jiǎn)單的區(qū)分,具體看源碼了解)來(lái)創(chuàng)建不同AopProxy的子類(JdkDynamicAopProxy或者ObjenesisCglibAopProxy(CglibAopProxy的子類本今,增加了ObjenesisStd))調(diào)用他們各自的getProxy方法以不同的方式創(chuàng)建代理對(duì)象返回拆座。

? JdkDynamicAopProxy就是以動(dòng)態(tài)代理的方式構(gòu)建代理對(duì)象返回(具體動(dòng)態(tài)代理原理自行了解哦)。

? CglibAopProxy就是以Cglib的方式進(jìn)行代理冠息,Cglib采用了非常底層的字節(jié)碼技術(shù)挪凑,其原理是通過(guò)字節(jié)碼技術(shù)為一個(gè)類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用逛艰,順勢(shì)織入橫切邏輯躏碳。具體細(xì)節(jié)超出這文章的范圍拉。

? prototype代理的方式大致相同有些許的差別也不做介紹散怖,可以參考源碼菇绵。

3、調(diào)用時(shí)攔截

? 在調(diào)用目標(biāo)類的方法時(shí)因?yàn)榇碚{(diào)用的是invoke(jdk動(dòng)態(tài)代理)或者intercept(cglib)镇眷。在invoke(jdk動(dòng)態(tài)代理)或者intercept(cglib)中根據(jù)目標(biāo)類被調(diào)用方法分別處理咬最。

? 如果是hashCode和equals方法直接調(diào)用代理類中重寫了的hashCode和equals方法(具體參考源碼)。? 如果是Adviced接口中定義的方法(ProxyFactoryBean就是Adviced接口實(shí)現(xiàn)類)直接以反射的方式拿到method調(diào)用方法(AopUtils的invokeJoinpointUsingReflection方法)欠动。? 其他情況就是拿到攔截器鏈(只初始化一次永乌,每次調(diào)用時(shí)有個(gè)currentInterceptorIndex記錄處理到第幾個(gè)攔截器)調(diào)用攔截器的proceed方法前進(jìn)調(diào)用。

? proceed前進(jìn)調(diào)用不是遞歸具伍,其中用matcher進(jìn)行匹配翅雏,如果匹配上調(diào)用攔截器的invoke方法,匹配不上就直接繼續(xù)前進(jìn)調(diào)用人芽,攔截器interceptor的invoke方法就是通知方法(自己實(shí)現(xiàn)的如afterReturning等)對(duì)目標(biāo)方法(實(shí)際是攔截器鏈的proceed前進(jìn)調(diào)用)的具體加強(qiáng)望几,就是順序問(wèn)題等等。

? 直到攔截器鏈前進(jìn)到底調(diào)用target目標(biāo)類的對(duì)應(yīng)方法(jdk反射獲取method調(diào)用)萤厅。

? 初始化攔截器鏈?zhǔn)峭ㄟ^(guò)遍歷之前IOC容器getBean獲取到advisor鏈中的Advisor橄抹,通過(guò)AdvisorAdapterRegistry當(dāng)中設(shè)置的3種adapter(before,afterreturning,throws)的supportsAdvice判斷是否支持該advisor,如果支持就將advisor中的advice注冊(cè)成不同的AdviceInterceptor列表(一個(gè)advisor可以被多個(gè)adapter支持惕味,因?yàn)橹灰约簩懙耐ㄖ悓?shí)現(xiàn)多種advice接口即可)都加入到攔截器鏈害碾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赦拘,隨后出現(xiàn)的幾起案子慌随,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阁猜,死亡現(xiàn)場(chǎng)離奇詭異丸逸,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)剃袍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門黄刚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人民效,你說(shuō)我怎么就攤上這事憔维。” “怎么了畏邢?”我有些...
    開(kāi)封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵业扒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我舒萎,道長(zhǎng)程储,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任臂寝,我火速辦了婚禮章鲤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咆贬。我一直安慰自己败徊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布掏缎。 她就那樣靜靜地躺著皱蹦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪御毅。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天怜珍,我揣著相機(jī)與錄音端蛆,去河邊找鬼。 笑死酥泛,一個(gè)胖子當(dāng)著我的面吹牛今豆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播柔袁,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼呆躲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了捶索?” 一聲冷哼從身側(cè)響起插掂,我...
    開(kāi)封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后辅甥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體酝润,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年璃弄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了要销。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夏块,死狀恐怖疏咐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脐供,我是刑警寧澤浑塞,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站患民,受9級(jí)特大地震影響缩举,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匹颤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一仅孩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧印蓖,春花似錦辽慕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至他宛,卻和暖如春船侧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背厅各。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工镜撩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人队塘。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓袁梗,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親憔古。 傳聞我的和親對(duì)象是個(gè)殘疾皇子遮怜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355