天天看點

一文讀懂Spring動态配置多資料源---源碼詳細分析

Spring動态多資料源源碼分析及解讀

​ 期初,最開始的原因是:想将答題服務中發送主觀題答題資料給批改中間件這塊抽象出來, 但這塊主要使用的是mq消息的方式發送到批改中間件,是以,最後決定将mq進行抽象,抽象後的結果是:國文,英語,通用任務都能個性化的配置mq,且可以擴充到任何使用mq的業務場景上。終端需要做的就是增加mq配置,自定義消費者業務邏輯方法,調用send方法即可。

​ 這樣做的好處是:原本在每個使用到mq的項目裡都要寫一遍mq生産者,mq消費者,發送mq資料,監聽mq消費等動作,且如果一個項目裡有多個mq配置,要寫多遍這樣的配置。抽象後,隻需要配置檔案中進行配置,然後自定義個性化的業務邏輯消費者,就可以進行mq發送了。

​ 這樣一個可動态配置的mq,要求還是挺多的,如何動态配置? 如何能夠在伺服器啟動的時候就啟動n個mq的生産者和消費者? 發送資料的時候, 怎麼找到正确的mq發送呢?

​ 其實, 我一直相信, 我遇到的問題, 肯定有大神已經遇到過, 并且已經有了成熟的解決方案了. 于是, 開始搜尋行業内的解決方案, 找了很久也沒找到,最後在同僚的提示下,發現Spring動态配置多資料源的思想和我想實作的動态配置多MQ的思想類似。于是,我開始花時間研究Spring動态多資料源的源碼。

Spring動态多資料源是一個我們在項目中常用到的元件,尤其是做項目重構,有多種資料庫,不同的請求可能會調用不同的資料源。這時,就需要動态調用指定的資料源。我們來看看Spring動态多資料源的整體架構

一文讀懂Spring動态配置多資料源---源碼詳細分析

上圖中虛線框部分是Spring動态多資料源的幾個組成部分

ds處理器

aop切面

建立資料源

動态資料源提供者

動态連接配接資料庫

除此之外,還可以看到如下資訊:

Spring動态多資料源是通過動态配置配置檔案的方式來指定多資料源的。

Spring動态多資料源支援四種類型的資料:base資料源,jndi資料源,druid資料源,hikari資料源。

多種觸發機制:通過header配置ds,通過session配置ds,通過spel配置ds,其中ds是datasource的簡稱。

支援資料源嵌套:一個請求過來,這個請求可能會通路多個資料源,也就是方法嵌套的時候調用多資料源,也是支援的。

Spring動态多資料源的幾個組成部分,在代碼源碼結構中完美的展現出來。

一文讀懂Spring動态配置多資料源---源碼詳細分析

上圖是Spring動态多資料源的源碼項目結構,我們主要列一下主要的結構

下圖是Spring多态多資料源的代碼項目結構圖。

一文讀懂Spring動态配置多資料源---源碼詳細分析

這個圖内容比較多,是以字比較小,大概看出一共有6個部分就可以了。後面會就每一個部分詳細說明。

Spring動态多資料源,我們在使用的時候,直接引入jar,然後配置資料源就可以使用了。配置jar包如下

然後是在yml配置檔案中增加配置

在測試的時候, 使用了兩個不同的資料庫, 一個是test,一個是test1

為什麼引入jar就能在項目裡使用了呢?因為在jar包裡配置了META-INF/spring.factories

在這個檔案裡,指定了spring動态加載的時候要自動掃描的檔案DynamicDataSourceAutoConfiguration,這個檔案就是源碼項目的入口了。這裡定義了項目啟動自動裝備DynamicDataSourceAutoConfiguration檔案。

接下來,我們就來看看DynamicDataSourceAutoConfiguration檔案。

下圖是DynamicDataSourceAutoConfiguration檔案的主要内容。

一文讀懂Spring動态配置多資料源---源碼詳細分析

