一文讀懂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動態多數據源的幾個組成部分

  1. ds處理器
  2. aop切面
  3. 創建數據源
  4. 動態數據源提供者
  5. 動態連接資料庫

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

  1. Spring動態多數據源是通過動態配置配置文件的方式來指定多數據源的。
  2. Spring動態多數據源支援四種類型的數據:base數據源,jndi數據源,druid數據源,hikari數據源。
  3. 多種觸發機制:通過header配置ds,通過session配置ds,通過spel配置ds,其中ds是datasource的簡稱。
  4. 支援數據源嵌套:一個請求過來,這個請求可能會訪問多個數據源,也就是方法嵌套的時候調用多數據源,也是支援的。

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呢?

  1. 動態數據源屬性類DynamicDataSourceProperties
  2. 數據源處理器DsProcessor,採用責任鏈設計模式3種方法載入ds
  3. 動態數據源註解類DynamicDataSourceAnnotationAdvisor,包括前置通知,切面類,切點的載入
  4. 數據源創建器DataSourceCreator,這個方法是在另一個類被載入的DynamicDataSourceCreatorAutoConfiguration。也是自動配置bean類。可以選擇4種類型的數據源進行創建。
  5. 數據源提供者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。我們來詳細看一下,他都注入了哪些內容。

  1. 動態多數據源預置處理器dsProcess,ds就是datasource的簡稱。這裡主要採用的是責任鏈設計模式,獲取ds。
  2. 動態多數據源註解通知dynamicDatasourceAnnotationAdvisor,這是一個aop前置通知,當一個請求發生的時候,會觸發前置通知,用來確定到底使用哪一個mq消息隊列
  3. 動態多數據源提供者dynamicDataSourceProvider,我們是動態配置多個數據源,那麼就有一個解析配置的過程,解析配置就是在這裡完成的,解析出多個數據源,然後分別調用數據源創建者去創建數據源。Spring動態多數據源支援數據源的嵌套。
  4. 動態路由到數據源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。

以上就是對數據源處理器模組的的分析,那麼最終在哪裡被調用呢?來看下一個模組。

五、動態數據源註解通知模組

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

這個模組里主要有三部分:

  1. 切面類:DynamicDataSourceAdvisor,DynamicDataSourceAnnotationAdvisor
  2. 切點類:DynamicAspectJExpressionPointcut,DynamicJdkRegexpMethodPointcut
  3. 前置通知類: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;
}

這個文件的功能:

  1. 這個文件定義了掃描yml配置文件的屬性前綴:spring.datasource.dynamic,
  2. 設置了默認的資料庫是master主庫, strict表示是否嚴格模式: 如果是嚴格模式,那麼沒有配置資料庫,卻調用了會拋異常, 如果非嚴格模式, 沒有配資料庫, 會採用默認的主資料庫.
  3. datasource: 用來存儲讀取到的數據源, 可能有多個數據源, 所以是map的格式
  4. strategy: 這裡定義了負載均衡策略, 採用的是策略設計模式: 可以在配置文件中定義, 如果有多個數據源匹配,如何選擇. 可選方案: 1. 負載均衡策略, 2. 隨機策略.

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

八、動態路由數據源

這一塊主要功能是在調用的時候, 進行動態選擇數據源。其源程式碼結構如下圖

我們知道動態數據源可以嵌套,為什麼可以嵌套呢,就是這裡決定的, 這裡一共有四個文件,
1.AbstractRoutingDataSource: 抽象的路由數據源, 這個類主要作用是在找到目標數據源的情況下,連接資料庫.
2.DynamicGroupDataSource:動態分組數據源, 在一個請求鏈接下的所有數據源就是一組. 也就是一個請求過來, 可以嵌套數據源, 這樣數據源就有多個, 這多個就是一組.

  1. DynamicRoutingDataSource: 動態路由數據源, 第一類AbstractRoutingDataSource用來連接數據源,那麼到底應該鏈接哪個數據源呢?在這個類裡面查找, 如何找呢, 從DynamicDataSourceContextHolder裡面獲取當前執行緒的數據源. 然後鏈接資料庫.
  2. 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: 動態分組數據源,

這裡定義了分組的概念.

  1. 每一個組有一個組名
  2. 組裡面有多個數據源, 用list存儲,指的注意的是, list是一個LinkedList,有順序的, 因為在調用資料庫查詢數據的時候, 不能調混了,所以使用順序列表集合.
  3. 選擇數據源的策略, 有多個數據源,按照什麼策略選擇呢?由策略類型來決定.
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()方法這麼重要, 就來看看他主要做了哪些事情吧.

  1. 通過數據源提供器獲取所有的數據源,
  2. 將上一步獲得的所有的數據源添加到 dataSourceMap 和 addGroupDataSource 中. 這裡獲取數據源的操作就完成
  3. 順著這個思路, 如何添加到 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>.這個類最主要的含義就是ThreadLocal, 保證每個執行緒獲取的是當前執行緒的數據源.

九、總結

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