1.背景
慎入:本文將會有大量代碼出入。
在看一些框架源碼的時候,可以看見他們很多都會和Spring去做結合。舉個例子dubbo的配置:

如果你也需要做一個框架和Spring結合,或者你想知道Spring其他框架是如何和Spring做結合的,那么你應該了解一下Spring的擴展機制。
2.如何擴展
本篇文章想從Spring的兩個流程去介紹如何擴展,一個是容器初始化流程,一個是Bean的創建流程。
2.1 容器的初始化
要想使用Spring,第一步肯定是需要先讓容器初始化。在AbstractApplicationContext中有一個refresh方法定義了容器如何進行刷新:

在refresh中的具體流程如下圖:

2.1.1 加載BeanDefinition
在介紹加載BeanDefinition之前,先讓我們了解一下什么是BeanDefinition,顧名思義BeanDefinition描述Bean的信息的,比如他的class信息,屬性信息,是否是單例,是否延遲加載等。
如何加載呢?一般有兩種手段,一個是通過我們的xml,一個是通過一些擴展手段。
xml加載如下:

我們在spring的XML中配置這樣一個bean的定義,他會進行解析然后轉換成我們的BeanDefinition。
還有種方式是通過XML schema擴展的方式,關於xsd的一些詳細介紹可以參考這篇文章: Spring中的XML schema擴展機制。有些同學會問不是還有個注解的方式嗎?我們在學的時候一般書上都寫XML和注解兩種方式,注解其實也是使用了XML schema的擴展機制,等會我會細講。
2.1.1.1 XML schema擴展
什么是XML schema的擴展呢?
Spring允許你自己定義XML的的結構並且可以用自己的bean解析器進行解析。這里參考一下Spring中的XML schema擴展機制進行自定義擴展的4個步驟:
- 編寫一個 XML schema 文件描述的你節點元素。
在resources/META-INF/目錄下定義demo.xsd文件。這里定義了一個demo的節點元素,其中定義了一個name字段。
- 編寫一個 NamespaceHandler 的實現類

- 編寫一個或者多個 BeanDefinitionParser 的實現 (關鍵步驟).

- 注冊上述的 schema 和 handler。 在resources/META-INF/ 目錄下面創建spring.handler文件輸入:
http\://www.demo.com/schema/demo = xsd.DemoNameSpaceHandler
復制代碼
,這一步將我們之前的標簽的url映射到我們NamespaceHandler。 再創建一個spring.schemas文件,輸入:
http\://www.demo.me/schema/demo/demo.xsd= META-INF/demo.xsd
復制代碼
這一步將xsd的url進行了映射。
回到注解,大家配置注解的時候一般都是使用下圖進行配置:



利用這個擴展機制的還有AOP,MVC,Spring-Cache以及我們的一些開源框架比如Dubbo等。
2.1.1.2 BeanFactoryPostProcessor擴展
這個機制可以讓我們在真正的實例化Bean之前對BeanDefinition進行修改。
這里我舉例一個實戰的例子,想必大家很多都配置過數據庫連接池吧,這里拿Druid來舉例:

然后我們創建一個druid.properties輸入:
url=jdbc:mysql://localhost:3306/test username=root password=123456 復制代碼
對於這種配置自己玩玩已經滿足,但是在公司有個問題,密碼放在項目中明碼存儲,這樣是不行的,別人只要獲得了你項目的查看權限那么密碼就會被泄漏,所以一般的公司會有一個統一的密碼存儲服務,只有足夠的權限才能夠使用,那么我們可以把密碼放在統一存儲服務中,通過對服務的調用才能進行密碼的使用,那么我們怎么把從遠程服務中獲取到的密碼注入到我們Bean中呢?那么就要使用我們的BeanFactoryPostpRrocessor,下面的代碼繼承PropertyPlaceholderConfigurer(BeanFactoryPostpRrocessor的實現類):

在XML中有:

通過這種方式我們可以有幾個好處:
- 設置統一配置中心,那么我們不需要修改我們項目中的文件,只需要在配置中心頁面中修改即可。
- 設置統一密碼中心,那么我們不需要暴露明文在項目中,密碼如何保護那么就直接丟給密碼中心即可。
2.2 Bean的創建
一般我們在API中獲取一個Bean都會如下操作:



可以看見真正的創建bean的操作在CreateBean中,對於真正的創建Bean有如下流程:

2.2.1 Aware接口
Spring提供了很多Aware接口用於進行擴展,通過Aware我們可以設置很多想設置的東西:

invokeAwareMethod提供了三種最基本的Aware,如果是ApplicationContext的話那么在ApplicationContextAwareProcessor又進行了一輪Aware注入。
- BeanNameAware:如果Spring檢測到當前對象實現了該接口,會將該對象實例的beanName設置到對錢對象實例中。
- BeanClassLoaderAware:會將加載當前Bean的ClassLoader注入進去。
- BeanFactoryAware:將當前BeanFactory容器注入進去。
如果使用ApplicaitonContext類型的容器的話又會有下面幾種:
-
EnvironmentAware:將上下文中Enviroment注入進去,一般獲取配置屬性時可以使用。
-
EmbeddedValueResolverAware:將上下文中EmbeddedValueResolver注入進去,一般用於參數解析。 ResourceLoaderAware:將上下文設置進去。
-
ApplicationEventPublisherAware:在ApplicationContext中實現了ApplicationEventPublisher接口,所以可以將自己注入進去。
-
MessageSourceAware:將自身注入。
-
ApplicationContextAware:這個是我們見的比較多的,會將自身容器注入進去。
2.2.2 BeanPostProcessor
在前面我們說過BeanFactoryPostProcessor,這兩個名字很像,BeanFactoryPostProcessor是用來對我們BeanFactory中的BeanDefinition進行處理,此時Bean還未生成。而BeanPostProcessor用來對我們生成的Bean進行處理。

有一種特殊的BeanPostProcessor,InstantiationAwareBeanPostProcessor,其會在我們實例化流程之前,如果實現了這個接口,那么就會使用其返回的對象實例,不會進入后續流程。
實戰:BeanPostProcessor有什么用呢?
如果你有一個需求,打點項目中方法每個方法的運行時常,你很容易想到用AOP去做,如果不用AOP的話那么你可以使用BeanPostProcessor的后置處理方法,將對應的每個Bean都進行動態代理。
2.2.3 InitializingBean/init-method
Spring提供了我們對Bean進行初始化邏輯的擴展:
- 實現InitalizingBean接口:
在afterPropertiesSet()方法中我們可以寫入我們的初始化邏輯。
- 通過xml方式:

2.2.4 DisposableBean/destory-method
俗話說,生與死輪回不止。那么我們有了生的擴展,自然Spring提供了死的擴展。我們也可以通過下面兩個擴展來實現我們銷毀的邏輯:
- DisposableBean: 實現DisposableBean接口

- 實現XML:
在destroy-method中定義銷毀方法。
PS: 在我們Spring容器中如果要在JVM關閉時自動調用關閉的方法那么我們可以((ClassPathXmlApplicationContext) applicationContext).registerShutdownHook();注冊關閉鈎子,這樣在關閉JVM的時候我們的Bean也能安全銷毀。
3.總結
本篇文章從Spring容器啟動原理,以及Bean的初始化原理介紹,引出了多個基本的擴展點。當然這部分擴展點還僅僅是Spring中的一部分,感興趣的可以閱讀Spring的文檔,或者閱讀Spring源碼。如果能掌握這些擴展,以后自己造輪子的時候和Spring結合這些擴展是不能少的。
作者:公眾號_咖啡拿鐵
鏈接:https://juejin.im/post/5ba45a94f265da0aa94a0d71