Spring配置檔案主要的作用是在系統加載的時候,就加載相關的bean。這裡項目初始化的時候都加載了哪些bean呢?

動态資料源屬性類DynamicDataSourceProperties

資料源處理器DsProcessor,采用責任鍊設計模式3種方法加載ds

動态資料源注解類DynamicDataSourceAnnotationAdvisor,包括前置通知,切面類,切點的加載

資料源建立器DataSourceCreator,這個方法是在另一個類被加載的DynamicDataSourceCreatorAutoConfiguration。也是自動配置bean類。可以選擇4種類型的資料源進行建立。

資料源提供者Provider,這是動态初始化資料源,讀取yml配置檔案,在配置檔案中可配置1個或多個資料源。

接下來看一下源代碼

1. DynamicDataSourceAutoConfiguration動态資料源配置檔案

看到這段代碼,我們就比較熟悉了,這就是通過注解的方式,在項目啟動的時候,自動注入bean。我們來詳細看一下,他都注入了哪些内容。

動态多資料源預置處理器dsProcess,ds就是datasource的簡稱。這裡主要采用的是責任鍊設計模式,擷取ds。

動态多資料源注解通知dynamicDatasourceAnnotationAdvisor,這是一個aop前置通知,當一個請求發生的時候,會觸發前置通知,用來确定到底使用哪一個mq消息隊列

動态多資料源提供者dynamicDataSourceProvider,我們是動态配置多個資料源,那麼就有一個解析配置的過程,解析配置就是在這裡完成的,解析出多個資料源,然後分别調用資料源建立者去建立資料源。Spring動态多資料源支援資料源的嵌套。

動态路由到資料源DynamicRoutingDataSource,當請求過來的時候,也找到對應的資料源了,要建立資料庫連接配接,資料庫連接配接的操作就是在這裡完成的。

我們發現在這裡就有四個bean的初始化,并沒有bean的create建立過程,bean的建立過程是在另一個配置類(DynamicDataSourceCreatorAutoConfiguration)中完成的。

大概是因為考慮到資料的種類比較多,是以将其單獨放到了一個配置裡面。從上面的源碼可以看出,有四種類型的資料源配置。分别是:basic、jndi、druid、hikari。這四種資料源通過組合設計模式被set到DataSourceCreator中。

接下來,分别來看每一個子產品都做了哪些事情。

Spring動态多資料源, 擷取資料源名稱的方式有3種,這3中方式采用的是責任鍊方式連續擷取的。首先在header中擷取,header中沒有,去session中擷取, session中也沒有, 通過spel擷取。

上圖是DSProcessor處理器的類圖。 一個接口量, 三個具體實作類,主要來看一下接口類實作

這裡定義了DsProcessor nextProcessor屬性, 下一個處理器。 判斷是否擷取到了datasource, 如果擷取到了則直接傳回, 沒有擷取到,則調用下一個處理器。這個邏輯就是處理器的主邏輯,在determineDatasource(MethodInvocation invocation, String key)方法中實作。

接下來,每一個子類都會自定義實作doDetermineDatasource擷取目标資料源的方法。不同的實作類擷取資料源的方式是不同的。

下面看看具體實作類的主邏輯代碼

他們三個的層級關系是在哪裡定義的呢?在DynamicDataSourceAutoConfiguration.java配置檔案中

第一層是headerProcessor,第二層是sessionProcessor, 第三層是spelExpressionProcessor。層級調用,最後獲得ds。

以上就是對資料源處理器子產品的的分析,那麼最終在哪裡被調用呢?來看下一個子產品。

這一塊對應的源代碼結構如下:

一文讀懂Spring動态配置多資料源---源碼詳細分析

這個子產品裡主要有三部分:

切面類:DynamicDataSourceAdvisor,DynamicDataSourceAnnotationAdvisor

切點類:DynamicAspectJExpressionPointcut,DynamicJdkRegexpMethodPointcut

