聊聊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 文件描述的你節點元素。 在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進行了映射。

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

 

但是可以看見其依然是使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM