慎入:本文將會有大量代碼出入。java
在看一些框架源碼的時候,能夠看見他們不少都會和Spring去作結合。舉個例子dubbo的配置:mysql
不少人其實配置了也就配置了,沒有去過多的思考:爲何這麼配置spring就能識別,dubbo就能啓動?若是你也須要作一個框架和Spring結合,或者你想知道Spring其餘框架是如何和Spring作結合的,那麼你應該瞭解一下Spring的擴展機制。git
本篇文章想從Spring的兩個流程去介紹如何擴展,一個是容器初始化流程,一個是Bean的建立流程。github
要想使用Spring,第一步確定是須要先讓容器初始化。在AbstractApplicationContext中有一個refresh方法定義了容器如何進行刷新:面試
在refresh中的具體流程以下圖:spring
其中比較常見的擴展在加載BeanDefinition中和執行BeanPostProcessor。下面講述一下如何進行這兩個的擴展。在介紹加載BeanDefinition以前,先讓咱們瞭解一下什麼是BeanDefinition,顧名思義BeanDefinition描述Bean的信息的,好比他的class信息,屬性信息,是不是單例,是否延遲加載等。sql
如何加載呢?通常有兩種手段,一個是經過咱們的xml,一個是經過一些擴展手段。數據庫
xml加載以下:安全
咱們在spring的XML中配置這樣一個bean的定義,他會進行解析而後轉換成咱們的BeanDefinition。bash
還有種方式是經過XML schema擴展的方式,關於xsd的一些詳細介紹能夠參考這篇文章: Spring中的XML schema擴展機制。有些同窗會問不是還有個註解的方式嗎?咱們在學的時候通常書上都寫XML和註解兩種方式,註解其實也是使用了XML schema的擴展機制,等會我會細講。
什麼是XML schema的擴展呢?
Spring容許你本身定義XML的的結構而且能夠用本身的bean解析器進行解析。這裏參考一下Spring中的XML schema擴展機制進行自定義擴展的4個步驟:
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等。
這個機制可讓咱們在真正的實例化Bean以前對BeanDefinition進行修改。
這裏我舉例一個實戰的例子,想必你們不少都配置過數據庫鏈接池吧,這裏拿Druid來舉例:
而後咱們建立一個druid.properties輸入:
url=jdbc:mysql://localhost:3306/test
username=root
password=123456
複製代碼
對於這種配置本身玩玩已經知足,可是在公司有個問題,密碼放在項目中明碼存儲,這樣是不行的,別人只要得到了你項目的查看權限那麼密碼就會被泄漏,因此通常的公司會有一個統一的密碼存儲服務,只有足夠的權限纔可以使用,那麼咱們能夠把密碼放在統一存儲服務中,經過對服務的調用才能進行密碼的使用,那麼咱們怎麼把從遠程服務中獲取到的密碼注入到咱們Bean中呢?那麼就要使用咱們的BeanFactoryPostpRrocessor,下面的代碼繼承PropertyPlaceholderConfigurer(BeanFactoryPostpRrocessor的實現類):
在XML中有:
經過這種方式咱們能夠有幾個好處:
通常咱們在API中獲取一個Bean都會以下操做:
經過GetBean操做進行獲取,前面咱們講到過若是是非延遲加載的單例Bean那麼會在容器刷新的時候進行加載,若是是延遲加載的Bean那麼會在咱們獲取Bean的時候根據BeanDefinition進行加載。 首先在AbstractBeanFactory有兩個方法一個是doCreate,一個是create用來描述如何建立一個Bean。這裏說一下單例Bean是如何建立的: doCreateBean操做流程以下圖:能夠看見真正的建立bean的操做在CreateBean中,對於真正的建立Bean有以下流程:
。Spring提供了不少Aware接口用於進行擴展,經過Aware咱們能夠設置不少想設置的東西:
invokeAwareMethod提供了三種最基本的Aware,若是是ApplicationContext的話那麼在ApplicationContextAwareProcessor又進行了一輪Aware注入。
若是使用ApplicaitonContext類型的容器的話又會有下面幾種:
EnvironmentAware:將上下文中Enviroment注入進去,通常獲取配置屬性時可使用。
EmbeddedValueResolverAware:將上下文中EmbeddedValueResolver注入進去,通常用於參數解析。 ResourceLoaderAware:將上下文設置進去。
ApplicationEventPublisherAware:在ApplicationContext中實現了ApplicationEventPublisher接口,因此能夠將本身注入進去。
MessageSourceAware:將自身注入。
ApplicationContextAware:這個是咱們見的比較多的,會將自身容器注入進去。
在前面咱們說過BeanFactoryPostProcessor,這兩個名字很像,BeanFactoryPostProcessor是用來對咱們BeanFactory中的BeanDefinition進行處理,此時Bean還未生成。而BeanPostProcessor用來對咱們生成的Bean進行處理。
在BeanPostProcessor分爲兩個方法,一個是用於初始化前置處理,一個是初始化用於後置處理。有一種特殊的BeanPostProcessor,InstantiationAwareBeanPostProcessor,其會在咱們實例化流程以前,若是實現了這個接口,那麼就會使用其返回的對象實例,不會進入後續流程。
實戰:BeanPostProcessor有什麼用呢?
若是你有一個需求,打點項目中方法每一個方法的運行時常,你很容易想到用AOP去作,若是不用AOP的話那麼你可使用BeanPostProcessor的後置處理方法,將對應的每一個Bean都進行動態代理。
Spring提供了咱們對Bean進行初始化邏輯的擴展:
俗話說,生與死輪迴不止。那麼咱們有了生的擴展,天然Spring提供了死的擴展。咱們也能夠經過下面兩個擴展來實現咱們銷燬的邏輯:
PS: 在咱們Spring容器中若是要在JVM關閉時自動調用關閉的方法那麼咱們能夠((ClassPathXmlApplicationContext) applicationContext).registerShutdownHook();註冊關閉鉤子,這樣在關閉JVM的時候咱們的Bean也能安全銷燬。
本篇文章從Spring容器啓動原理,以及Bean的初始化原理介紹,引出了多個基本的擴展點。固然這部分擴展點還僅僅是Spring中的一部分,感興趣的能夠閱讀Spring的文檔,或者閱讀Spring源碼。若是能掌握這些擴展,之後本身造輪子的時候和Spring結合這些擴展是不能少的。
這篇文章被我收錄於JGrowing,一個全面,優秀,由社區一塊兒共建的Java學習路線,若是您想參與開源項目的維護,能夠一塊兒共建,github地址爲:github.com/javagrowing… 麻煩給個小星星喲。
最後打個廣告,若是你以爲這篇文章對你有文章,能夠關注個人技術公衆號,最近做者收集了不少最新的學習資料視頻以及面試資料,關注以後便可領取,你的關注和轉發是對我最大的支持,O(∩_∩)O