前置通知類:DynamicDataSourceAnnotationInterceptor

他們之間的關系如下。這裡主要是aop方面的知識體系。具體項目結構圖如下:

因為在項目中使用最多的情況是通過注解的方式來解析,是以,我們重點看一下兩個檔案

這裡入參中有一個是DsProcessor,也就是ds處理器。在determineDatasource中看看DS的value值是否包含#,如果包含就經過dsProcessor處理後獲得key,如果不包含#則直接傳回注解的value值。

在切面類的構造函數中設定了前置通知和切點。這個類在項目啟動的時候就會被加載。所有帶有DS注解的方法都會被掃描,在方法被調用的時候觸發前置通知。

這是最底層的操作了,建立資料源。至于到底建立哪種類型的資料源,是由上層配置決定的,在這裡,定義了4中類型的資料源。 并通過組合的方式,用到那個資料源,就動态的建立哪個資料源。

下面來看這個子產品的源代碼結構:

一文讀懂Spring動态配置多資料源---源碼詳細分析

這裡面定義了一個資料源組合類和四種類型的資料源。我們來看看他們之間的關系

四個基本的資料源類,最後通過DataSourceCreator類組合建立資料源,這裡面使用了簡單工廠模式建立類。下面來一個一個看看

這裡就有兩塊,一個是類初始化的時候初始化成員變量, 另一個是建立資料源。當被調用createDataSource的時候執行建立資料源,使用的反射機制建立資料源。

這裡通過name查找的方式過去datasource

其實,這裡面重點方法也是createDataSource(), 如果看不太明白是怎麼建立的,一點關系都沒有,就知道通過這種方式建立了資料源就ok了。

這裡就不多說了, 就是建立hikari類型的資料源。

其實仔細看,就是整合了前面四種類型的資料源,通過簡單工廠模式建立實體類。這裡是真正的去調用資料源,開始建立的地方。

通過拆解來看,發現,也并不太難。繼續來看下一個子產品。

資料源提供者是連接配接配置檔案和資料源建立器的橋梁。資料源提供者先去讀取配置檔案, 将所有的資料源讀取到DynamicDataSourceProperties對象的datasource屬性中,datasource是一個Map集合,可以用來存儲多種類型的資料源。

下面先來看一下資料源提供者的源碼結構:

一文讀懂Spring動态配置多資料源---源碼詳細分析

裡面一共有四個檔案,AbstractDataSourceProvider是父類,其他類繼承自這個類,下面來看一下他們的結構

一文讀懂Spring動态配置多資料源---源碼詳細分析

這裡的成員變量是資料源資料源建立者dataSourceCreator. 提供了一個建立資料源的方法:createDataSourceMap(...), 這個方法的入參是屬性配置檔案datasources, 傳回值是建立的資料源對象結合.

這裡的主要邏輯思想是: 循環周遊從配置檔案讀取的多個資料源, 然後根據資料源的類型, 調用DataSourceCreator資料源建立器去建立(初始化)資料源, 然後傳回已經初始化好的資料源,将其儲存到map集合中.

這是一個抽象類, 裡面就提供了一個抽象方法, 加載資料源.

這個源碼也是非常簡單, 繼承了AbstractDataSourceProvider抽象類, 實作了DynamicDataSourceProvider接口. 在loadDataSources()方法中, 建立了多資料源, 并傳回多資料源的map集合.

這裡指的一提的是他的成員變量dataSourcePropertiesMap. 這個變量是什麼時候被指派的呢? 是在項目啟動, 掃描配置檔案DynamicDataSourceAutoConfiguration的時候被初始化的.

在DynamicDataSourceAutoConfiguration的腦袋上, 有一個注解@EnableConfigurationProperties(DynamicDataSourceProperties.class), 這個注解的作用是自動掃描配置檔案,并自動比對屬性值.

然後,将執行個體化後的屬性對象指派給properties成員變量. 下面來看看DynamicDataSourceProperties.java屬性配置檔案.

