前言
說是Java web,Spring已經(jīng)成為了事實標(biāo)準(zhǔn)线衫,Spring原理的深入學(xué)習(xí),無論是在工作中惑折,還是在面試中授账,都尤為重要。
Spring的兩個核心概念是IOC(控制反轉(zhuǎn))和AOP(面向切面編程)惨驶。想了解Spring的工作原理白热,毫無疑問,首先要從這兩個概念的Spring實現(xiàn)入手粗卜。但是Spring源碼浩如煙海屋确,里面摻雜了太多的實現(xiàn)細(xì)節(jié),入門可謂極其困難续扔。當(dāng)我正苦于難以入門時攻臀,好友介紹了tiny-spring這個開源項目,這個項目用了不到千行的代碼纱昧,就將Spring的IOC刨啸、AOP的核心流程實現(xiàn)完畢,真是居家旅行砌些、吹逼面試之必備呀呜投!
廢話少說,我們開始吧存璃!
目錄結(jié)構(gòu)
在github上clone下項目來之后仑荐,我們關(guān)注src文件夾,其余的是一些愛好者提的注釋PR纵东,恰巧被作者merge了粘招,不必理會。目錄結(jié)構(gòu)是這樣的:
-
aop
包偎球,顧名思義洒扎,實現(xiàn)了Spring的AOP功能,可以通過bean的自動AOP切入衰絮,文件稍多袍冷,暫時先不展開。 -
bean.factory
包猫牡,通過BeanFactory
胡诗、AbstractBeanFactory
、AutowireCapableBeanFactory
三個類,實現(xiàn)了BeanFactory
的核心功能煌恢,詳情稍后講解骇陈。 -
bean.io
包定義了資源加載相關(guān)的抽象概念,這里的資源包括xml配置文件等瑰抵。 -
bean.xml
包中只包含一個類:XmlBeanDefinitionReader
你雌,主要負(fù)責(zé)在xml配置文件中讀取bean定義。 -
bean
包其他類二汛,定義了BeanDefinition等核心概念婿崭,詳情后講。 -
context
包定義了ApplicationContext
的核心概念习贫。 -
BeanReference
指的是引用類型的Bean逛球,而不是實體類。
IOC--浮沙筑臺之根基
IOC(控制翻轉(zhuǎn))是一種編程范式苫昌,可以在一定程度上解決復(fù)雜系統(tǒng)對象耦合度太高的問題,并不是Spring的專利幸海。IOC最常見的方式是DI(依賴注入)祟身,可以通過一個容器,將Bean維護(hù)起來物独,方便在其他地方直接使用袜硫,而不是重新new〉猜ǎ可以說婉陷,IOC是Spring最基本的概念,沒有IOC就沒有Spring官研。
為什么DI可以起到解耦的作用秽澳?
一個軟件系統(tǒng)包含了大量的對象,每個對象之間都有依賴關(guān)系戏羽,在普通的軟件編寫過程中担神,對象的定義分布在各個文件之中,對象之間的依賴始花,只能通過類的構(gòu)造器傳參妄讯,方法傳參的形式來完成。當(dāng)工程變大之后酷宵,復(fù)雜的邏輯會讓對象之間的依賴梳理變得異常困難亥贸。
在Spring IOC中,一般情況浇垦,我們可以在XML文件之中炕置,統(tǒng)一的編寫bean的定義,bean與bean之間的依賴關(guān)系,從而增加邏輯的清晰度讹俊。而且垦沉,bean的創(chuàng)建是由Spring來完成的,不需要編程人員關(guān)心仍劈,編程人員只需要將精力放到業(yè)務(wù)的邏輯上面厕倍,減輕了思維的負(fù)擔(dān)。
在tiny-spring
里面贩疙,整個beans
和context
包都是用來實現(xiàn)IOC
的讹弯。
beans
包實現(xiàn)的核心關(guān)注點是BeanFactory
,BeanFactory
也叫作Bean容器这溅,顧名思義组民,是用來盛放、管理bean的悲靴。
context
包實現(xiàn)的核心關(guān)注是ApplicationContext
臭胜,ApplicationContext
也是用來獲取Bean的,但是它更高層癞尚,它的面向用戶是Spring的使用者耸三,而BeanFactory面向的用戶更多是Spring開發(fā)者。BeanFactory定義了Bean初始化的流程浇揩,ApplicationContext定義了從XML讀取仪壮,到Bean初始化,再到使用的過程胳徽。
Bean在哪定義积锅?
剛才有說到,Spring通常通過xml文件养盗,來統(tǒng)一的描述bean缚陷,bean與bean的依賴關(guān)系。所以說爪瓜,bean的定義表述蹬跃,發(fā)生在xml配置文件之中。這個XML文件就是我們需要讀取的資源文件铆铆。
因此蝶缀,首要任務(wù)就是研究與讀取XML資源文件相關(guān)的類。
bean.io
中存放的是讀取資源文件的抽象概念薄货。其中包含了三個類或者接口:
-
Resource
接口翁都,這個接口只有一個方法,InputStream getInputStream() throws IOException;
谅猾。實現(xiàn)這個接口的類就是一個抽象的資源柄慰,可以獲取這個資源的輸入流鳍悠,從而獲取其中的內(nèi)容。 -
UrlResource
類坐搔,這個類實現(xiàn)了Resource
接口藏研,通過構(gòu)造器傳入一個url地址,代表的是這個url所對應(yīng)的文件概行。 -
ResourceLoader
類蠢挡,只有一個方法,public Resource getResource(String location)
凳忙。輸入url的文件地址(并不是真正的URL格式地址)业踏,來獲取Resource。
通過分析上面三個類涧卵、接口勤家,我們知道,這個包完成了一個任務(wù):通過ResourceLoader
這個類柳恐,獲取某一個地址的Resource
伐脖,從而獲取這個文件的輸入流。因為使用了Resource概念胎撤,可以使用網(wǎng)絡(luò)文件或者本地文件晓殊。
Bean如何定義?
-
BeanDefinition
是Bean定義的核心概念伤提,BeanDefinition
包含了:bean的對象、bean的類類型认烁、bean的名字肿男,bean的所有屬性。這個類對bean的基本信息做好一個包裝却嗡。 -
BeanDefinitionReader
接口舶沛,只有一個方法:void loadBeanDefinitions(String location) throws Exception;
,實現(xiàn)這個接口的類窗价,具有將某一個文件中的所有bean定義載入的功能如庭。所以BeanDefinitionReader
定義了,在哪載入bean定義撼港,至于載入到哪里坪它、如何載入,稍后看具體實現(xiàn)帝牡。 -
AbstractBeanDefinitionReader
抽象類往毡,上面剛說了實現(xiàn)了BeanDefinitionReader
接口的類,具有將某一個文件中描述的bean定義載入的功能靶溜,AbstractBeanDefinitionReader
就實現(xiàn)了這樣一個抽象功能开瞭。它的作用就是定義懒震,載入到哪和如何載入的問題。在這個類里面嗤详,有兩個屬性:Map<String,BeanDefinition> registry;
和ResourceLoader resourceLoader;
个扰。registry
是一個注冊表,他保存的就是所有的Bean定義葱色,Map結(jié)構(gòu)递宅,key是bean的名字,value就是BeanDefinition冬筒。resourceLoader
描述了如何載入恐锣。 -
XmlBeanDefinitionReader
這是beans.xml
包里面的唯一一個方法,也是最重要的方法之一舞痰。它繼承了AbstractBeanDefinitionReader
土榴,實現(xiàn)了所有方法,解決了bean定義中:在哪載入响牛、如何載入玷禽、載入到哪的三個大問題。這個類面向用戶的方法有兩個呀打,一個是loadBeanDefinitions
矢赁,毫無疑問,這個是必須的贬丛。另一個是getRegistry
用來獲取bean注冊表撩银,得到所有bean的信息,registry是bean們在內(nèi)存中實際的家豺憔。但是這個getRegistry
方法并不是面向用戶的额获,而是面向ApplicationContext的。 -
PropertyValue
和PropertyValue
代表一種抽象概念恭应,在xml中抄邀,bean的屬性包括屬性名和屬性對象,PropertyValue
就是這么一個實體昼榛。 -
BeanReference
代表的是Bean的屬性不是真實對象境肾,而是另一個bean的引用。
Bean的組裝全過程
上面兩部分是鋪墊胆屿,而BeanFactory才是重點對象奥喻。beans.factory
包中有三個類用來定義BeanFactory相關(guān)的概念。
-
BeanFactory
接口莺掠,只有一個方法:Object getBean(String name) throws Exception;
衫嵌,實現(xiàn)這個接口的類,就具有了得到一個bean的能力彻秆。 -
AbstractBeanFactory
類楔绞,較為復(fù)雜结闸。詳情后講。 -
AutowireCapableBeanFactory
繼承了AbstractBeanFactory
酒朵,實現(xiàn)了applyPropertyValues方法桦锄,通過反射,將bean的所有屬性蔫耽,通過set方法注入進(jìn)去结耀。
AbstractBeanFactory
有三大屬性:
-
beanDefinitionMap
,類似于registry匙铡,但是他是BeanFactory里面私有的图甜,代表的是這個BeanFactory里面暫時有哪些bean定義。 -
beanDefinitionNames
代表里面鳖眼,這個BeanFactory里面有哪些bean(名字)黑毅。 -
beanPostProcessors
,代理處理器钦讳,AOP會用到矿瘦,詳情后講。
AbstractBeanFactory
實現(xiàn)了幾大功能:
-
getBean
愿卒,這是主要功能缚去,可以獲取一個Bean對象。 -
registerBeanDefinition
琼开,面向ApplicationContext易结,用來將XML配置文件里面的bean定義注冊到BeanFactory里面。 -
preInstantiateSingletons
柜候,面向ApplicationContext衬衬,用來在開始的時候,將所有的bean都初始化完畢改橘,避免懶加載。 -
addBeanPostProcessor
添加代理處理器玉控。 -
getBeansForType
飞主,在BeanFactory里面,獲取某一個類型的所有bean高诺。
經(jīng)過上面的分析碌识,我們可以知道BeanFactory完成了Bean初始化的整個流程。BeanFactory的工作流程如下:
- getBean, 在beanDefinitionMap里面得到bean虱而,如果沒有的話筏餐,先初始化。(為什么會沒有牡拇,因為ApplicationContext讀取xml文件時候魁瞪,只是給BeanDefinition服了類類型穆律,并沒有賦值對象,這個對象還是需要BeanFactory通過反射生成的)导俘。
- createBeanInstance峦耘,通過反射,根據(jù)BeanDefinition的類對象新建實體對象->將得到的bean對象賦值給beandefinition旅薄,然后將BeanDefinition里面的屬性都注入到Bean里面辅髓,這就完成了doCreateBean。
- initializeBean就是調(diào)用BeanPostProcessor的postProcessBeforeInitilizztion方法和postProcessAfterIntilizatin方法少梁,獲取新的bean洛口,這里會在aop中用到。
好了凯沪,到這BeanFactory就講完了第焰,下面是更重要的ApplicationContext。
ApplicationContext-用戶與BeanFactory之間的橋梁
beans.context
包有三個類著洼、接口樟遣,完成了ApplicationContext的基本功能。
- ApplicationContext接口身笤,沒有任何方法豹悬,只是繼承了BeanFactory接口,暗示ApplicationContext與BeanFactory都是獲取Bean的地方液荸。
-
AbstractApplicationContext
抽象類瞻佛,首先,它的構(gòu)造函數(shù)接收入?yún)eanFactory娇钱,所以說ApplicationContext內(nèi)部具有一個BeanFactory伤柄。類似于一種裝飾器模式,但不是裝飾器模式文搂,類似于代理模式适刀,但也不是代理模式。fresh方法分為三個步驟:1.loadBeanDefinitions煤蹭,這個是一個模板方法笔喉,需要子類實現(xiàn),它的作用就是從某一個地方讀取BeanDefinition硝皂,然后寫入到ApplicationContext自己的BeanFactory里面常挚,這就是ApplicationContext與BeanFactory之間的聯(lián)系,也就是ApplicationContext還負(fù)責(zé)了讀取定義稽物。2. registerBeanPostProcessors奄毡,這個就是在BeanFactory里面找到BeanPostProcessor,然后將他們放到BeanFactory的beanPostProcessors容器里面贝或,方便BeanFactory初始化使用吼过。3. onRefresh初始化一遍所有的bean锐秦。 -
ClassPathXmlApplicationContext
實現(xiàn)了loadBeanDefinitions的方法,將xml文件和BeanFactory結(jié)合在一起那先。
總結(jié)-ApplicationContext初始化流程
總結(jié)-ApplicationContext獲取bean流程
AOP--移花接木之魔法
上一節(jié)农猬,講完了Spring IOC的整個流程,也就是bean從定義獲取售淡,到得到bean之間的整個流程斤葱。本節(jié),我們接觸一下Spring另一個重要概念揖闸,AOP揍堕。AOP用途十分廣泛,其中Spring內(nèi)部的聲明式事務(wù)和攔截器都是利用了AOP的強(qiáng)大威力汤纸,才得以優(yōu)雅的實現(xiàn)衩茸。
AOP是什么呢,簡單來說贮泞,它可以讓編程人員在不修改對象代碼的情況下楞慈,為這個對象添加額外的功能或者限制。
很熟悉吧啃擦,這就是代理模式囊蓝!
Java中存在兩種代理模式:
一種叫靜態(tài)代理,就是通過接口繼承復(fù)用的方式來完成的令蛉, 代理類與被代理對象實現(xiàn)相同的接口聚霜,然后代理類里面會擁有一個被代理對象,代理類與被代理對象相同的方法珠叔,活調(diào)用被代理對象的方法蝎宇,不過中間會加以限制,您翻開任何一本設(shè)計模式相關(guān)的書祷安,翻到代理模式這一節(jié)姥芥,講的就是它了。
另一種叫做動態(tài)代理汇鞭,動態(tài)代理就是允許我們在程序運(yùn)行過程中撇眯,為動態(tài)生成的對象,動態(tài)的生成代理虱咧。顯然,這比靜態(tài)代理靈活太多了锚国。
Java默認(rèn)提供了動態(tài)代理的實現(xiàn)方式腕巡,但是有限制,它要求被代理對象必須實現(xiàn)某一個接口血筑。為了突破這一限制绘沉,為普通類也可以提供代理煎楣,CGLib這個庫橫空出世。
因為AOP涉及的知識較為復(fù)雜车伞,所以我先將背景知識介紹一下择懂。
- Java動態(tài)代理,就是Java本身提供的代理實現(xiàn)另玖,要求對象必須實現(xiàn)某一個接口困曙。
- CGLib庫,為Java提供了谦去,為普通類提供代理的功能慷丽。
- aopalliance,aop聯(lián)盟包鳄哭,統(tǒng)一類aop編程的一些概念要糊,這個包里沒有具體的類實現(xiàn),而是定義了幾個重要的概念接口妆丘,具體的aop實現(xiàn)锄俄,要遵從這些接口編程,才能達(dá)到一定的通用性勺拣。
- aspectj包奶赠,實現(xiàn)了,通過一種固定的編程語言宣脉,通過這種簡單的編程語言车柠,我們可以定位到被代理的類,自動完成代理塑猖。
在aopallicance里面竹祷,定義了幾個核心概念:
-
Advice
增強(qiáng),說明這是一個羊苟,實現(xiàn)這個接口塑陵,說明這個類負(fù)責(zé)某一種形式的增強(qiáng)。 -
Joinpoint
連接點蜡励,表示切點與目標(biāo)方法連接處的信息令花。 -
MethodInterceptor
繼承了Interceptor接口,而Interceptor繼承了Advice接口凉倚,是一種Advice兼都,但是有一個方法invoke。這個方法需要一個參數(shù)MethodInvocation稽寒。 -
MethodInvocation
表示的是連接點的信息以及連接點函數(shù)的調(diào)用扮碧。
結(jié)合上面的信息,我們發(fā)現(xiàn),其實MethodInterceptor的invoke方法慎王,調(diào)用的就是MethodInvocation的proceed方法蚓土,而這個proceed方法呢,應(yīng)該調(diào)用的肯定是Method.invoke方法赖淤。所以蜀漆,這是一種變相調(diào)用method.invoke的方式。為什么這樣做呢咱旱,猜一猜的話确丢,肯定是為了代碼的復(fù)用,哈哈哈莽龟,這是廢話蠕嫁。
在Spring中,還定義了幾個核心概念: -
Pointcut
毯盈,切點剃毒,可以定位類以及方法 -
Advisor
,可以獲取一個增強(qiáng)搂赋。 -
PointcutAdvisor
赘阀,定義了哪些方法,具有什么類型的增強(qiáng)脑奠。 -
MethodMatcher
表示某一個方法是否符合條件 -
ClassFilter
定義了某個類是否符合條件基公。 -
TargetSource
被代理的對象,包括對象本身宋欺,類類型轰豆、所有接口 -
AdvisedSupport
代理相關(guān)的元數(shù)據(jù),包括被代理的對象齿诞,增強(qiáng)等酸休。 -
ProxyFactory
,代理工廠祷杈,可以獲得一個代理對象斑司,同時具有AdvisedSupport的所有特性。 -
Cglib2AopProxy
但汞,使用cglib實現(xiàn)的動態(tài)代理類宿刮,繼承了AbstractAopProxy抽象類,這個類的主要方法就是getProxy私蕾,通過什么呢僵缺,通過AdvisorSupport。 -
ReflectiveMethodInvocation
可以獲取連接點的信息踩叭,代理的對象等谤饭。 -
JdkDynamicAopProxy
,和Cglib2AopProxy類一個作用,通過AdvisorSupport來getProxy揉抵,不過是使用Java自帶的動態(tài)代理實現(xiàn)的。
其中嗤疯,ProxyFactory是獲取一個代理對象的直接工廠冤今,而這個代理對象,可以通過Cglib2AopProxy產(chǎn)生茂缚,也可以通過JdkDynamicAopProxy產(chǎn)生戏罢。
Spring AOP之所以能夠為動態(tài)生成的Bean提供代理,得益于PostProcessor接口脚囊。我們會議IOC初始化流程中龟糕,最后一部,就是得到BeanFactory之中所有繼承了PostProcessor接口的bean悔耘,調(diào)用它們的postProcessBeforeInitilization讲岁、postProcessAfterInitilization方法,來代理bean衬以,生成新的bean缓艳。
基于這個突破口,我們只需要在xml配置文件中看峻,放入PostProcessor對象阶淘,Spring就會自動的用這寫對象,來代理真正的對象互妓。
在這里溪窒,我們的對象是AspectJAwareAdvisorAutoProxyCreator。
在這個對象的方法中冯勉,邏輯是這樣的澈蚌,找到xml里面所有切面bean,然后在這些bean里面珠闰,找到符合被代理類的切面bean惜浅,找到切面bean之后,就可以獲得增強(qiáng)伏嗜,切點等坛悉,于是可有構(gòu)造一個AdvisorSupport,知道了AdvisorSupport承绸,我們就能夠通過proxyFactory來獲取代理了裸影。
至于如何這個類切面是用來切入代理類的,這個就要交給PointCut來實現(xiàn)了军熏,pointcut有很多實現(xiàn)方式轩猩,這里我們用的是aspectj。具體這個類我就不細(xì)講了。
到目前位置均践,我自己已經(jīng)將整個AOP的流程搞清楚了晤锹,下面通過流程圖的形式展示出來: