聊聊Spring中的那些擴充套件機制

咖啡拿鐵發表於2018-09-25


前方高能預警:本文將會有大量程式碼出沒。

1.背景

在看一些框架原始碼的時候,可以看見他們很多都會和Spring去做結合。舉個例子dubbo的配置:

聊聊Spring中的那些擴充套件機制很多人其實配置了也就配置了,沒有去過多的思考:為什麼這麼配置spring就能識別,dubbo就能啟動?

如果你也需要做一個框架和Spring結合,或者你想知道Spring其他框架是如何和Spring做結合的,那麼你應該瞭解一下Spring的擴充套件機制。

2.如何擴充套件

本篇文章想從Spring的兩個流程去介紹如何擴充套件,一個是容器初始化流程,一個是Bean的建立流程進行將。

2.1 容器的初始化

要想使用Spring,第一步肯定是需要先讓容器初始化。在AbstractApplicationContext中有一個refresh方法定義了容器如何進行重新整理:

聊聊Spring中的那些擴充套件機制

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

聊聊Spring中的那些擴充套件機制其中比較常見的擴充套件在載入BeanDefinition中和執行BeanPostProcessor。下面講述一下如何進行這兩個的擴充套件。

2.1.1 載入BeanDefinition

在介紹載入BeanDefinition之前,先讓我們瞭解一下什麼是BeanDefinition,顧名思義BeanDefinition描述Bean的資訊的,比如他的class資訊,屬性資訊,是否是單例,是否延遲載入等。

如何載入呢?一般有兩種手段,一個是透過我們的xml,一個是透過一些擴充套件手段。

xml載入如下:

聊聊Spring中的那些擴充套件機制

我們在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 檔案描述的你節點元素。 聊聊Spring中的那些擴充套件機制在resources/META-INF/目錄下定義demo.xsd檔案。這裡定義了一個demo的節點元素,其中定義了一個name欄位。

  • 編寫一個 NamespaceHandler 的實現類

聊聊Spring中的那些擴充套件機制

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

聊聊Spring中的那些擴充套件機制

  • 註冊上述的 schema 和 handler。 在resources/META-INF/ 目錄下面建立spring.handler檔案輸入:

http\://

,這一步將我們之前的標籤的url對映到我們NamespaceHandler。 再建立一個spring.schemas檔案,輸入:

http\://

這一步將xsd的url進行了對映。

回到註解,大家配置註解的時候一般都是使用下圖進行配置:

聊聊Spring中的那些擴充套件機制但是可以看見其依然是使用XML schema擴充套件進行處理,在Spring中有個叫ContextNamespaceHandler,註冊很多解析器:聊聊Spring中的那些擴充套件機制其中有一個解析器是compnent-scan,在他的parse方法中定義瞭如何進行註解掃描,獲取註解:

聊聊Spring中的那些擴充套件機制

利用這個擴充套件機制的還有AOP,MVC,Spring-Cache以及我們的一些開源框架比如Dubbo等。

2.1.1.2 BeanFactoryPostProcessor擴充套件

這個機制可以讓我們在真正的例項化Bean之前對BeanDefinition進行修改。

這裡我舉例一個實戰的例子,想必大家很多都配置過資料庫連線池吧,這裡拿Druid來舉例:

聊聊Spring中的那些擴充套件機制

然後我們建立一個druid.properties輸入:

url=jdbc:mysql://localhost:3306/testusername=rootpassword=123456

對於這種配置自己玩玩已經滿足,但是在公司有個問題,密碼放在專案中明碼儲存,這樣是不行的,別人只要獲得了你專案的檢視許可權那麼密碼就會被洩漏,所以一般的公司會有一個統一的密碼儲存服務,只有足夠的許可權才能夠使用,那麼我們可以把密碼放在統一儲存服務中,透過對服務的呼叫才能進行密碼的使用,那麼我們怎麼把從遠端服務中獲取到的密碼注入到我們Bean中呢?那麼就要使用我們的BeanFactoryPostpRrocessor,下面的程式碼繼承PropertyPlaceholderConfigurer(BeanFactoryPostpRrocessor的實現類):

