解析配置檔案自動裝配 DataSource + AbstractRoutingDataSource + AOP 實現動態資料來源 上:原理解析,解析資料來源
spring boot 自動裝配會通過 spring.datasource.*
為我們自動裝配資料來源,所以想要動態的切換資料來源,第一件事是配置資料來源,其次是怎麼切換?最後何時切換?
原理解析(使用 AbstractRoutingDataSource 實現)
spring-jdbc 提供了 AbstractRoutingDataSource
在 getConnection()
時通過 lookup key
決定目標資料來源,使用 AbstractRoutingDataSource 需要準備至少兩個資料來源,這在原始碼中也有體現:
一個預設資料來源 + 動態匹配的資料來源,resolvedDataSources
Object
為 key,DataSource
為 value 的 Map,可見這裡 key 即充當了 lookup key
的角色。在呼叫
getConnection
獲取資料來源時會呼叫 determineTargetDataSource
方法獲取目標資料來源,進而通過 determineCurrentLookupKey
方法獲得當前的 lookup key
,再從 resolvedDataSources
中獲得目標資料來源。
AbstractRoutingDataSource
是一個抽象類,determineCurrentLookupKey
是其唯一的抽象方法,意味著子類只需在適當的時候修改當前的 lookup key
配置資料來源
配置資料來源也就是給 AbstractRoutingDataSource
的 defaultTargetDataSource
和 resolvedDataSources
進行賦值。
這裡我們將資料來源配置在 properties 檔案中,通過工具類解析進行配置。
在 properties 中配置資料來源
demo 專案為 spring boot 專案,配置檔案採用 yml 格式:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding =utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
multi:
ds-keys: db1,db2
db1:
url: jdbc:mysql://127.0.0.1:3306/db11?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
db2:
url: jdbc:mysql://127.0.0.1:3306/db12?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
spring.datasource 字首的將被解析為預設資料來源, spring.datasource.multi 字首的則作為動態資料來源進行解析。
spring.datasource.multi.ds-keys
是比較關鍵的屬性,這個引數定義了專案中所有的動態資料來源的 key,如上述程式碼所示,spring.datasource.multi.ds-keys: db1,db2
:表明共有兩個動態資料來源,key 分別為 db1 和 db2 ,進行解析時將解析如下兩個字首配置的資料來源:
- spring.datasource.multi.db1
- spring.datasource.multi.db2
當然這個規則是我自己定義的,可以按需定義自己的規則並實現相應的解析。
解析資料來源
使用 DynamicDataSourceBuilder 類來解析資料來源,註冊為 bean 並實現 EnvironmentAware 介面,在 setEnvironment
方法中開始進行解析。
initDefaultDataSource
和 initCustomDataSources
方法將解析得到預設資料來源和動態資料來源,預設資料來源將賦值給 defaultDataSource
,targetDataSources
中是所有解析得到的資料來源,包括預設資料來源,其資料型別是一個 Map,Map 的 key 即為 Lookup key
。
初始化動態資料來源
/**
* 初始化定製資料來源
*/
private void initCustomDataSources(Environment env) {
// 讀取配置檔案獲取定製資料來源,也可以通過資料庫獲取資料來源
String dsNames = env.getProperty(customDataSourceKeys);
for (String dsKey : dsNames.split(",")) {
DataSource ds = buildDataSource(env, customDataSourcePrefix + "." + dsKey);
targetDataSources.put(dsKey, ds);
dataBinder(ds, env);
}
}
customDataSourceKeys 即為 spring.datasource.multi.ds-keys
,得到定製資料來源字首後進行構建,之後新增到 targetDataSources
中。
構建資料來源
/**
* 建立 datasource.
*/
@SuppressWarnings("unchecked")
private DataSource buildDataSource(Environment env, String dsPrefix) {
try {
String prefix = dsPrefix + ".";
String dbpType = env.getProperty(prefix + DataSourcePropertyKey.type, defaultDataSourceType);
Class<? extends DataSource> dsType = (Class<? extends DataSource>) Class.forName(dbpType);
DataSource dataSource = DataSourceBuilder.create()
.driverClassName(env.getProperty(prefix + DataSourcePropertyKey.driverClassName))
.url(env.getProperty(prefix + DataSourcePropertyKey.url))
.username(env.getProperty(prefix + DataSourcePropertyKey.username))
.password(env.getProperty(prefix + DataSourcePropertyKey.password))
.type(dsType)
.build();
configDataSourcePool(dataSource);
return dataSource;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
資料來源的構建通過讀取 properties 檔案,並藉助 DataSourceBuilder
類進行構建。configDataSourcePool
方法可對資料來源對應的連線池進行配置。
這裡需要注意的是 spring boot 2.* 對配置檔案的讀取 API 有比較大的變動,可參考這裡。