1.背景
在看一些框架源碼的時(shí)候越走,可以看見(jiàn)他們很多都會(huì)和Spring去做結(jié)合。舉個(gè)例子dubbo的配置:
很多人其實(shí)配置了也就配置了会傲,沒(méi)有去過(guò)多的思考:為什么這么配置spring就能識(shí)別错英,dubbo就能啟動(dòng)秉宿?
如果你也需要做一個(gè)框架和Spring結(jié)合,或者你想知道Spring其他框架是如何和Spring做結(jié)合的,那么你應(yīng)該了解一下Spring的擴(kuò)展機(jī)制践叠。
2.如何擴(kuò)展
本篇文章想從Spring的兩個(gè)流程去介紹如何擴(kuò)展言缤,一個(gè)是容器初始化流程,一個(gè)是Bean的創(chuàng)建流程進(jìn)行將禁灼。
2.1 容器的初始化
要想使用Spring管挟,第一步肯定是需要先讓容器初始化。在AbstractApplicationContext中有一個(gè)refresh方法定義了容器如何進(jìn)行刷新:
在refresh中的具體流程如下圖:
其中比較常見(jiàn)的擴(kuò)展在加載BeanDefinition中和執(zhí)行BeanPostProcessor弄捕。下面講述一下如何進(jìn)行這兩個(gè)的擴(kuò)展僻孝。
2.1.1 加載BeanDefinition
在介紹加載BeanDefinition之前,先讓我們了解一下什么是BeanDefinition,顧名思義BeanDefinition描述Bean的信息的守谓,比如他的class信息穿铆,屬性信息,是否是單例斋荞,是否延遲加載等悴务。
如何加載呢?一般有兩種手段譬猫,一個(gè)是通過(guò)我們的xml讯檐,一個(gè)是通過(guò)一些擴(kuò)展手段。
xml加載如下:
我們?cè)趕pring的XML中配置這樣一個(gè)bean的定義染服,他會(huì)進(jìn)行解析然后轉(zhuǎn)換成我們的BeanDefinition别洪。
還有種方式是通過(guò)XML schema擴(kuò)展的方式,關(guān)于xsd的一些詳細(xì)介紹可以參考這篇文章:Spring中的XML schema擴(kuò)展機(jī)制柳刮。有些同學(xué)會(huì)問(wèn)不是還有個(gè)注解的方式嗎挖垛?我們?cè)趯W(xué)的時(shí)候一般書上都寫XML和注解兩種方式,注解其實(shí)也是使用了XML schema的擴(kuò)展機(jī)制秉颗,等會(huì)我會(huì)細(xì)講痢毒。
2.1.1.1 XML schema擴(kuò)展
什么是XML schema的擴(kuò)展呢?
Spring允許你自己定義XML的的結(jié)構(gòu)并且可以用自己的bean解析器進(jìn)行解析蚕甥。這里參考一下Spring中的XML schema擴(kuò)展機(jī)制進(jìn)行自定義擴(kuò)展的4個(gè)步驟:
編寫一個(gè) XML schema 文件描述的你節(jié)點(diǎn)元素哪替。
在resources/META-INF/目錄下定義demo.xsd文件。這里定義了一個(gè)demo的節(jié)點(diǎn)元素菇怀,其中定義了一個(gè)name字段凭舶。
編寫一個(gè) NamespaceHandler 的實(shí)現(xiàn)類
編寫一個(gè)或者多個(gè) BeanDefinitionParser 的實(shí)現(xiàn) (關(guān)鍵步驟).
注冊(cè)上述的 schema 和 handler。 在resources/META-INF/ 目錄下面創(chuàng)建spring.handler文件輸入:
http\://www.demo.com/schema/demo = xsd.DemoNameSpaceHandler
爱沟,這一步將我們之前的標(biāo)簽的url映射到我們NamespaceHandler帅霜。
再創(chuàng)建一個(gè)spring.schemas文件,輸入:
http\://www.demo.me/schema/demo/demo.xsd= META-INF/demo.xsd
這一步將xsd的url進(jìn)行了映射。
回到注解呼伸,大家配置注解的時(shí)候一般都是使用下圖進(jìn)行配置:
但是可以看見(jiàn)其依然是使用XML schema擴(kuò)展進(jìn)行處理身冀,在Spring中有個(gè)叫ContextNamespaceHandler,注冊(cè)很多解析器:
其中有一個(gè)解析器是compnent-scan,在他的parse方法中定義了如何進(jìn)行注解掃描,獲取注解:
利用這個(gè)擴(kuò)展機(jī)制的還有AOP,MVC,Spring-Cache以及我們的一些開源框架比如Dubbo等。
2.1.1.2 BeanFactoryPostProcessor擴(kuò)展
這個(gè)機(jī)制可以讓我們?cè)谡嬲膶?shí)例化Bean之前對(duì)BeanDefinition進(jìn)行修改搂根。
這里我舉例一個(gè)實(shí)戰(zhàn)的例子珍促,想必大家很多都配置過(guò)數(shù)據(jù)庫(kù)連接池吧,這里拿Druid來(lái)舉例:
然后我們創(chuàng)建一個(gè)druid.properties輸入:
url=jdbc:mysql://localhost:3306/test
username=root
password=123456
對(duì)于這種配置自己玩玩已經(jīng)滿足兄墅,但是在公司有個(gè)問(wèn)題踢星,密碼放在項(xiàng)目中明碼存儲(chǔ),這樣是不行的隙咸,別人只要獲得了你項(xiàng)目的查看權(quán)限那么密碼就會(huì)被泄漏沐悦,所以一般的公司會(huì)有一個(gè)統(tǒng)一的密碼存儲(chǔ)服務(wù),只有足夠的權(quán)限才能夠使用五督,那么我們可以把密碼放在統(tǒng)一存儲(chǔ)服務(wù)中藏否,通過(guò)對(duì)服務(wù)的調(diào)用才能進(jìn)行密碼的使用,那么我們?cè)趺窗褟倪h(yuǎn)程服務(wù)中獲取到的密碼注入到我們Bean中呢?那么就要使用我們的BeanFactoryPostpRrocessor充包,下面的代碼繼承PropertyPlaceholderConfigurer(BeanFactoryPostpRrocessor的實(shí)現(xiàn)類):
在XML中有:
通過(guò)這種方式我們可以有幾個(gè)好處:
設(shè)置統(tǒng)一配置中心副签,那么我們不需要修改我們項(xiàng)目中的文件,只需要在配置中心頁(yè)面中修改即可基矮。
設(shè)置統(tǒng)一密碼中心淆储,那么我們不需要暴露明文在項(xiàng)目中,密碼如何保護(hù)那么就直接丟給密碼中心即可家浇。
2.2 Bean的創(chuàng)建
一般我們?cè)贏PI中獲取一個(gè)Bean都會(huì)如下操作:
通過(guò)GetBean操作進(jìn)行獲取本砰,前面我們講到過(guò)如果是非延遲加載的單例Bean那么會(huì)在容器刷新的時(shí)候進(jìn)行加載,如果是延遲加載的Bean那么會(huì)在我們獲取Bean的時(shí)候根據(jù)BeanDefinition進(jìn)行加載钢悲。首先在AbstractBeanFactory有兩個(gè)方法一個(gè)是doCreate,一個(gè)是create用來(lái)描述如何創(chuàng)建一個(gè)Bean点额。這里說(shuō)一下單例Bean是如何創(chuàng)建的:
doCreateBean操作流程如下圖:
可以看見(jiàn)真正的創(chuàng)建bean的操作在CreateBean中,對(duì)于真正的創(chuàng)建Bean有如下流程:
莺琳。
2.2.1 Aware接口
Spring提供了很多Aware接口用于進(jìn)行擴(kuò)展还棱,通過(guò)Aware我們可以設(shè)置很多想設(shè)置的東西:
invokeAwareMethod提供了三種最基本的Aware,如果是ApplicationContext的話那么在ApplicationContextAwareProcessor又進(jìn)行了一輪Aware注入。
BeanNameAware:如果Spring檢測(cè)到當(dāng)前對(duì)象實(shí)現(xiàn)了該接口惭等,會(huì)將該對(duì)象實(shí)例的beanName設(shè)置到對(duì)錢對(duì)象實(shí)例中珍手。
BeanClassLoaderAware:會(huì)將加載當(dāng)前Bean的ClassLoader注入進(jìn)去。
BeanFactoryAware:將當(dāng)前BeanFactory容器注入進(jìn)去咕缎。
如果使用ApplicaitonContext類型的容器的話又會(huì)有下面幾種:
EnvironmentAware:將上下文中Enviroment注入進(jìn)去珠十,一般獲取配置屬性時(shí)可以使用。
EmbeddedValueResolverAware:將上下文中EmbeddedValueResolver注入進(jìn)去凭豪,一般用于參數(shù)解析。 ResourceLoaderAware:將上下文設(shè)置進(jìn)去晒杈。
ApplicationEventPublisherAware:在ApplicationContext中實(shí)現(xiàn)了ApplicationEventPublisher接口嫂伞,所以可以將自己注入進(jìn)去。
MessageSourceAware:將自身注入。
ApplicationContextAware:這個(gè)是我們見(jiàn)的比較多的帖努,會(huì)將自身容器注入進(jìn)去撰豺。
2.2.2 BeanPostProcessor
在前面我們說(shuō)過(guò)BeanFactoryPostProcessor,這兩個(gè)名字很像,BeanFactoryPostProcessor是用來(lái)對(duì)我們BeanFactory中的BeanDefinition進(jìn)行處理拼余,此時(shí)Bean還未生成污桦。而BeanPostProcessor用來(lái)對(duì)我們生成的Bean進(jìn)行處理。
在BeanPostProcessor分為兩個(gè)方法匙监,一個(gè)是用于初始化前置處理凡橱,一個(gè)是初始化用于后置處理。
有一種特殊的BeanPostProcessor,InstantiationAwareBeanPostProcessor亭姥,其會(huì)在我們實(shí)例化流程之前稼钩,如果實(shí)現(xiàn)了這個(gè)接口,那么就會(huì)使用其返回的對(duì)象實(shí)例达罗,不會(huì)進(jìn)入后續(xù)流程坝撑。
實(shí)戰(zhàn):BeanPostProcessor有什么用呢?
如果你有一個(gè)需求粮揉,打點(diǎn)項(xiàng)目中方法每個(gè)方法的運(yùn)行時(shí)常巡李,你很容易想到用AOP去做,如果不用AOP的話那么你可以使用BeanPostProcessor的后置處理方法,將對(duì)應(yīng)的每個(gè)Bean都進(jìn)行動(dòng)態(tài)代理扶认。
2.2.3 InitializingBean/init-method
Spring提供了我們對(duì)Bean進(jìn)行初始化邏輯的擴(kuò)展:
實(shí)現(xiàn)InitalizingBean接口:
在afterPropertiesSet()方法中我們可以寫入我們的初始化邏輯侨拦。
通過(guò)xml方式:
在init-method中定義了我們初始化方法。
2.2.4 DisposableBean/destory-method
俗話說(shuō)蝠引,生與死輪回不止阳谍。那么我們有了生的擴(kuò)展,自然Spring提供了死的擴(kuò)展螃概。我們也可以通過(guò)下面兩個(gè)擴(kuò)展來(lái)實(shí)現(xiàn)我們銷毀的邏輯:
DisposableBean: 實(shí)現(xiàn)DisposableBean接口
實(shí)現(xiàn)destroy方法即可矫夯。
實(shí)現(xiàn)XML:
在destroy-method中定義銷毀方法。
PS:在我們Spring容器中如果要在JVM關(guān)閉時(shí)自動(dòng)調(diào)用關(guān)閉的方法那么我們可以((ClassPathXmlApplicationContext) applicationContext).registerShutdownHook();注冊(cè)關(guān)閉鉤子吊洼,這樣在關(guān)閉JVM的時(shí)候我們的Bean也能安全銷毀训貌。
3.總結(jié)
本篇文章從Spring容器啟動(dòng)原理,以及Bean的初始化原理介紹冒窍,引出了多個(gè)基本的擴(kuò)展點(diǎn)递沪。當(dāng)然這部分?jǐn)U展點(diǎn)還僅僅是Spring中的一部分,感興趣的可以閱讀Spring的文檔综液,或者閱讀Spring源碼款慨。如果能掌握這些擴(kuò)展,以后自己造輪子的時(shí)候和Spring結(jié)合這些擴(kuò)展是不能少的