<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          mybatis-plus多數據源解析

          共 4925字,需瀏覽 10分鐘

           ·

          2021-10-28 23:53

          文章已收錄到我的Github精選,歡迎Star:https://github.com/yehongzhi/learningSummary

          寫在前面

          上一篇文章大致介紹了dynamic-datasource的功能,用起來的確很方便,只需要一個@DS注解,加上一些簡單的配置即可完成多數據源的切換。究竟是怎么做到的呢,底層是怎么實現(xiàn)呢?帶著這個疑問,一起研究了一下源碼。

          由于框架本身功能點比較多,有很多小功能比如支持spel、正則表達式匹配,動態(tài)增刪數據源這種功能的源碼就不去細講了。我們只關心核心的功能,就是多數據源的切換。

          源碼解析

          首先我們都記得,一開始需要引入spring-boot-starter:

          <dependency>
          ????<groupId>com.baomidougroupId>
          ????<artifactId>dynamic-datasource-spring-boot-starterartifactId>
          ????<version>3.3.0version>
          dependency>

          一般starter自動配置,都是從 META-INF/spring.factories文件中指定自動配置類:

          org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
          com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration

          接著打開這個類:

          /**
          ?*?動態(tài)數據源核心自動配置類
          ?*
          ?*?@author?TaoYu?Kanyuxia
          ?*?@see?DynamicDataSourceProvider
          ?*?@see?DynamicDataSourceStrategy
          ?*?@see?DynamicRoutingDataSource
          ?*?@since?1.0.0
          ?*/

          @Slf4j
          @Configuration
          @AllArgsConstructor
          //以spring.datasource.dynamic為前綴讀取配置
          @EnableConfigurationProperties(DynamicDataSourceProperties.class)
          //在SpringBoot注入DataSourceAutoConfigurationbean自動配置之前,先加載注入當前這個類的bean到容器中
          @AutoConfigureBefore(DataSourceAutoConfiguration.class)
          //引入了DruidautoConfig和各種數據源連接池的Creator
          @Import(value?
          =?{DruidDynamicDataSourceConfiguration.class,?DynamicDataSourceCreatorAutoConfiguration.class})
          //條件加載,當前綴是"spring.datasource.dynamic"配置的時候啟用這個autoConfig
          @ConditionalOnProperty(prefix?
          =?DynamicDataSourceProperties.PREFIX,?name?=?"enabled",?havingValue?=?"true",?matchIfMissing?=?true)
          public?class?DynamicDataSourceAutoConfiguration?{
          ?
          ????private?final?DynamicDataSourceProperties?properties;

          ????//讀取多數據源配置,注入到spring容器中
          ????@Bean
          ????@ConditionalOnMissingBean
          ????public?DynamicDataSourceProvider?dynamicDataSourceProvider()?{
          ????????Map?datasourceMap?=?properties.getDatasource();
          ????????return?new?YmlDynamicDataSourceProvider(datasourceMap);
          ????}

          ????//注冊自己的動態(tài)多數據源DataSource
          ????@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;
          ????}

          ????//AOP切面,對DS注解過的方法進行增強,達到切換數據源的目的
          ????@Role(value?=?BeanDefinition.ROLE_INFRASTRUCTURE)
          ????@Bean
          ????@ConditionalOnMissingBean
          ????public?DynamicDataSourceAnnotationAdvisor?dynamicDatasourceAnnotationAdvisor(DsProcessor?dsProcessor)?{
          ????????DynamicDataSourceAnnotationInterceptor?interceptor?=?new?DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(),?dsProcessor);
          ????????DynamicDataSourceAnnotationAdvisor?advisor?=?new?DynamicDataSourceAnnotationAdvisor(interceptor);
          ????????advisor.setOrder(properties.getOrder());
          ????????return?advisor;
          ????}

          ????//關于分布式事務加強
          ????@Role(value?=?BeanDefinition.ROLE_INFRASTRUCTURE)
          ????@ConditionalOnProperty(prefix?=?DynamicDataSourceProperties.PREFIX,?name?=?"seata",?havingValue?=?"false",?matchIfMissing?=?true)
          ????@Bean
          ????public?Advisor?dynamicTransactionAdvisor()?{
          ????????AspectJExpressionPointcut?pointcut?=?new?AspectJExpressionPointcut();
          ????????pointcut.setExpression("@annotation(com.baomidou.dynamic.datasource.annotation.DSTransactional)");
          ????????return?new?DefaultPointcutAdvisor(pointcut,?new?DynamicTransactionAdvisor());
          ????}

          ????//動態(tài)參數解析器鏈
          ????@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;
          ????}

          }

          我們可以發(fā)現(xiàn),在使用的時候配置的前綴為spring.datasource.dynamic的配置都會被讀取到DynamicDataSourceProperties類,作為一個Bean注入到Spring容器。其實這種讀取配置文件信息的方式在日常開發(fā)中也是很常見的。

          @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;
          ????/**
          ?????*?seata使用模式,默認AT
          ?????*/

          ????private?SeataMode?seataMode?=?SeataMode.AT;
          ????/**
          ?????*?是否使用?spring?actuator?監(jiān)控檢查,默認不檢查
          ?????*/

          ????private?boolean?health?=?false;
          ????/**
          ?????*?每一個數據源
          ?????*/

          ????private?Map?datasource?=?new?LinkedHashMap<>();
          ????/**
          ?????*?多數據源選擇算法clazz,默認負載均衡算法
          ?????*/

          ????private?Class?strategy?=?LoadBalanceDynamicDataSourceStrategy.class;
          ????/**
          ?????*?aop切面順序,默認優(yōu)先級最高
          ?????*/

          ????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;
          ????/**
          ?????*?aop?切面是否只允許切?public?方法
          ?????*/

          ????private?boolean?allowedPublicOnly?=?true;
          }

          但是讀取到配置文件怎么讓這些配置文件信息跟spring的DataSource結合起來呢?我們利用反向思維,從結果往回推,要整合一個數據源到spring,是需要實現(xiàn)DataSource接口,那么Mybatis-Plus的動態(tài)數據源也是有實現(xiàn)的,就是這個:

          /**
          ?*?抽象動態(tài)獲取數據源
          ?*
          ?*?@author?TaoYu
          ?*?@since?2.2.0
          ?*/

          public?abstract?class?AbstractRoutingDataSource?extends?AbstractDataSource?{

          ????//抽象方法,由子類實現(xiàn),讓子類決定最終使用的數據源
          ????protected?abstract?DataSource?determineDataSource();

          ????//重寫getConnection()方法,實現(xiàn)切換數據源的功能
          ????@Override
          ????public?Connection?getConnection()?throws?SQLException?{
          ????????//這里xid涉及分布式事務的處理
          ????????String?xid?=?TransactionContext.getXID();
          ????????if?(StringUtils.isEmpty(xid))?{
          ????????????//不使用分布式事務,就是直接返回一個數據連接
          ????????????return?determineDataSource().getConnection();
          ????????}?else?{
          ????????????String?ds?=?DynamicDataSourceContextHolder.peek();
          ????????????ConnectionProxy?connection?=?ConnectionFactory.getConnection(ds);
          ????????????return?connection?==?null???getConnectionProxy(ds,?determineDataSource().getConnection())?:?connection;
          ????????}
          ????}
          }

          上面的源碼如果學過模板模式肯定都熟悉,他把獲取DataSource的行為延伸到子類去實現(xiàn)了,所以關鍵在于看子類的實現(xiàn):

          @Slf4j
          public?class?DynamicRoutingDataSource?extends?AbstractRoutingDataSource?implements?InitializingBean,?DisposableBean?{

          ????private?static?final?String?UNDERLINE?=?"_";
          ????/**
          ?????*?所有數據庫
          ?????*/

          ????private?final?Map?dataSourceMap?=?new?ConcurrentHashMap<>();
          ????/**
          ?????*?分組數據庫
          ?????*/

          ????private?final?Map?groupDataSources?=?new?ConcurrentHashMap<>();
          ????@Setter
          ????private?DynamicDataSourceProvider?provider;
          ????@Setter
          ????private?Class?strategy?=?LoadBalanceDynamicDataSourceStrategy.class;
          ????@Setter
          ????private?String?primary?=?"master";
          ????@Setter
          ????private?Boolean?strict?=?false;
          ????@Setter
          ????private?Boolean?p6spy?=?false;
          ????@Setter
          ????private?Boolean?seata?=?false;

          ????@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);
          ????}
          ????
          ????@Override
          ????public?void?afterPropertiesSet()?throws?Exception?{
          ????????//?檢查開啟了配置但沒有相關依賴
          ????????checkEnv();
          ????????//?添加并分組數據源
          ????????Map?dataSources?=?provider.loadDataSources();
          ????????for?(Map.Entry?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");
          ????????}
          ????}
          }

          他實現(xiàn)了InitializingBean接口,這個接口需要實現(xiàn)afterPropertiesSet()方法,這是一個Bean的生命周期函數,在Bean初始化的時候做一些操作。

          這里做的操作就是檢查配置,然后通過調用provider.loadDataSources()方法獲取到關于DataSource的Map集合,Key是數據源的名稱,Value則是DataSource。

          @Slf4j
          @AllArgsConstructor
          public?class?YmlDynamicDataSourceProvider?extends?AbstractDataSourceProvider?{
          ????/**
          ?????*?所有數據源
          ?????*/

          ????private?final?Map?dataSourcePropertiesMap;

          ????@Override
          ????public?Map?loadDataSources()?{
          ????????//調AbstractDataSourceProvider的createDataSourceMap()方法
          ????????return?createDataSourceMap(dataSourcePropertiesMap);
          ????}
          }

          @Slf4j
          public?abstract?class?AbstractDataSourceProvider?implements?DynamicDataSourceProvider?{

          ????@Autowired
          ????private?DefaultDataSourceCreator?defaultDataSourceCreator;

          ????protected?Map?createDataSourceMap(
          ????????????Map?dataSourcePropertiesMap)
          ?
          {
          ????????Map?dataSourceMap?=?new?HashMap<>(dataSourcePropertiesMap.size()?*?2);
          ????????for?(Map.Entry?item?:?dataSourcePropertiesMap.entrySet())?{
          ????????????DataSourceProperty?dataSourceProperty?=?item.getValue();
          ????????????String?poolName?=?dataSourceProperty.getPoolName();
          ????????????if?(poolName?==?null?||?"".equals(poolName))?{
          ????????????????poolName?=?item.getKey();
          ????????????}
          ????????????dataSourceProperty.setPoolName(poolName);
          ????????????dataSourceMap.put(poolName,?defaultDataSourceCreator.createDataSource(dataSourceProperty));
          ????????}
          ????????return?dataSourceMap;
          ????}
          }

          這里的defaultDataSourceCreator.createDataSource()方法使用到適配器模式。

          因為每種配置數據源創(chuàng)建的DataSource實現(xiàn)類都不一定相同的,所以需要根據配置的數據源類型進行具體的DataSource創(chuàng)建。

          @Override
          public?DataSource?createDataSource(DataSourceProperty?dataSourceProperty,?String?publicKey)?{
          ????DataSourceCreator?dataSourceCreator?=?null;
          ????//this.creators是所有適配的DataSourceCreator實現(xiàn)類
          ????for?(DataSourceCreator?creator?:?this.creators)?{
          ????????//根據配置匹配對應的dataSourceCreator
          ????????if?(creator.support(dataSourceProperty))?{
          ????????????//如果匹配,則使用對應的dataSourceCreator
          ????????????dataSourceCreator?=?creator;
          ????????????break;
          ????????}
          ????}
          ????if?(dataSourceCreator?==?null)?{
          ????????throw?new?IllegalStateException("creator?must?not?be?null,please?check?the?DataSourceCreator");
          ????}
          ????//然后再調用createDataSource方法進行創(chuàng)建對應DataSource
          ????DataSource?dataSource?=?dataSourceCreator.createDataSource(dataSourceProperty,?publicKey);
          ????this.runScrip(dataSource,?dataSourceProperty);
          ????return?wrapDataSource(dataSource,?dataSourceProperty);
          }

          對應的全部實現(xiàn)類是放在creator包下:

          af52cc3bae154c37627a86ca24dce284.webp

          我們看其中一個實現(xiàn)類就:

          @Data
          @AllArgsConstructor
          public?class?HikariDataSourceCreator?extends?AbstractDataSourceCreator?implements?DataSourceCreator?{

          ????private?static?Boolean?hikariExists?=?false;

          ????static?{
          ????????try?{
          ????????????Class.forName(HIKARI_DATASOURCE);
          ????????????hikariExists?=?true;
          ????????}?catch?(ClassNotFoundException?ignored)?{
          ????????}
          ????}

          ????private?HikariCpConfig?hikariCpConfig;

          ????//創(chuàng)建HikariCp數據源
          ????@Override
          ????public?DataSource?createDataSource(DataSourceProperty?dataSourceProperty,?String?publicKey)?{
          ????????if?(StringUtils.isEmpty(dataSourceProperty.getPublicKey()))?{
          ????????????dataSourceProperty.setPublicKey(publicKey);
          ????????}
          ????????HikariConfig?config?=?dataSourceProperty.getHikari().toHikariConfig(hikariCpConfig);
          ????????config.setUsername(dataSourceProperty.getUsername());
          ????????config.setPassword(dataSourceProperty.getPassword());
          ????????config.setJdbcUrl(dataSourceProperty.getUrl());
          ????????config.setPoolName(dataSourceProperty.getPoolName());
          ????????String?driverClassName?=?dataSourceProperty.getDriverClassName();
          ????????if?(!StringUtils.isEmpty(driverClassName))?{
          ????????????config.setDriverClassName(driverClassName);
          ????????}
          ????????return?new?HikariDataSource(config);
          ????}

          ?//判斷是否是HikariCp數據源
          ????@Override
          ????public?boolean?support(DataSourceProperty?dataSourceProperty)?{
          ????????Class?type?=?dataSourceProperty.getType();
          ????????return?(type?==?null?&&?hikariExists)?||?(type?!=?null?&&?HIKARI_DATASOURCE.equals(type.getName()));
          ????}
          }

          再回到之前的,當拿到DataSource的Map集合之后,再做什么呢?

          接著調addDataSource()方法,這個方法是根據下劃線"_"對數據源進行分組,最后放到groupDataSources成員變量里面。

          /**
          ?????*?新數據源添加到分組
          ?????*
          ?????*?@param?ds?????????新數據源的名字
          ?????*?@param?dataSource?新數據源
          ?????*/

          private?void?addGroupDataSource(String?ds,?DataSource?dataSource)?{
          ????if?(ds.contains(UNDERLINE))?{
          ????????String?group?=?ds.split(UNDERLINE)[0];
          ????????GroupDataSource?groupDataSource?=?groupDataSources.get(group);
          ????????if?(groupDataSource?==?null)?{
          ????????????try?{
          ????????????????//順便設置負載均衡策略,strategy默認是LoadBalanceDynamicDataSourceStrategy
          ????????????????groupDataSource?=?new?GroupDataSource(group,?strategy.getDeclaredConstructor().newInstance());
          ????????????????groupDataSources.put(group,?groupDataSource);
          ????????????}?catch?(Exception?e)?{
          ????????????????throw?new?RuntimeException("dynamic-datasource?-?add?the?datasource?named?"?+?ds?+?"?error",?e);
          ????????????}
          ????????}
          ????????groupDataSource.addDatasource(ds,?dataSource);
          ????}
          }

          分組的時候,會順便把負載均衡策略也一起設置進去。這個負載均衡是做什么呢?

          比如一個組master里有三個數據源(A、B、C),需要合理地分配使用的頻率,不可能全都使用某一個,那么這就需要負載均衡策略,默認是輪詢,對應的類是:

          public?class?LoadBalanceDynamicDataSourceStrategy?implements?DynamicDataSourceStrategy?{

          ????/**
          ?????*?負載均衡計數器
          ?????*/

          ????private?final?AtomicInteger?index?=?new?AtomicInteger(0);

          ????@Override
          ????public?DataSource?determineDataSource(List?dataSources)?{
          ????????return?dataSources.get(Math.abs(index.getAndAdd(1)?%?dataSources.size()));
          ????}
          }

          獲取數據源的時候就通過:

          @Override
          public?DataSource?determineDataSource()?{
          ????return?getDataSource(DynamicDataSourceContextHolder.peek());
          }

          /**
          ?????*?獲取數據源
          ?????*
          ?????*?@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();
          }

          那么上面的DynamicDataSourceContextHolder這個類是干嘛的呢?注解@DS的值又是怎么傳進來的呢?

          回到最開始的自動配置類,其中有一個是配置DynamicDataSourceAnnotationAdvisor的,還設置了一個攔截器:

          @Role(value?=?BeanDefinition.ROLE_INFRASTRUCTURE)
          @Bean
          @ConditionalOnMissingBean
          public?DynamicDataSourceAnnotationAdvisor?dynamicDatasourceAnnotationAdvisor(DsProcessor?dsProcessor)?{
          ????//創(chuàng)建攔截器
          ????DynamicDataSourceAnnotationInterceptor?interceptor?=?new?DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(),?dsProcessor);
          ????DynamicDataSourceAnnotationAdvisor?advisor?=?new?DynamicDataSourceAnnotationAdvisor(interceptor);
          ????advisor.setOrder(properties.getOrder());
          ????return?advisor;
          }

          DynamicDataSourceAnnotationAdvisor是用于AOP切面編程的,針對注解@DS的切面進行處理:

          public?class?DynamicDataSourceAnnotationAdvisor?extends?AbstractPointcutAdvisor?implements?BeanFactoryAware?{

          ????//通知
          ????private?final?Advice?advice;

          ????//切入點
          ????private?final?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?=?new?AnnotationMethodPoint(DS.class);
          ????????//方法優(yōu)于類
          ????????return?new?ComposablePointcut(cpc).union(mpc);
          ????}
          }

          切入點我們都清楚了,是@DS注解。那么做了什么處理,主要看advice,也就是傳進來的那個攔截器

          DynamicDataSourceAnnotationInterceptor

          public?class?DynamicDataSourceAnnotationInterceptor?implements?MethodInterceptor?{

          ????/**
          ?????*?The?identification?of?SPEL.
          ?????*/

          ????private?static?final?String?DYNAMIC_PREFIX?=?"#";

          ????private?final?DataSourceClassResolver?dataSourceClassResolver;
          ????private?final?DsProcessor?dsProcessor;

          ????public?DynamicDataSourceAnnotationInterceptor(Boolean?allowedPublicOnly,?DsProcessor?dsProcessor)?{
          ????????dataSourceClassResolver?=?new?DataSourceClassResolver(allowedPublicOnly);
          ????????this.dsProcessor?=?dsProcessor;
          ????}

          ????@Override
          ????public?Object?invoke(MethodInvocation?invocation)?throws?Throwable?{
          ????????//找到@DS注解的屬性值,也就是數據源名稱
          ????????String?dsKey?=?determineDatasourceKey(invocation);
          ????????//把數據源名稱push到當前線程的棧
          ????????DynamicDataSourceContextHolder.push(dsKey);
          ????????try?{
          ????????????//執(zhí)行當前方法
          ????????????return?invocation.proceed();
          ????????}?finally?{
          ????????????//從棧里釋放數據源
          ????????????DynamicDataSourceContextHolder.poll();
          ????????}
          ????}

          ????//這個是使用責任鏈模式進行一些處理,可以先不管他
          ????private?String?determineDatasourceKey(MethodInvocation?invocation)?{
          ????????String?key?=?dataSourceClassResolver.findDSKey(invocation.getMethod(),?invocation.getThis());
          ????????return?(!key.isEmpty()?&&?key.startsWith(DYNAMIC_PREFIX))???dsProcessor.determineDatasource(invocation,?key)?:?key;
          ????}
          }

          這里也有一個DynamicDataSourceContextHolder,這樣就跟前面獲取數據連接關聯(lián)起來了,最后我們看一下這個類的源碼:

          /**
          ?*?核心基于ThreadLocal的切換數據源工具類
          ?*
          ?*?@author?TaoYu?Kanyuxia
          ?*?@since?1.0.0
          ?*/

          public?final?class?DynamicDataSourceContextHolder?{

          ????/**
          ?????*?為什么要用鏈表存儲(準確的是棧)
          ?????*?

          ?????*?為了支持嵌套切換,如ABC三個service都是不同的數據源
          ?????*?其中A的某個業(yè)務要調B的方法,B的方法需要調用C的方法。一級一級調用切換,形成了鏈。
          ?????*?傳統(tǒng)的只設置當前線程的方式不能滿足此業(yè)務需求,必須使用棧,后進先出。
          ?????*?

          ?????*/

          ????private?static?final?ThreadLocal>?LOOKUP_KEY_HOLDER?=?new?NamedThreadLocal>("dynamic-datasource")?{
          ????????@Override
          ????????protected?Deque?initialValue()?{
          ????????????return?new?ArrayDeque<>();
          ????????}
          ????};

          ????private?DynamicDataSourceContextHolder()?{
          ????}

          ????/**
          ?????*?獲得當前線程數據源
          ?????*
          ?????*?@return?數據源名稱
          ?????*/

          ????public?static?String?peek()?{
          ????????return?LOOKUP_KEY_HOLDER.get().peek();
          ????}

          ????/**
          ?????*?設置當前線程數據源
          ?????*?


          ?????*?如非必要不要手動調用,調用后確保最終清除
          ?????*?


          ?????*
          ?????*?@param?ds?數據源名稱
          ?????*/

          ????public?static?void?push(String?ds)?{
          ????????LOOKUP_KEY_HOLDER.get().push(StringUtils.isEmpty(ds)???""?:?ds);
          ????}

          ????/**
          ?????*?清空當前線程數據源
          ?????*?


          ?????*?如果當前線程是連續(xù)切換數據源?只會移除掉當前線程的數據源名稱
          ?????*?


          ?????*/

          ????public?static?void?poll()?{
          ????????Deque?deque?=?LOOKUP_KEY_HOLDER.get();
          ????????deque.poll();
          ????????if?(deque.isEmpty())?{
          ????????????LOOKUP_KEY_HOLDER.remove();
          ????????}
          ????}

          ????/**
          ?????*?強制清空本地線程
          ?????*?


          ?????*?防止內存泄漏,如手動調用了push可調用此方法確保清除
          ?????*?


          ?????*/

          ????public?static?void?clear()?{
          ????????LOOKUP_KEY_HOLDER.remove();
          ????}
          }

          這里為什么使用棧,主要是會存在嵌套切換數據源的情況,也就是最里面那層數據源應該先釋放,最外面那層的數據源應該最后釋放,所以需要用棧的數據結構。

          整體流程

          可能大家還是有點暈,畢竟有點繞,很正常。那么想研究透徹一點,我建議大家自己打開IDEA,參考我寫的去研究一下。這里我畫個整體的流程圖,能有個大概的思路:

          f18d580da2e9c05a7dc4698ded3a60db.webp總結

          源碼解析能提高讀代碼的能力,讀代碼的能力我覺得是很重要的,因為當我們加入一個新公司的時候,對項目不熟悉,那么就需要從文檔,代碼上面去了解項目。讀懂代碼才能去修改、擴展。

          這篇文章介紹的這個框架的源碼解析只是涉及核心代碼,所以不是很難,有興趣的同學可以自己多看幾遍。多數據源的應用在日常項目中也是很常見的場景。

          非常感謝你的閱讀,希望這篇文章能給到你幫助和啟發(fā)。

          覺得有用就點個贊吧,你的點贊是我創(chuàng)作的最大動力~

          我是一個努力讓大家記住的程序員。我們下期再見!!!

          能力有限,如果有什么錯誤或者不當之處,請大家批評指正,一起學習交流!


          瀏覽 99
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九九九精彩视频 | 麻豆videos | 免费无码婬片AAAA片直播 | 天堂av免费 | 一区二区三区性爱视频 |