這個檔案的功能:

這個檔案定義了掃描yml配置檔案的屬性字首:spring.datasource.dynamic,

設定了預設的資料庫是master主庫, strict表示是否嚴格模式: 如果是嚴格模式,那麼沒有配置資料庫,卻調用了會抛異常, 如果非嚴格模式, 沒有配資料庫, 會采用預設的主資料庫.

datasource: 用來存儲讀取到的資料源, 可能有多個資料源, 是以是map的格式

strategy: 這裡定義了負載均衡政策, 采用的是政策設計模式: 可以在配置檔案中定義, 如果有多個資料源比對,如何選擇. 可選方案: 1. 負載均衡政策, 2. 随機政策.

其他參數就不多說, 比較簡單, 見名思意. 以上就是資料源提供者的主要内容了.

這一塊主要功能是在調用的時候, 進行動态選擇資料源。其源代碼結構如下圖

一文讀懂Spring動态配置多資料源---源碼詳細分析

我們知道動态資料源可以嵌套,為什麼可以嵌套呢,就是這裡決定的, 這裡一共有四個檔案,

1.AbstractRoutingDataSource: 抽象的路由資料源, 這個類主要作用是在找到目标資料源的情況下,連接配接資料庫.

2.DynamicGroupDataSource:動态分組資料源, 在一個請求連結下的所有資料源就是一組. 也就是一個請求過來, 可以嵌套資料源, 這樣資料源就有多個, 這多個就是一組.

DynamicRoutingDataSource: 動态路由資料源, 第一類AbstractRoutingDataSource用來連接配接資料源,那麼到底應該連結哪個資料源呢?在這個類裡面查找, 如何找呢, 從DynamicDataSourceContextHolder裡面擷取目前線程的資料源. 然後連結資料庫.

DynamicDataSourceConfigure: 基于多種政策的自動切換資料源.

這四個檔案的結構關系如下:

先來看看資料源連接配接是如何實作的:

一類是具體方法,用來進行資料庫連接配接

另一類是抽象方法, 給出一個抽象方法, 子類實作決定最終資料源.

這裡定義了分組的概念.

每一個組有一個組名

組裡面有多個資料源, 用list存儲,指的注意的是, list是一個LinkedList,有順序的, 因為在調用資料庫查詢資料的時候, 不能調混了,是以使用順序清單集合.

選擇資料源的政策, 有多個資料源,按照什麼政策選擇呢?由政策類型來決定.

方法的含義都比較好了解,向這個組裡添加資料源,删除資料源,根據政策尋找目标資料源等.

除此之外, 還有一個非常用來的資訊, 那就是這個類實作了InitializingBean接口,這個接口提供了一個afterPropertiesSet()方法, 這個方法在bean被初始化完成之後就會被調用. 這裡也是整個項目能夠被加載的重點.

既然afterPropertiesSet()方法這麼重要, 就來看看他主要做了哪些事情吧.

通過資料源提供器擷取所有的資料源,

将上一步獲得的所有的資料源添加到 dataSourceMap 和 addGroupDataSource 中. 這裡擷取資料源的操作就完成

順着這個思路, 如何添加到 dataSourceMap 和 addGroupDataSource中的呢?

注意第一句話, if (ds.contains(UNDERLINE)) 隻有ds中有下劃線才會走分組資料源. 如果沒有下劃線,則就是按照單個資料源來處理的. 向組裡面添加資料源就不多說了.

除此之外還有一個非常重要的類:DynamicDataSourceContextHolder

儲存了目前線程裡面所有的資料源. 使用的是ThreadLocal<Deque>.這個類最主要的含義就是ThreadLocal, 保證每個線程擷取的是目前線程的資料源.

以上就是整個資料源源碼的全部内容, 内容比較多, 部分功能描述不是特别詳細. 如有任何疑問, 可以留言, 一起研究.

一文讀懂Spring動态配置多資料源---源碼詳細分析

繼續閱讀