聊聊Spring中的那些擴充套件機制

在XML中有:

聊聊Spring中的那些擴充套件機制

透過這種方式我們可以有幾個好處:

  • 設定統一配置中心,那麼我們不需要修改我們專案中的檔案,只需要在配置中心頁面中修改即可。

  • 設定統一密碼中心,那麼我們不需要暴露明文在專案中,密碼如何保護那麼就直接丟給密碼中心即可。

2.2 Bean的建立

一般我們在API中獲取一個Bean都會如下操作:

聊聊Spring中的那些擴充套件機制透過GetBean操作進行獲取,前面我們講到過如果是非延遲載入的單例Bean那麼會在容器重新整理的時候進行載入,如果是延遲載入的Bean那麼會在我們獲取Bean的時候根據BeanDefinition進行載入。 首先在AbstractBeanFactory有兩個方法一個是doCreate,一個是create用來描述如何建立一個Bean。這裡說一下單例Bean是如何建立的:

聊聊Spring中的那些擴充套件機制doCreateBean操作流程如下圖:

聊聊Spring中的那些擴充套件機制

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

聊聊Spring中的那些擴充套件機制

2.2.1 Aware介面

Spring提供了很多Aware介面用於進行擴充套件,透過Aware我們可以設定很多想設定的東西:

聊聊Spring中的那些擴充套件機制

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進行處理。

聊聊Spring中的那些擴充套件機制在BeanPostProcessor分為兩個方法,一個是用於初始化前置處理,一個是初始化用於後置處理。

有一種特殊的BeanPostProcessor,InstantiationAwareBeanPostProcessor,其會在我們例項化流程之前,如果實現了這個介面,那麼就會使用其返回的物件例項,不會進入後續流程。

實戰:BeanPostProcessor有什麼用呢?

如果你有一個需求,打點專案中方法每個方法的執行時常,你很容易想到用AOP去做,如果不用AOP的話那麼你可以使用BeanPostProcessor的後置處理方法,將對應的每個Bean都進行動態代理。

2.2.3 InitializingBean/init-method

Spring提供了我們對Bean進行初始化邏輯的擴充套件:

  • 實現InitalizingBean介面: 聊聊Spring中的那些擴充套件機制在afterPropertiesSet()方法中我們可以寫入我們的初始化邏輯。

  • 透過xml方式:

聊聊Spring中的那些擴充套件機制在init-method中定義了我們初始化方法。

2.2.4 DisposableBean/destory-method

俗話說,生與死輪迴不止。那麼我們有了生的擴充套件,自然Spring提供了死的擴充套件。我們也可以透過下面兩個擴充套件來實現我們銷燬的邏輯:

  • DisposableBean: 實現DisposableBean介面

聊聊Spring中的那些擴充套件機制實現destroy方法即可。

  • 實現XML: 聊聊Spring中的那些擴充套件機制在destroy-method中定義銷燬方法。

PS:在我們Spring容器中如果要在JVM關閉時自動呼叫關閉的方法那麼我們可以((ClassPathXmlApplicationContext) applicationContext).registerShutdownHook();註冊關閉鉤子,這樣在關閉JVM的時候我們的Bean也能安全銷燬。

3.總結

本篇文章從Spring容器啟動原理,以及Bean的初始化原理介紹,引出了多個基本的擴充套件點。當然這部分擴充套件點還僅僅是Spring中的一部分,感興趣的可以閱讀Spring的文件,或者閱讀Spring原始碼。如果能掌握這些擴充套件,以後自己造輪子的時候和Spring結合這些擴充套件是不能少的。

最後打個廣告,如果你覺得這篇文章對你有文章,可以關注我的技術公眾號,最近作者收集了很多最新的學習資料影片以及面試資料,關注之後即可領取,你的關注和轉發是對我最大的支援,O(∩_∩)O

聊聊Spring中的那些擴充套件機制

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31555607/viewspace-2214762/,如需轉載,請註明出處,否則將追究法律責任。

相關文章