聊聊spring的那些擴充套件機制
1.背景
慎入:本文將會有大量程式碼出入。
在看一些框架原始碼的時候,可以看見他們很多都會和Spring去做結合。舉個例子dubbo的配置:
很多人其實配置了也就配置了,沒有去過多的思考:為什麼這麼配置spring就能識別,dubbo就能啟動?
如果你也需要做一個框架和Spring結合,或者你想知道Spring其他框架是如何和Spring做結合的,那麼你應該瞭解一下Spring的擴充套件機制。
2.如何擴充套件
本篇文章想從Spring的兩個流程去介紹如何擴充套件,一個是容器初始化流程,一個是Bean的建立流程。
2.1 容器的初始化
要想使用Spring,第一步肯定是需要先讓容器初始化。在AbstractApplicationContext中有一個refresh方法定義了容器如何進行重新整理:
在refresh中的具體流程如下圖:
其中比較常見的擴充套件在載入BeanDefinition中和執行BeanPostProcessor。下面講述一下如何進行這兩個的擴充套件。
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 檔案描述的你節點元素。
- 編寫一個 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進行了對映。
回到註解,大家配置註解的時候一般都是使用下圖進行配置:
但是可以看見其依然是使用XML schema擴充套件進行處理,在Spring中有個叫ContextNamespaceHandler,註冊很多解析器: 其中有一個解析器是compnent-scan,在他的parse方法中定義瞭如何進行註解掃描,獲取註解:
利用這個擴充套件機制的還有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都會如下操作:
通過GetBean操作進行獲取,前面我們講到過如果是非延遲載入的單例Bean那麼會在容器重新整理的時候進行載入,如果是延遲載入的Bean那麼會在我們獲取Bean的時候根據BeanDefinition進行載入。 首先在AbstractBeanFactory有兩個方法一個是doCreate,一個是create用來描述如何建立一個Bean。這裡說一下單例Bean是如何建立的:
doCreateBean操作流程如下圖:
可以看見真正的建立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分為兩個方法,一個是用於初始化前置處理,一個是初始化用於後置處理。
有一種特殊的BeanPostProcessor,InstantiationAwareBeanPostProcessor,其會在我們例項化流程之前,如果實現了這個介面,那麼就會使用其返回的物件例項,不會進入後續流程。
實戰:BeanPostProcessor有什麼用呢?
如果你有一個需求,打點專案中方法每個方法的執行時常,你很容易想到用AOP去做,如果不用AOP的話那麼你可以使用BeanPostProcessor的後置處理方法,將對應的每個Bean都進行動態代理。
2.2.3 InitializingBean/init-method
Spring提供了我們對Bean進行初始化邏輯的擴充套件:
- 實現InitalizingBean介面: 在afterPropertiesSet()方法中我們可以寫入我們的初始化邏輯。
- 通過xml方式:
在init-method中定義了我們初始化方法。
2.2.4 DisposableBean/destory-method
俗話說,生與死輪迴不止。那麼我們有了生的擴充套件,自然Spring提供了死的擴充套件。我們也可以通過下面兩個擴充套件來實現我們銷燬的邏輯:
- DisposableBean: 實現DisposableBean介面
實現destroy方法即可。
- 實現XML: 在destroy-method中定義銷燬方法。
PS: 在我們Spring容器中如果要在JVM關閉時自動呼叫關閉的方法那麼我們可以((ClassPathXmlApplicationContext) applicationContext).registerShutdownHook();註冊關閉鉤子,這樣在關閉JVM的時候我們的Bean也能安全銷燬。
3.總結
本篇文章從Spring容器啟動原理,以及Bean的初始化原理介紹,引出了多個基本的擴充套件點。當然這部分擴充套件點還僅僅是Spring中的一部分,感興趣的可以閱讀Spring的文件,或者閱讀Spring原始碼。如果能掌握這些擴充套件,以後自己造輪子的時候和Spring結合這些擴充套件是不能少的。
作者:公眾號_咖啡拿鐵
連結:https://juejin.im/post/5ba45a94f265da0aa94a0d71