一文讀懂Spring動態配置多數據源—源碼詳細分析
Spring動態多數據源源碼分析及解讀
一、為什麼要研究Spring動態多數據源
期初,最開始的原因是:想將答題服務中發送主觀題答題數據給批改中間件這塊抽象出來, 但這塊主要使用的是mq消息的方式發送到批改中間件,所以,最後決定將mq進行抽象,抽象後的結果是:語文,英語,通用任務都能個性化的配置mq,且可以擴展到任何使用mq的業務場景上。終端需要做的就是增加mq配置,自定義消費者業務邏輯方法,調用send方法即可。
這樣做的好處是:原本在每個使用到mq的項目里都要寫一遍mq生產者,mq消費者,發送mq數據,監聽mq消費等動作,且如果一個項目里有多個mq配置,要寫多遍這樣的配置。抽象後,只需要配置文件中進行配置,然後自定義個性化的業務邏輯消費者,就可以進行mq發送了。
這樣一個可動態配置的mq,要求還是挺多的,如何動態配置? 如何能夠在伺服器啟動的時候就啟動n個mq的生產者和消費者? 發送數據的時候, 怎麼找到正確的mq發送呢?
其實, 我一直相信, 我遇到的問題, 肯定有大神已經遇到過, 並且已經有了成熟的解決方案了. 於是, 開始搜索行業內的解決方案, 找了很久也沒找到,最後在同事的提示下,發現Spring動態配置多數據源的思想和我想實現的動態配置多MQ的思想類似。於是,我開始花時間研究Spring動態多數據源的源碼。
二、Spring動態多數據源框架梳理
2.1 框架結構
Spring動態多數據源是一個我們在項目中常用到的組件,尤其是做項目重構,有多種資料庫,不同的請求可能會調用不同的數據源。這時,就需要動態調用指定的數據源。我們來看看Spring動態多數據源的整體框架
上圖中虛線框部分是Spring動態多數據源的幾個組成部分
- ds處理器
- aop切面
- 創建數據源
- 動態數據源提供者
- 動態連接資料庫
除此之外,還可以看到如下資訊:
- Spring動態多數據源是通過動態配置配置文件的方式來指定多數據源的。
- Spring動態多數據源支援四種類型的數據:base數據源,jndi數據源,druid數據源,hikari數據源。
- 多種觸發機制:通過header配置ds,通過session配置ds,通過spel配置ds,其中ds是datasource的簡稱。
- 支援數據源嵌套:一個請求過來,這個請求可能會訪問多個數據源,也就是方法嵌套的時候調用多數據源,也是支援的。
2.2 源碼結構
Spring動態多數據源的幾個組成部分,在程式碼源碼結構中完美的體現出來。
上圖是Spring動態多數據源的源碼項目結構,我們主要列一下主要的結構
----annotation:定義了DS主機
----aop:定義了一個前置通知,切面類
----creator:動態多數據源的創建器
----exception:異常處理
----matcher:匹配器
----processor:ds處理器
----provider:數據員提供者
----spring:spring動態多數據源啟動配置相關類
----toolkit:工具包
----AbstractRoutingDataSource:動態路由數據源抽象類
----DynamicRoutingDataSource:動態路由數據源實現類
2.3 整體項目結構圖
下圖是Spring多態多數據源的程式碼項目結構圖。
這個圖內容比較多,所以字比較小,大概看出一共有6個部分就可以了。後面會就每一個部分詳細說明。
三、項目源碼分析
3.1 引入Spring依賴jar包.
Spring動態多數據源,我們在使用的時候,直接引入jar,然後配置數據源就可以使用了。配置jar包如下
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
然後是在yml配置文件中增加配置
# master
spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.dynamic.datasource.master.username=root
spring.datasource.dynamic.datasource.master.password=123456
# slave
spring.datasource.dynamic.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.dynamic.datasource.slave.url=jdbc:mysql://localhost:3306/test1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.dynamic.datasource.slave.username=root
spring.datasource.dynamic.datasource.slave.password=123456
在測試的時候, 使用了兩個不同的資料庫, 一個是test,一個是test1
3.2 Spring 源碼分析的入口
為什麼引入jar就能在項目里使用了呢?因為在jar包里配置了META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration
在這個文件里,指定了spring動態載入的時候要自動掃描的文件DynamicDataSourceAutoConfiguration,這個文件就是源碼項目的入口了。這裡定義了項目啟動自動裝備DynamicDataSourceAutoConfiguration文件。
接下來,我們就來看看DynamicDataSourceAutoConfiguration文件。
3.3、Spring配置文件入口。
下圖是DynamicDataSourceAutoConfiguration文件的主要內容。
Spring配置文件主要的作用是在系統載入的時候,就載入相關的bean。這裡項目初始化的時候都載入了哪些bean呢?
- 動態數據源屬性類DynamicDataSourceProperties
- 數據源處理器DsProcessor,採用責任鏈設計模式3種方法載入ds
- 動態數據源註解類DynamicDataSourceAnnotationAdvisor,包括前置通知,切面類,切點的載入
- 數據源創建器DataSourceCreator,這個方法是在另一個類被載入的DynamicDataSourceCreatorAutoConfiguration。也是自動配置bean類。可以選擇4種類型的數據源進行創建。
- 數據源提供者Provider,這是動態初始化數據源,讀取yml配置文件,在配置文件中可配置1個或多個數據源。
接下來看一下源程式碼
1. DynamicDataSourceAutoConfiguration動態數據源配置文件
@Slf4j
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class})
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class DynamicDataSourceAutoConfiguration {
private final DynamicDataSourceProperties properties;
@Bean
@ConditionalOnMissingBean
public DynamicDataSourceProvider dynamicDataSourceProvider() {
Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
return new YmlDynamicDataSourceProvider(datasourceMap);
}
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(properties.getPrimary());
dataSource.setStrict(properties.getStrict());
dataSource.setStrategy(properties.getStrategy());
dataSource.setProvider(dynamicDataSourceProvider);
dataSource.setP6spy(properties.getP6spy());
dataSource.setSeata(properties.getSeata());
return dataSource;
}
@Bean
@ConditionalOnMissingBean
public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor();
interceptor.setDsProcessor(dsProcessor);
DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);
advisor.setOrder(properties.getOrder());
return advisor;
}
@Bean
@ConditionalOnMissingBean
public DsProcessor dsProcessor() {
DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
DsSessionProcessor sessionProcessor = new DsSessionProcessor();
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
headerProcessor.setNextProcessor(sessionProcessor);
sessionProcessor.setNextProcessor(spelExpressionProcessor);
return headerProcessor;
}
@Bean
@ConditionalOnBean(DynamicDataSourceConfigure.class)
public DynamicDataSourceAdvisor dynamicAdvisor(DynamicDataSourceConfigure dynamicDataSourceConfigure, DsProcessor dsProcessor) {
DynamicDataSourceAdvisor advisor = new DynamicDataSourceAdvisor(dynamicDataSourceConfigure.getMatchers());
advisor.setDsProcessor(dsProcessor);
advisor.setOrder(Ordered.HIGHEST_PRECEDENCE);
return advisor;
}
}
看到這段程式碼,我們就比較熟悉了,這就是通過註解的方式,在項目啟動的時候,自動注入bean。我們來詳細看一下,他都注入了哪些內容。
- 動態多數據源預置處理器dsProcess,ds就是datasource的簡稱。這裡主要採用的是責任鏈設計模式,獲取ds。
- 動態多數據源註解通知dynamicDatasourceAnnotationAdvisor,這是一個aop前置通知,當一個請求發生的時候,會觸發前置通知,用來確定到底使用哪一個mq消息隊列
- 動態多數據源提供者dynamicDataSourceProvider,我們是動態配置多個數據源,那麼就有一個解析配置的過程,解析配置就是在這裡完成的,解析出多個數據源,然後分別調用數據源創建者去創建數據源。Spring動態多數據源支援數據源的嵌套。
- 動態路由到數據源DynamicRoutingDataSource,當請求過來的時候,也找到對應的數據源了,要建立資料庫連接,資料庫連接的操作就是在這裡完成的。
我們發現在這裡就有四個bean的初始化,並沒有bean的create創建過程,bean的創建過程是在另一個配置類(DynamicDataSourceCreatorAutoConfiguration)中完成的。
@Slf4j
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class DynamicDataSourceCreatorAutoConfiguration {
private final DynamicDataSourceProperties properties;
@Bean
@ConditionalOnMissingBean
public DataSourceCreator dataSourceCreator() {
DataSourceCreator dataSourceCreator = new DataSourceCreator();
dataSourceCreator.setBasicDataSourceCreator(basicDataSourceCreator());
dataSourceCreator.setJndiDataSourceCreator(jndiDataSourceCreator());
dataSourceCreator.setDruidDataSourceCreator(druidDataSourceCreator());
dataSourceCreator.setHikariDataSourceCreator(hikariDataSourceCreator());
dataSourceCreator.setGlobalPublicKey(properties.getPublicKey());
return dataSourceCreator;
}
@Bean
@ConditionalOnMissingBean
public BasicDataSourceCreator basicDataSourceCreator() {
return new BasicDataSourceCreator();
}
@Bean
@ConditionalOnMissingBean
public JndiDataSourceCreator jndiDataSourceCreator() {
return new JndiDataSourceCreator();
}
@Bean
@ConditionalOnMissingBean
public DruidDataSourceCreator druidDataSourceCreator() {
return new DruidDataSourceCreator(properties.getDruid());
}
@Bean
@ConditionalOnMissingBean
public HikariDataSourceCreator hikariDataSourceCreator() {
return new HikariDataSourceCreator(properties.getHikari());
}
}
大概是因為考慮到數據的種類比較多,所以將其單獨放到了一個配置裡面。從上面的源碼可以看出,有四種類型的數據源配置。分別是:basic、jndi、druid、hikari。這四種數據源通過組合設計模式被set到DataSourceCreator中。
接下來,分別來看每一個模組都做了哪些事情。
四、通過責任鏈設計模式獲取數據源名稱
Spring動態多數據源, 獲取數據源名稱的方式有3種,這3中方式採用的是責任鏈方式連續獲取的。首先在header中獲取,header中沒有,去session中獲取, session中也沒有, 通過spel獲取。
上圖是DSProcessor處理器的類圖。 一個介面量, 三個具體實現類,主要來看一下介面類實現
1. DsProcessor 抽象類
package com.baomidou.dynamic.datasource.processor;
import org.aopalliance.intercept.MethodInvocation;
public abstract class DsProcessor {
private DsProcessor nextProcessor;
public void setNextProcessor(DsProcessor dsProcessor) {
this.nextProcessor = dsProcessor;
}
/**
* 抽象匹配條件 匹配才會走當前執行器否則走下一級執行器
*
* @param key DS註解里的內容
* @return 是否匹配
*/
public abstract boolean matches(String key);
/**
* 決定數據源
* <pre>
* 調用底層doDetermineDatasource,
* 如果返回的是null則繼續執行下一個,否則直接返回
* </pre>
*
* @param invocation 方法執行資訊
* @param key DS註解里的內容
* @return 數據源名稱
*/
public String determineDatasource(MethodInvocation invocation, String key) {
if (matches(key)) {
String datasource = doDetermineDatasource(invocation, key);
if (datasource == null && nextProcessor != null) {
return nextProcessor.determineDatasource(invocation, key);
}
return datasource;
}
if (nextProcessor != null) {
return nextProcessor.determineDatasource(invocation, key);
}
return null;
}
/**
* 抽象最終決定數據源
*
* @param invocation 方法執行資訊
* @param key DS註解里的內容
* @return 數據源名稱
*/
public abstract String doDetermineDatasource(MethodInvocation invocation, String key);
}
這裡定義了DsProcessor nextProcessor屬性, 下一個處理器。 判斷是否獲取到了datasource, 如果獲取到了則直接返回, 沒有獲取到,則調用下一個處理器。這個邏輯就是處理器的主邏輯,在determineDatasource(MethodInvocation invocation, String key)方法中實現。
接下來,每一個子類都會自定義實現doDetermineDatasource獲取目標數據源的方法。不同的實現類獲取數據源的方式是不同的。
下面看看具體實現類的主邏輯程式碼
2.DsHeaderProcessor: 從請求的header中獲取ds數據源名稱。
public class DsHeaderProcessor extends DsProcessor {
/**
* header prefix
*/
private static final String HEADER_PREFIX = "#header";
@Override
public boolean matches(String key) {
return key.startsWith(HEADER_PREFIX);
}
@Override
public String doDetermineDatasource(MethodInvocation invocation, String key) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return request.getHeader(key.substring(8));
}
}
3.DsSessionProcessor: 從session中獲取數據源d名稱
public class DsSessionProcessor extends DsProcessor {
/**
* session開頭
*/
private static final String SESSION_PREFIX = "#session";
@Override
public boolean matches(String key) {
return key.startsWith(SESSION_PREFIX);
}
@Override
public String doDetermineDatasource(MethodInvocation invocation, String key) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return request.getSession().getAttribute(key.substring(9)).toString();
}
}
4. DsSpelExpressionProcessor: 通過spel表達式獲取ds數據源名稱
public class DsSpelExpressionProcessor extends DsProcessor {
/**
* 參數發現器
*/
private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
/**
* Express語法解析器
*/
private static final ExpressionParser PARSER = new SpelExpressionParser();
/**
* 解析上下文的模板
* 對於默認不設置的情況下,從參數中取值的方式 #param1
* 設置指定模板 ParserContext.TEMPLATE_EXPRESSION 後的取值方式: #{#param1}
* issues: //github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/199
*/
private ParserContext parserContext = new ParserContext() {
@Override
public boolean isTemplate() {
return false;
}
@Override
public String getExpressionPrefix() {
return null;
}
@Override
public String getExpressionSuffix() {
return null;
}
};
@Override
public boolean matches(String key) {
return true;
}
@Override
public String doDetermineDatasource(MethodInvocation invocation, String key) {
Method method = invocation.getMethod();
Object[] arguments = invocation.getArguments();
EvaluationContext context = new MethodBasedEvaluationContext(null, method, arguments, NAME_DISCOVERER);
final Object value = PARSER.parseExpression(key, parserContext).getValue(context);
return value == null ? null : value.toString();
}
public void setParserContext(ParserContext parserContext) {
this.parserContext = parserContext;
}
}
他們三個的層級關係是在哪裡定義的呢?在DynamicDataSourceAutoConfiguration.java配置文件中
5. DynamicDataSourceAutoConfiguration.java配置文件
@Bean
@ConditionalOnMissingBean
public DsProcessor dsProcessor() {
DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
DsSessionProcessor sessionProcessor = new DsSessionProcessor();
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
headerProcessor.setNextProcessor(sessionProcessor);
sessionProcessor.setNextProcessor(spelExpressionProcessor);
return headerProcessor;
}
第一層是headerProcessor,第二層是sessionProcessor, 第三層是spelExpressionProcessor。層級調用,最後獲得ds。
以上就是對數據源處理器模組的的分析,那麼最終在哪裡被調用呢?來看下一個模組。
五、動態數據源註解通知模組
這一塊對應的源程式碼結構如下:
這個模組里主要有三部分:
- 切面類:DynamicDataSourceAdvisor,DynamicDataSourceAnnotationAdvisor
- 切點類:DynamicAspectJExpressionPointcut,DynamicJdkRegexpMethodPointcut
- 前置通知類:DynamicDataSourceAnnotationInterceptor
他們之間的關係如下。這裡主要是aop方面的知識體系。具體項目結構圖如下:
因為在項目中使用最多的情況是通過註解的方式來解析,所以,我們重點看一下兩個文件
1.DynamicDataSourceAnnotationInterceptor:自定義的前置通知類
public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
/**
* The identification of SPEL.
*/
private static final String DYNAMIC_PREFIX = "#";
private static final DataSourceClassResolver RESOLVER = new DataSourceClassResolver();
@Setter
private DsProcessor dsProcessor;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
DynamicDataSourceContextHolder.push(determineDatasource(invocation));
return invocation.proceed();
} finally {
DynamicDataSourceContextHolder.poll();
}
}
private String determineDatasource(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
DS ds = method.isAnnotationPresent(DS.class) ? method.getAnnotation(DS.class)
: AnnotationUtils.findAnnotation(RESOLVER.targetClass(invocation), DS.class);
String key = ds.value();
return (!key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor.determineDatasource(invocation, key) : key;
}
}
這裡入參中有一個是DsProcessor,也就是ds處理器。在determineDatasource中看看DS的value值是否包含#,如果包含就經過dsProcessor處理後獲得key,如果不包含#則直接返回註解的value值。
2.DynamicDataSourceAnnotationAdvisor 切面類
public class DynamicDataSourceAnnotationAdvisor extends AbstractPointcutAdvisor implements
BeanFactoryAware {
private Advice advice;
private Pointcut pointcut;
public DynamicDataSourceAnnotationAdvisor(@NonNull DynamicDataSourceAnnotationInterceptor dynamicDataSourceAnnotationInterceptor) {
this.advice = dynamicDataSourceAnnotationInterceptor;
this.pointcut = buildPointcut();
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this.advice;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (this.advice instanceof BeanFactoryAware) {
((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
}
}
private Pointcut buildPointcut() {
Pointcut cpc = new AnnotationMatchingPointcut(DS.class, true);
Pointcut mpc = AnnotationMatchingPointcut.forMethodAnnotation(DS.class);
return new ComposablePointcut(cpc).union(mpc);
}
}
在切面類的構造函數中設置了前置通知和切點。這個類在項目啟動的時候就會被載入。所有帶有DS註解的方法都會被掃描,在方法被調用的時候觸發前置通知。
六、數據源創建器
這是最底層的操作了,創建數據源。至於到底創建哪種類型的數據源,是由上層配置決定的,在這裡,定義了4中類型的數據源。 並通過組合的方式,用到那個數據源,就動態的創建哪個數據源。
下面來看這個模組的源程式碼結構:
這裡面定義了一個數據源組合類和四種類型的數據源。我們來看看他們之間的關係
四個基本的數據源類,最後通過DataSourceCreator類組合創建數據源,這裡面使用了簡單工廠模式創建類。下面來一個一個看看
1.BasicDataSourceCreator:基礎數據源創建器
package com.baomidou.dynamic.datasource.creator;
import com.baomidou.dynamic.datasource.exception.ErrorCreateDataSourceException;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import javax.sql.DataSource;
import java.lang.reflect.Method;
/**
* 基礎數據源創建器
*
* @author TaoYu
* @since 2020/1/21
*/
@Data
@Slf4j
public class BasicDataSourceCreator {
private static Method createMethod;
private static Method typeMethod;
private static Method urlMethod;
private static Method usernameMethod;
private static Method passwordMethod;
private static Method driverClassNameMethod;
private static Method buildMethod;
static {
//to support springboot 1.5 and 2.x
Class<?> builderClass = null;
try {
builderClass = Class.forName("org.springframework.boot.jdbc.DataSourceBuilder");
} catch (Exception ignored) {
}
if (builderClass == null) {
try {
builderClass = Class.forName("org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder");
} catch (Exception e) {
log.warn("not in springBoot ENV,could not create BasicDataSourceCreator");
}
}
if (builderClass != null) {
try {
createMethod = builderClass.getDeclaredMethod("create");
typeMethod = builderClass.getDeclaredMethod("type", Class.class);
urlMethod = builderClass.getDeclaredMethod("url", String.class);
usernameMethod = builderClass.getDeclaredMethod("username", String.class);
passwordMethod = builderClass.getDeclaredMethod("password", String.class);
driverClassNameMethod = builderClass.getDeclaredMethod("driverClassName", String.class);
buildMethod = builderClass.getDeclaredMethod("build");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 創建基礎數據源
*
* @param dataSourceProperty 數據源參數
* @return 數據源
*/
public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
try {
Object o1 = createMethod.invoke(null);
Object o2 = typeMethod.invoke(o1, dataSourceProperty.getType());
Object o3 = urlMethod.invoke(o2, dataSourceProperty.getUrl());
Object o4 = usernameMethod.invoke(o3, dataSourceProperty.getUsername());
Object o5 = passwordMethod.invoke(o4, dataSourceProperty.getPassword());
Object o6 = driverClassNameMethod.invoke(o5, dataSourceProperty.getDriverClassName());
return (DataSource) buildMethod.invoke(o6);
} catch (Exception e) {
throw new ErrorCreateDataSourceException(
"dynamic-datasource create basic database named " + dataSourceProperty.getPoolName() + " error");
}
}
}
這裡就有兩塊,一個是類初始化的時候初始化成員變數, 另一個是創建數據源。當被調用createDataSource的時候執行創建數據源,使用的反射機制創建數據源。
2.JndiDataSourceCreator 使用jndi的方式創建數據源
public class JndiDataSourceCreator {
private static final JndiDataSourceLookup LOOKUP = new JndiDataSourceLookup();
/**
* 創建基礎數據源
*
* @param name 數據源參數
* @return 數據源
*/
public DataSource createDataSource(String name) {
return LOOKUP.getDataSource(name);
}
}
這裡通過name查找的方式過去datasource
3.DruidDataSourceCreator: 創建druid類型的數據源
public class DruidDataSourceCreator {
private DruidConfig druidConfig;
@Autowired(required = false)
private ApplicationContext applicationContext;
public DruidDataSourceCreator(DruidConfig druidConfig) {
this.druidConfig = druidConfig;
}
public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(dataSourceProperty.getUsername());
dataSource.setPassword(dataSourceProperty.getPassword());
dataSource.setUrl(dataSourceProperty.getUrl());
dataSource.setDriverClassName(dataSourceProperty.getDriverClassName());
dataSource.setName(dataSourceProperty.getPoolName());
DruidConfig config = dataSourceProperty.getDruid();
Properties properties = config.toProperties(druidConfig);
String filters = properties.getProperty("druid.filters");
List<Filter> proxyFilters = new ArrayList<>(2);
if (!StringUtils.isEmpty(filters) && filters.contains("stat")) {
StatFilter statFilter = new StatFilter();
statFilter.configFromProperties(properties);
proxyFilters.add(statFilter);
}
if (!StringUtils.isEmpty(filters) && filters.contains("wall")) {
WallConfig wallConfig = DruidWallConfigUtil.toWallConfig(dataSourceProperty.getDruid().getWall(), druidConfig.getWall());
WallFilter wallFilter = new WallFilter();
wallFilter.setConfig(wallConfig);
proxyFilters.add(wallFilter);
}
if (!StringUtils.isEmpty(filters) && filters.contains("slf4j")) {
Slf4jLogFilter slf4jLogFilter = new Slf4jLogFilter();
// 由於properties上面被用了,LogFilter不能使用configFromProperties方法,這裡只能一個個set了。
DruidSlf4jConfig slf4jConfig = druidConfig.getSlf4j();
slf4jLogFilter.setStatementLogEnabled(slf4jConfig.getEnable());
slf4jLogFilter.setStatementExecutableSqlLogEnable(slf4jConfig.getStatementExecutableSqlLogEnable());
proxyFilters.add(slf4jLogFilter);
}
if (this.applicationContext != null) {
for (String filterId : druidConfig.getProxyFilters()) {
proxyFilters.add(this.applicationContext.getBean(filterId, Filter.class));
}
}
dataSource.setProxyFilters(proxyFilters);
dataSource.configFromPropety(properties);
//連接參數單獨設置
dataSource.setConnectProperties(config.getConnectionProperties());
//設置druid內置properties不支援的的參數
Boolean testOnReturn = config.getTestOnReturn() == null ? druidConfig.getTestOnReturn() : config.getTestOnReturn();
if (testOnReturn != null && testOnReturn.equals(true)) {
dataSource.setTestOnReturn(true);
}
Integer validationQueryTimeout =
config.getValidationQueryTimeout() == null ? druidConfig.getValidationQueryTimeout() : config.getValidationQueryTimeout();
if (validationQueryTimeout != null && !validationQueryTimeout.equals(-1)) {
dataSource.setValidationQueryTimeout(validationQueryTimeout);
}
Boolean sharePreparedStatements =
config.getSharePreparedStatements() == null ? druidConfig.getSharePreparedStatements() : config.getSharePreparedStatements();
if (sharePreparedStatements != null && sharePreparedStatements.equals(true)) {
dataSource.setSharePreparedStatements(true);
}
Integer connectionErrorRetryAttempts =
config.getConnectionErrorRetryAttempts() == null ? druidConfig.getConnectionErrorRetryAttempts()
: config.getConnectionErrorRetryAttempts();
if (connectionErrorRetryAttempts != null && !connectionErrorRetryAttempts.equals(1)) {
dataSource.setConnectionErrorRetryAttempts(connectionErrorRetryAttempts);
}
Boolean breakAfterAcquireFailure =
config.getBreakAfterAcquireFailure() == null ? druidConfig.getBreakAfterAcquireFailure() : config.getBreakAfterAcquireFailure();
if (breakAfterAcquireFailure != null && breakAfterAcquireFailure.equals(true)) {
dataSource.setBreakAfterAcquireFailure(true);
}
Integer timeout = config.getRemoveAbandonedTimeoutMillis() == null ? druidConfig.getRemoveAbandonedTimeoutMillis()
: config.getRemoveAbandonedTimeoutMillis();
if (timeout != null) {
dataSource.setRemoveAbandonedTimeout(timeout);
}
Boolean abandoned = config.getRemoveAbandoned() == null ? druidConfig.getRemoveAbandoned() : config.getRemoveAbandoned();
if (abandoned != null) {
dataSource.setRemoveAbandoned(abandoned);
}
Boolean logAbandoned = config.getLogAbandoned() == null ? druidConfig.getLogAbandoned() : config.getLogAbandoned();
if (logAbandoned != null) {
dataSource.setLogAbandoned(logAbandoned);
}
Integer queryTimeOut = config.getQueryTimeout() == null ? druidConfig.getQueryTimeout() : config.getQueryTimeout();
if (queryTimeOut != null) {
dataSource.setQueryTimeout(queryTimeOut);
}
Integer transactionQueryTimeout =
config.getTransactionQueryTimeout() == null ? druidConfig.getTransactionQueryTimeout() : config.getTransactionQueryTimeout();
if (transactionQueryTimeout != null) {
dataSource.setTransactionQueryTimeout(transactionQueryTimeout);
}
try {
dataSource.init();
} catch (SQLException e) {
throw new ErrorCreateDataSourceException("druid create error", e);
}
return dataSource;
}
}
其實,這裡面重點方法也是createDataSource(), 如果看不太明白是怎麼創建的,一點關係都沒有,就知道通過這種方式創建了數據源就ok了。
4. HikariDataSourceCreator: 創建Hikari類型的數據源
@Data
@AllArgsConstructor
public class HikariDataSourceCreator {
private HikariCpConfig hikariCpConfig;
public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
HikariConfig config = dataSourceProperty.getHikari().toHikariConfig(hikariCpConfig);
config.setUsername(dataSourceProperty.getUsername());
config.setPassword(dataSourceProperty.getPassword());
config.setJdbcUrl(dataSourceProperty.getUrl());
config.setDriverClassName(dataSourceProperty.getDriverClassName());
config.setPoolName(dataSourceProperty.getPoolName());
return new HikariDataSource(config);
}
}
這裡就不多說了, 就是創建hikari類型的數據源。
5.DataSourceCreator數據源創建器
@Slf4j
@Setter
public class DataSourceCreator {
/**
* 是否存在druid
*/
private static Boolean druidExists = false;
/**
* 是否存在hikari
*/
private static Boolean hikariExists = false;
static {
try {
Class.forName(DRUID_DATASOURCE);
druidExists = true;
log.debug("dynamic-datasource detect druid,Please Notice \n " +
"//github.com/baomidou/dynamic-datasource-spring-boot-starter/wiki/Integration-With-Druid");
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName(HIKARI_DATASOURCE);
hikariExists = true;
} catch (ClassNotFoundException ignored) {
}
}
private BasicDataSourceCreator basicDataSourceCreator;
private JndiDataSourceCreator jndiDataSourceCreator;
private HikariDataSourceCreator hikariDataSourceCreator;
private DruidDataSourceCreator druidDataSourceCreator;
private String globalPublicKey;
/**
* 創建數據源
*
* @param dataSourceProperty 數據源資訊
* @return 數據源
*/
public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
DataSource dataSource;
//如果是jndi數據源
String jndiName = dataSourceProperty.getJndiName();
if (jndiName != null && !jndiName.isEmpty()) {
dataSource = createJNDIDataSource(jndiName);
} else {
Class<? extends DataSource> type = dataSourceProperty.getType();
if (type == null) {
if (druidExists) {
dataSource = createDruidDataSource(dataSourceProperty);
} else if (hikariExists) {
dataSource = createHikariDataSource(dataSourceProperty);
} else {
dataSource = createBasicDataSource(dataSourceProperty);
}
} else if (DRUID_DATASOURCE.equals(type.getName())) {
dataSource = createDruidDataSource(dataSourceProperty);
} else if (HIKARI_DATASOURCE.equals(type.getName())) {
dataSource = createHikariDataSource(dataSourceProperty);
} else {
dataSource = createBasicDataSource(dataSourceProperty);
}
}
this.runScrip(dataSourceProperty, dataSource);
return dataSource;
}
private void runScrip(DataSourceProperty dataSourceProperty, DataSource dataSource) {
String schema = dataSourceProperty.getSchema();
String data = dataSourceProperty.getData();
if (StringUtils.hasText(schema) || StringUtils.hasText(data)) {
ScriptRunner scriptRunner = new ScriptRunner(dataSourceProperty.isContinueOnError(), dataSourceProperty.getSeparator());
if (StringUtils.hasText(schema)) {
scriptRunner.runScript(dataSource, schema);
}
if (StringUtils.hasText(data)) {
scriptRunner.runScript(dataSource, data);
}
}
}
/**
* 創建基礎數據源
*
* @param dataSourceProperty 數據源參數
* @return 數據源
*/
public DataSource createBasicDataSource(DataSourceProperty dataSourceProperty) {
if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
dataSourceProperty.setPublicKey(globalPublicKey);
}
return basicDataSourceCreator.createDataSource(dataSourceProperty);
}
/**
* 創建JNDI數據源
*
* @param jndiName jndi數據源名稱
* @return 數據源
*/
public DataSource createJNDIDataSource(String jndiName) {
return jndiDataSourceCreator.createDataSource(jndiName);
}
/**
* 創建Druid數據源
*
* @param dataSourceProperty 數據源參數
* @return 數據源
*/
public DataSource createDruidDataSource(DataSourceProperty dataSourceProperty) {
if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
dataSourceProperty.setPublicKey(globalPublicKey);
}
return druidDataSourceCreator.createDataSource(dataSourceProperty);
}
/**
* 創建Hikari數據源
*
* @param dataSourceProperty 數據源參數
* @return 數據源
* @author 離世庭院 小鍋蓋
*/
public DataSource createHikariDataSource(DataSourceProperty dataSourceProperty) {
if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
dataSourceProperty.setPublicKey(globalPublicKey);
}
return hikariDataSourceCreator.createDataSource(dataSourceProperty);
}
}
其實仔細看,就是整合了前面四種類型的數據源,通過簡單工廠模式創建實體類。這裡是真正的去調用數據源,開始創建的地方。
通過拆解來看,發現,也並不太難。繼續來看下一個模組。
七、數據源提供者
數據源提供者是連接配置文件和數據源創建器的橋樑。數據源提供者先去讀取配置文件, 將所有的數據源讀取到DynamicDataSourceProperties對象的datasource屬性中,datasource是一個Map集合,可以用來存儲多種類型的數據源。
下面先來看一下數據源提供者的源碼結構:
裡面一共有四個文件,AbstractDataSourceProvider是父類,其他類繼承自這個類,下面來看一下他們的結構
1.AbstractDataSourceProvider是整個動態數據源提供者的的抽象類
public abstract class AbstractDataSourceProvider implements DynamicDataSourceProvider {
@Autowired
private DataSourceCreator dataSourceCreator;
protected Map<String, DataSource> createDataSourceMap(
Map<String, DataSourceProperty> dataSourcePropertiesMap) {
Map<String, DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);
for (Map.Entry<String, DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {
DataSourceProperty dataSourceProperty = item.getValue();
String pollName = dataSourceProperty.getPoolName();
if (pollName == null || "".equals(pollName)) {
pollName = item.getKey();
}
dataSourceProperty.setPoolName(pollName);
dataSourceMap.put(pollName, dataSourceCreator.createDataSource(dataSourceProperty));
}
return dataSourceMap;
}
}
這裡的成員變數是數據源數據源創建者dataSourceCreator. 提供了一個創建數據源的方法:createDataSourceMap(…), 這個方法的入參是屬性配置文件datasources, 返回值是創建的數據源對象結合.
這裡的主要邏輯思想是: 循環遍歷從配置文件讀取的多個數據源, 然後根據數據源的類型, 調用DataSourceCreator數據源創建器去創建(初始化)數據源, 然後返回已經初始化好的數據源,將其保存到map集合中.
2.DynamicDataSourceProvider動態數據源提供者
/**
* 多數據源載入介面,默認的實現為從yml資訊中載入所有數據源 你可以自己實現從其他地方載入所有數據源
*
*/
public interface DynamicDataSourceProvider {
/**
* 載入所有數據源
*
* @return 所有數據源,key為數據源名稱
*/
Map<String, DataSource> loadDataSources();
}
這是一個抽象類, 裡面就提供了一個抽象方法, 載入數據源.
3.YmlDynamicDataSourceProvider使用yml配置文件讀取的方式的動態數據源提供者
@Slf4j
@AllArgsConstructor
public class YmlDynamicDataSourceProvider extends AbstractDataSourceProvider implements DynamicDataSourceProvider {
/**
* 所有數據源
*/
private Map<String, DataSourceProperty> dataSourcePropertiesMap;
@Override
public Map<String, DataSource> loadDataSources() {
return createDataSourceMap(dataSourcePropertiesMap);
}
}
這個源碼也是非常簡單, 繼承了AbstractDataSourceProvider抽象類, 實現了DynamicDataSourceProvider介面. 在loadDataSources()方法中, 創建了多數據源, 並返回多數據源的map集合.
這裡指的一提的是他的成員變數dataSourcePropertiesMap. 這個變數是什麼時候被賦值的呢? 是在項目啟動, 掃描配置文件DynamicDataSourceAutoConfiguration的時候被初始化的.
4.DynamicDataSourceAutoConfiguration
/**
* 動態數據源核心自動配置類
*
* @author TaoYu Kanyuxia
* @see DynamicDataSourceProvider
* @see DynamicDataSourceStrategy
* @see DynamicRoutingDataSource
* @since 1.0.0
*/
@Slf4j
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class})
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class DynamicDataSourceAutoConfiguration {
private final DynamicDataSourceProperties properties;
@Bean
@ConditionalOnMissingBean
public DynamicDataSourceProvider dynamicDataSourceProvider() {
Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
return new YmlDynamicDataSourceProvider(datasourceMap);
}
}
在DynamicDataSourceAutoConfiguration的腦袋上, 有一個註解@EnableConfigurationProperties(DynamicDataSourceProperties.class), 這個註解的作用是自動掃描配置文件,並自動匹配屬性值.
然後,將實例化後的屬性對象賦值給properties成員變數. 下面來看看DynamicDataSourceProperties.java屬性配置文件.
@Slf4j
@Getter
@Setter
@ConfigurationProperties(prefix = DynamicDataSourceProperties.PREFIX)
public class DynamicDataSourceProperties {
public static final String PREFIX = "spring.datasource.dynamic";
public static final String HEALTH = PREFIX + ".health";
/**
* 必須設置默認的庫,默認master
*/
private String primary = "master";
/**
* 是否啟用嚴格模式,默認不啟動. 嚴格模式下未匹配到數據源直接報錯, 非嚴格模式下則使用默認數據源primary所設置的數據源
*/
private Boolean strict = false;
/**
* 是否使用p6spy輸出,默認不輸出
*/
private Boolean p6spy = false;
/**
* 是否使用seata,默認不使用
*/
private Boolean seata = false;
/**
* 是否使用 spring actuator 監控檢查,默認不檢查
*/
private boolean health = false;
/**
* 每一個數據源
*/
private Map<String, DataSourceProperty> datasource = new LinkedHashMap<>();
/**
* 多數據源選擇演算法clazz,默認負載均衡演算法
*/
private Class<? extends DynamicDataSourceStrategy> strategy = LoadBalanceDynamicDataSourceStrategy.class;
/**
* aop切面順序,默認優先順序最高
*/
private Integer order = Ordered.HIGHEST_PRECEDENCE;
/**
* Druid全局參數配置
*/
@NestedConfigurationProperty
private DruidConfig druid = new DruidConfig();
/**
* HikariCp全局參數配置
*/
@NestedConfigurationProperty
private HikariCpConfig hikari = new HikariCpConfig();
/**
* 全局默認publicKey
*/
private String publicKey = CryptoUtils.DEFAULT_PUBLIC_KEY_STRING;
}
這個文件的功能:
- 這個文件定義了掃描yml配置文件的屬性前綴:spring.datasource.dynamic,
- 設置了默認的資料庫是master主庫, strict表示是否嚴格模式: 如果是嚴格模式,那麼沒有配置資料庫,卻調用了會拋異常, 如果非嚴格模式, 沒有配資料庫, 會採用默認的主資料庫.
- datasource: 用來存儲讀取到的數據源, 可能有多個數據源, 所以是map的格式
- strategy: 這裡定義了負載均衡策略, 採用的是策略設計模式: 可以在配置文件中定義, 如果有多個數據源匹配,如何選擇. 可選方案: 1. 負載均衡策略, 2. 隨機策略.
其他參數就不多說, 比較簡單, 見名思意. 以上就是數據源提供者的主要內容了.
八、動態路由數據源
這一塊主要功能是在調用的時候, 進行動態選擇數據源。其源程式碼結構如下圖
我們知道動態數據源可以嵌套,為什麼可以嵌套呢,就是這裡決定的, 這裡一共有四個文件,
1.AbstractRoutingDataSource: 抽象的路由數據源, 這個類主要作用是在找到目標數據源的情況下,連接資料庫.
2.DynamicGroupDataSource:動態分組數據源, 在一個請求鏈接下的所有數據源就是一組. 也就是一個請求過來, 可以嵌套數據源, 這樣數據源就有多個, 這多個就是一組.
- DynamicRoutingDataSource: 動態路由數據源, 第一類AbstractRoutingDataSource用來連接數據源,那麼到底應該鏈接哪個數據源呢?在這個類裡面查找, 如何找呢, 從DynamicDataSourceContextHolder裡面獲取當前執行緒的數據源. 然後鏈接資料庫.
- DynamicDataSourceConfigure: 基於多種策略的自動切換數據源.
這四個文件的結構關係如下:
先來看看數據源連接是如何實現的:
1.AbstractRoutingDataSource: 這是一個抽象類, 裡面主要有兩類方法
一類是具體方法,用來進行資料庫連接
另一類是抽象方法, 給出一個抽象方法, 子類實現決定最終數據源.
public abstract class AbstractRoutingDataSource extends AbstractDataSource {
/**
* 子類實現決定最終數據源
*
* @return 數據源
*/
protected abstract DataSource determineDataSource();
@Override
public Connection getConnection() throws SQLException {
return determineDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineDataSource().getConnection(username, password);
}
}
2.DynamicGroupDataSource: 動態分組數據源,
這裡定義了分組的概念.
- 每一個組有一個組名
- 組裡面有多個數據源, 用list存儲,指的注意的是, list是一個LinkedList,有順序的, 因為在調用資料庫查詢數據的時候, 不能調混了,所以使用順序列表集合.
- 選擇數據源的策略, 有多個數據源,按照什麼策略選擇呢?由策略類型來決定.
public class DynamicGroupDataSource {
private String groupName;
private DynamicDataSourceStrategy dynamicDataSourceStrategy;
private List<DataSource> dataSources = new LinkedList<>();
public DynamicGroupDataSource(String groupName, DynamicDataSourceStrategy dynamicDataSourceStrategy) {
this.groupName = groupName;
this.dynamicDataSourceStrategy = dynamicDataSourceStrategy;
}
public void addDatasource(DataSource dataSource) {
dataSources.add(dataSource);
}
public void removeDatasource(DataSource dataSource) {
dataSources.remove(dataSource);
}
public DataSource determineDataSource() {
return dynamicDataSourceStrategy.determineDataSource(dataSources);
}
public int size() {
return dataSources.size();
}
}
方法的含義都比較好理解,向這個組裡添加數據源,刪除數據源,根據策略尋找目標數據源等.
3.DynamicRoutingDataSource: 這是外部調用的實現類, 這個類繼承自AbstractRoutingDataSource, 所以可以直接調用鏈接資料庫的方法, 並且要重寫獲取目標數據源的方法. 同時採用組合的方式調用了DynamicGroupDataSource動態分組數據源.
除此之外, 還有一個非常用來的資訊, 那就是這個類實現了InitializingBean介面,這個介面提供了一個afterPropertiesSet()方法, 這個方法在bean被初始化完成之後就會被調用. 這裡也是整個項目能夠被載入的重點.
@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean {
private static final String UNDERLINE = "_";
/**
* 所有資料庫
*/
private final Map<String, DataSource> dataSourceMap = new LinkedHashMap<>();
/**
* 分組資料庫
*/
private final Map<String, DynamicGroupDataSource> groupDataSources = new ConcurrentHashMap<>();
@Setter
private DynamicDataSourceProvider provider;
@Setter
private String primary;
@Setter
private boolean strict;
@Setter
private Class<? extends DynamicDataSourceStrategy> strategy;
private boolean p6spy;
private boolean seata;
@Override
public DataSource determineDataSource() {
return getDataSource(DynamicDataSourceContextHolder.peek());
}
private DataSource determinePrimaryDataSource() {
log.debug("dynamic-datasource switch to the primary datasource");
return groupDataSources.containsKey(primary) ? groupDataSources.get(primary).determineDataSource() : dataSourceMap.get(primary);
}
/**
* 獲取當前所有的數據源
*
* @return 當前所有數據源
*/
public Map<String, DataSource> getCurrentDataSources() {
return dataSourceMap;
}
/**
* 獲取的當前所有的分組數據源
*
* @return 當前所有的分組數據源
*/
public Map<String, DynamicGroupDataSource> getCurrentGroupDataSources() {
return groupDataSources;
}
/**
* 獲取數據源
*
* @param ds 數據源名稱
* @return 數據源
*/
public DataSource getDataSource(String ds) {
if (StringUtils.isEmpty(ds)) {
return determinePrimaryDataSource();
} else if (!groupDataSources.isEmpty() && groupDataSources.containsKey(ds)) {
log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
return groupDataSources.get(ds).determineDataSource();
} else if (dataSourceMap.containsKey(ds)) {
log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
return dataSourceMap.get(ds);
}
if (strict) {
throw new RuntimeException("dynamic-datasource could not find a datasource named" + ds);
}
return determinePrimaryDataSource();
}
/**
* 添加數據源
*
* @param ds 數據源名稱
* @param dataSource 數據源
*/
public synchronized void addDataSource(String ds, DataSource dataSource) {
if (!dataSourceMap.containsKey(ds)) {
dataSource = wrapDataSource(ds, dataSource);
dataSourceMap.put(ds, dataSource);
this.addGroupDataSource(ds, dataSource);
log.info("dynamic-datasource - load a datasource named [{}] success", ds);
} else {
log.warn("dynamic-datasource - load a datasource named [{}] failed, because it already exist", ds);
}
}
private void addGroupDataSource(String ds, DataSource dataSource) {
if (ds.contains(UNDERLINE)) {
String group = ds.split(UNDERLINE)[0];
if (groupDataSources.containsKey(group)) {
groupDataSources.get(group).addDatasource(dataSource);
} else {
try {
DynamicGroupDataSource groupDatasource = new DynamicGroupDataSource(group, strategy.newInstance());
groupDatasource.addDatasource(dataSource);
groupDataSources.put(group, groupDatasource);
} catch (Exception e) {
log.error("dynamic-datasource - add the datasource named [{}] error", ds, e);
dataSourceMap.remove(ds);
}
}
}
}
@Override
public void afterPropertiesSet() throws Exception {
Map<String, DataSource> dataSources = provider.loadDataSources();
// 添加並分組數據源
for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) {
addDataSource(dsItem.getKey(), dsItem.getValue());
}
// 檢測默認數據源設置
if (groupDataSources.containsKey(primary)) {
log.info("dynamic-datasource initial loaded [{}] datasource,primary group datasource named [{}]", dataSources.size(), primary);
} else if (dataSourceMap.containsKey(primary)) {
log.info("dynamic-datasource initial loaded [{}] datasource,primary datasource named [{}]", dataSources.size(), primary);
} else {
throw new RuntimeException("dynamic-datasource Please check the setting of primary");
}
}
}
既然afterPropertiesSet()方法這麼重要, 就來看看他主要做了哪些事情吧.
- 通過數據源提供器獲取所有的數據源,
- 將上一步獲得的所有的數據源添加到 dataSourceMap 和 addGroupDataSource 中. 這裡獲取數據源的操作就完成
- 順著這個思路, 如何添加到 dataSourceMap 和 addGroupDataSource中的呢?
private void addGroupDataSource(String ds, DataSource dataSource) {
if (ds.contains(UNDERLINE)) {
String group = ds.split(UNDERLINE)[0];
if (groupDataSources.containsKey(group)) {
groupDataSources.get(group).addDatasource(dataSource);
} else {
try {
DynamicGroupDataSource groupDatasource = new DynamicGroupDataSource(group, strategy.newInstance());
groupDatasource.addDatasource(dataSource);
groupDataSources.put(group, groupDatasource);
} catch (Exception e) {
log.error("dynamic-datasource - add the datasource named [{}] error", ds, e);
dataSourceMap.remove(ds);
}
}
}
}
注意第一句話, if (ds.contains(UNDERLINE)) 只有ds中有下劃線才會走分組數據源. 如果沒有下劃線,則就是按照單個數據源來處理的. 向組裡面添加數據源就不多說了.
除此之外還有一個非常重要的類:DynamicDataSourceContextHolder
public final class DynamicDataSourceContextHolder {
/**
* 為什麼要用鏈表存儲(準確的是棧)
* <pre>
* 為了支援嵌套切換,如ABC三個service都是不同的數據源
* 其中A的某個業務要調B的方法,B的方法需要調用C的方法。一級一級調用切換,形成了鏈。
* 傳統的只設置當前執行緒的方式不能滿足此業務需求,必須使用棧,後進先出。
* </pre>
*/
private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") {
@Override
protected Deque<String> initialValue() {
return new ArrayDeque<>();
}
};
private DynamicDataSourceContextHolder() {
}
/**
* 獲得當前執行緒數據源
*
* @return 數據源名稱
*/
public static String peek() {
return LOOKUP_KEY_HOLDER.get().peek();
}
/**
* 設置當前執行緒數據源
* <p>
* 如非必要不要手動調用,調用後確保最終清除
* </p>
*
* @param ds 數據源名稱
*/
public static void push(String ds) {
LOOKUP_KEY_HOLDER.get().push(StringUtils.isEmpty(ds) ? "" : ds);
}
/**
* 清空當前執行緒數據源
* <p>
* 如果當前執行緒是連續切換數據源 只會移除掉當前執行緒的數據源名稱
* </p>
*/
public static void poll() {
Deque<String> deque = LOOKUP_KEY_HOLDER.get();
deque.poll();
if (deque.isEmpty()) {
LOOKUP_KEY_HOLDER.remove();
}
}
/**
* 強制清空本地執行緒
* <p>
* 防止記憶體泄漏,如手動調用了push可調用此方法確保清除
* </p>
*/
public static void clear() {
LOOKUP_KEY_HOLDER.remove();
}
}
保存了當前執行緒裡面所有的數據源. 使用的是ThreadLocal<Deque
九、總結
以上就是整個數據源源碼的全部內容, 內容比較多, 部分功能描述不是特別詳細. 如有任何疑問, 可以留言, 一起研究.