Mybatis-Plus多数据源解析

写在前面

上一篇文章大致介绍了dynamic-datasource的功能,用起来的确很方便,只需要一个@DS注解,加上一些简单的配置即可完成多数据源的切换。究竟是怎么做到的呢,底层是怎么实现呢?带着这个疑问,一起研究了一下源码。

由于框架本身功能点比较多,有很多小功能比如支持spel、正则表达式匹配,动态增删数据源这种功能的源码就不去细讲了。我们只关心核心的功能,就是多数据源的切换。

源码解析

首先我们都记得,一开始需要引入spring-boot-starter:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.3.0</version>
</dependency>

一般starter自动配置,都是从 META-INF/spring.factories文件中指定自动配置类:

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

接着打开这个类:

 1 /**
 2  * 动态数据源核心自动配置类
 3  *
 4  * @author TaoYu Kanyuxia
 5  * @see DynamicDataSourceProvider
 6  * @see DynamicDataSourceStrategy
 7  * @see DynamicRoutingDataSource
 8  * @since 1.0.0
 9  */
10 @Slf4j
11 @Configuration
12 @AllArgsConstructor
13 //以spring.datasource.dynamic为前缀读取配置
14 @EnableConfigurationProperties(DynamicDataSourceProperties.class)
15 //在SpringBoot注入DataSourceAutoConfiguration的bean自动配置之前,先加载注入当前这个类的bean到容器中
16 @AutoConfigureBefore(DataSourceAutoConfiguration.class)
17 //引入了Druid的autoConfig和各种数据源连接池的Creator
18 @Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class})
19 //条件加载,当前缀是"spring.datasource.dynamic"配置的时候启用这个autoConfig
20 @ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
21 public class DynamicDataSourceAutoConfiguration {
22     
23     private final DynamicDataSourceProperties properties;
24 
25     //读取多数据源配置,注入到spring容器中
26     @Bean
27     @ConditionalOnMissingBean
28     public DynamicDataSourceProvider dynamicDataSourceProvider() {
29         Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
30         return new YmlDynamicDataSourceProvider(datasourceMap);
31     }
32 
33     //注册自己的动态多数据源DataSource
34     @Bean
35     @ConditionalOnMissingBean
36     public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
37         DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
38         dataSource.setPrimary(properties.getPrimary());
39         dataSource.setStrict(properties.getStrict());
40         dataSource.setStrategy(properties.getStrategy());
41         dataSource.setProvider(dynamicDataSourceProvider);
42         dataSource.setP6spy(properties.getP6spy());
43         dataSource.setSeata(properties.getSeata());
44         return dataSource;
45     }
46 
47     //AOP切面,对DS注解过的方法进行增强,达到切换数据源的目的
48     @Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
49     @Bean
50     @ConditionalOnMissingBean
51     public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
52         DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(), dsProcessor);
53         DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);
54         advisor.setOrder(properties.getOrder());
55         return advisor;
56     }
57 
58     //关于分布式事务加强
59     @Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
60     @ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "seata", havingValue = "false", matchIfMissing = true)
61     @Bean
62     public Advisor dynamicTransactionAdvisor() {
63         AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
64         pointcut.setExpression("@annotation(com.baomidou.dynamic.datasource.annotation.DSTransactional)");
65         return new DefaultPointcutAdvisor(pointcut, new DynamicTransactionAdvisor());
66     }
67 
68     //动态参数解析器链
69     @Bean
70     @ConditionalOnMissingBean
71     public DsProcessor dsProcessor() {
72         DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
73         DsSessionProcessor sessionProcessor = new DsSessionProcessor();
74         DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
75         headerProcessor.setNextProcessor(sessionProcessor);
76         sessionProcessor.setNextProcessor(spelExpressionProcessor);
77         return headerProcessor;
78     }
79 }
我们可以发现,在使用的时候配置的前缀为spring.datasource.dynamic的配置都会被读取到DynamicDataSourceProperties类,作为一个Bean注入到Spring容器。其实这种读取配置文件信息的方式在日常开发中也是很常见的。
 1 @Slf4j
 2 @Getter
 3 @Setter
 4 @ConfigurationProperties(prefix = DynamicDataSourceProperties.PREFIX)
 5 public class DynamicDataSourceProperties {
 6  7     public static final String PREFIX = "spring.datasource.dynamic";
 8     public static final String HEALTH = PREFIX + ".health";
 9 10     /**
11      * 必须设置默认的库,默认master
12      */
13     private String primary = "master";
14     /**
15      * 是否启用严格模式,默认不启动. 严格模式下未匹配到数据源直接报错, 非严格模式下则使用默认数据源primary所设置的数据源
16      */
17     private Boolean strict = false;
18     /**
19      * 是否使用p6spy输出,默认不输出
20      */
21     private Boolean p6spy = false;
22     /**
23      * 是否使用开启seata,默认不开启
24      */
25     private Boolean seata = false;
26     /**
27      * seata使用模式,默认AT
28      */
29     private SeataMode seataMode = SeataMode.AT;
30     /**
31      * 是否使用 spring actuator 监控检查,默认不检查
32      */
33     private boolean health = false;
34     /**
35      * 每一个数据源
36      */
37     private Map<String, DataSourceProperty> datasource = new LinkedHashMap<>();
38     /**
39      * 多数据源选择算法clazz,默认负载均衡算法
40      */
41     private Class<? extends DynamicDataSourceStrategy> strategy = LoadBalanceDynamicDataSourceStrategy.class;
42     /**
43      * aop切面顺序,默认优先级最高
44      */
45     private Integer order = Ordered.HIGHEST_PRECEDENCE;
46     /**
47      * Druid全局参数配置
48      */
49     @NestedConfigurationProperty
50     private DruidConfig druid = new DruidConfig();
51     /**
52      * HikariCp全局参数配置
53      */
54     @NestedConfigurationProperty
55     private HikariCpConfig hikari = new HikariCpConfig();
56 57     /**
58      * 全局默认publicKey
59      */
60     private String publicKey = CryptoUtils.DEFAULT_PUBLIC_KEY_STRING;
61     /**
62      * aop 切面是否只允许切 public 方法
63      */
64     private boolean allowedPublicOnly = true;
65 }

但是读取到配置文件怎么让这些配置文件信息跟spring的DataSource结合起来呢?我们利用反向思维,从结果往回推,要整合一个数据源到spring,是需要实现DataSource接口,那么Mybatis-Plus的动态数据源也是有实现的,就是这个:

 1 /**
 2  * 抽象动态获取数据源
 3  *
 4  * @author TaoYu
 5  * @since 2.2.0
 6  */
 7 public abstract class AbstractRoutingDataSource extends AbstractDataSource {
 8  9     //抽象方法,由子类实现,让子类决定最终使用的数据源
10     protected abstract DataSource determineDataSource();
11 12     //重写getConnection()方法,实现切换数据源的功能
13     @Override
14     public Connection getConnection() throws SQLException {
15         //这里xid涉及分布式事务的处理
16         String xid = TransactionContext.getXID();
17         if (StringUtils.isEmpty(xid)) {
18             //不使用分布式事务,就是直接返回一个数据连接
19             return determineDataSource().getConnection();
20         } else {
21             String ds = DynamicDataSourceContextHolder.peek();
22             ConnectionProxy connection = ConnectionFactory.getConnection(ds);
23             return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection()) : connection;
24         }
25     }
26 }

上面的源码如果学过模板模式肯定都熟悉,他把获取DataSource的行为延伸到子类去实现了,所以关键在于看子类的实现:

 1 @Slf4j
 2 public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean {
 3 
 4     private static final String UNDERLINE = "_";
 5     /**
 6      * 所有数据库
 7      */
 8     private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();
 9     /**
10      * 分组数据库
11      */
12     private final Map<String, GroupDataSource> groupDataSources = new ConcurrentHashMap<>();
13     @Setter
14     private DynamicDataSourceProvider provider;
15     @Setter
16     private Class<? extends DynamicDataSourceStrategy> strategy = LoadBalanceDynamicDataSourceStrategy.class;
17     @Setter
18     private String primary = "master";
19     @Setter
20     private Boolean strict = false;
21     @Setter
22     private Boolean p6spy = false;
23     @Setter
24     private Boolean seata = false;
25 
26     @Override
27     public DataSource determineDataSource() {
28         return getDataSource(DynamicDataSourceContextHolder.peek());
29     }
30     
31     private DataSource determinePrimaryDataSource() {
32         log.debug("dynamic-datasource switch to the primary datasource");
33         return groupDataSources.containsKey(primary) ? groupDataSources.get(primary).determineDataSource() : dataSourceMap.get(primary);
34     }
35     
36     @Override
37     public void afterPropertiesSet() throws Exception {
38         // 检查开启了配置但没有相关依赖
39         checkEnv();
40         // 添加并分组数据源
41         Map<String, DataSource> dataSources = provider.loadDataSources();
42         for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) {
43             addDataSource(dsItem.getKey(), dsItem.getValue());
44         }
45         // 检测默认数据源是否设置
46         if (groupDataSources.containsKey(primary)) {
47             log.info("dynamic-datasource initial loaded [{}] datasource,primary group datasource named [{}]", dataSources.size(), primary);
48         } else if (dataSourceMap.containsKey(primary)) {
49             log.info("dynamic-datasource initial loaded [{}] datasource,primary datasource named [{}]", dataSources.size(), primary);
50         } else {
51             throw new RuntimeException("dynamic-datasource Please check the setting of primary");
52         }
53     }
54 }

他实现了InitializingBean接口,这个接口需要实现afterPropertiesSet()方法,这是一个Bean的生命周期函数,在Bean初始化的时候做一些操作。

这里做的操作就是检查配置,然后通过调用provider.loadDataSources()方法获取到关于DataSource的Map集合,Key是数据源的名称,Value则是DataSource。

 1 @Slf4j
 2 @AllArgsConstructor
 3 public class YmlDynamicDataSourceProvider extends AbstractDataSourceProvider {
 4     /**
 5      * 所有数据源
 6      */
 7     private final Map<String, DataSourceProperty> dataSourcePropertiesMap;
 8 
 9     @Override
10     public Map<String, DataSource> loadDataSources() {
11         //调AbstractDataSourceProvider的createDataSourceMap()方法
12         return createDataSourceMap(dataSourcePropertiesMap);
13     }
14 }
15 
16 @Slf4j
17 public abstract class AbstractDataSourceProvider implements DynamicDataSourceProvider {
18 
19     @Autowired
20     private DefaultDataSourceCreator defaultDataSourceCreator;
21 
22     protected Map<String, DataSource> createDataSourceMap(
23             Map<String, DataSourceProperty> dataSourcePropertiesMap) {
24         Map<String, DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);
25         for (Map.Entry<String, DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {
26             DataSourceProperty dataSourceProperty = item.getValue();
27             String poolName = dataSourceProperty.getPoolName();
28             if (poolName == null || "".equals(poolName)) {
29                 poolName = item.getKey();
30             }
31             dataSourceProperty.setPoolName(poolName);
32             dataSourceMap.put(poolName, defaultDataSourceCreator.createDataSource(dataSourceProperty));
33         }
34         return dataSourceMap;
35     }
36 }

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

因为每种配置数据源创建的DataSource实现类都不一定相同的,所以需要根据配置的数据源类型进行具体的DataSource创建。

 1 @Override
 2 public DataSource createDataSource(DataSourceProperty dataSourceProperty, String publicKey) {
 3     DataSourceCreator dataSourceCreator = null;
 4     //this.creators是所有适配的DataSourceCreator实现类
 5     for (DataSourceCreator creator : this.creators) {
 6         //根据配置匹配对应的dataSourceCreator
 7         if (creator.support(dataSourceProperty)) {
 8             //如果匹配,则使用对应的dataSourceCreator
 9             dataSourceCreator = creator;
10             break;
11         }
12     }
13     if (dataSourceCreator == null) {
14         throw new IllegalStateException("creator must not be null,please check the DataSourceCreator");
15     }
16     //然后再调用createDataSource方法进行创建对应DataSource
17     DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty, publicKey);
18     this.runScrip(dataSource, dataSourceProperty);
19     return wrapDataSource(dataSource, dataSourceProperty);
20 }

对应的全部实现类是放在creator包下:

我们看其中一个实现类就:

 1 @Data
 2 @AllArgsConstructor
 3 public class HikariDataSourceCreator extends AbstractDataSourceCreator implements DataSourceCreator {
 4  5     private static Boolean hikariExists = false;
 6  7     static {
 8         try {
 9             Class.forName(HIKARI_DATASOURCE);
10             hikariExists = true;
11         } catch (ClassNotFoundException ignored) {
12         }
13     }
14 15     private HikariCpConfig hikariCpConfig;
16 17     //创建HikariCp数据源
18     @Override
19     public DataSource createDataSource(DataSourceProperty dataSourceProperty, String publicKey) {
20         if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {
21             dataSourceProperty.setPublicKey(publicKey);
22         }
23         HikariConfig config = dataSourceProperty.getHikari().toHikariConfig(hikariCpConfig);
24         config.setUsername(dataSourceProperty.getUsername());
25         config.setPassword(dataSourceProperty.getPassword());
26         config.setJdbcUrl(dataSourceProperty.getUrl());
27         config.setPoolName(dataSourceProperty.getPoolName());
28         String driverClassName = dataSourceProperty.getDriverClassName();
29         if (!StringUtils.isEmpty(driverClassName)) {
30             config.setDriverClassName(driverClassName);
31         }
32         return new HikariDataSource(config);
33     }
34 35     //判断是否是HikariCp数据源
36     @Override
37     public boolean support(DataSourceProperty dataSourceProperty) {
38         Class<? extends DataSource> type = dataSourceProperty.getType();
39         return (type == null && hikariExists) || (type != null && HIKARI_DATASOURCE.equals(type.getName()));
40     }
41 }

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

接着调addDataSource()方法,这个方法是根据下划线"_"对数据源进行分组,最后放到groupDataSources成员变量里面。

 1     /**
 2      * 新数据源添加到分组
 3      *
 4      * @param ds         新数据源的名字
 5      * @param dataSource 新数据源
 6      */
 7 private void addGroupDataSource(String ds, DataSource dataSource) {
 8     if (ds.contains(UNDERLINE)) {
 9         String group = ds.split(UNDERLINE)[0];
10         GroupDataSource groupDataSource = groupDataSources.get(group);
11         if (groupDataSource == null) {
12             try {
13                 //顺便设置负载均衡策略,strategy默认是LoadBalanceDynamicDataSourceStrategy
14                 groupDataSource = new GroupDataSource(group, strategy.getDeclaredConstructor().newInstance());
15                 groupDataSources.put(group, groupDataSource);
16             } catch (Exception e) {
17                 throw new RuntimeException("dynamic-datasource - add the datasource named " + ds + " error", e);
18             }
19         }
20         groupDataSource.addDatasource(ds, dataSource);
21     }
22 }

分组的时候,会顺便把负载均衡策略也一起设置进去。这个负载均衡是做什么呢?

比如一个组master里有三个数据源(A、B、C),需要合理地分配使用的频率,不可能全都使用某一个,那么这就需要负载均衡策略,默认是轮询,对应的类是:

 1 public class LoadBalanceDynamicDataSourceStrategy implements DynamicDataSourceStrategy {
 2 
 3     /**
 4      * 负载均衡计数器
 5      */
 6     private final AtomicInteger index = new AtomicInteger(0);
 7 
 8     @Override
 9     public DataSource determineDataSource(List<DataSource> dataSources) {
10         return dataSources.get(Math.abs(index.getAndAdd(1) % dataSources.size()));
11     }
12 }

获取数据源的时候就通过:

 1 @Override
 2 public DataSource determineDataSource() {
 3     return getDataSource(DynamicDataSourceContextHolder.peek());
 4 }
 5 
 6 /**
 7      * 获取数据源
 8      *
 9      * @param ds 数据源名称
10      * @return 数据源
11      */
12 public DataSource getDataSource(String ds) {
13     //没有传数据源名称,默认使用主数据源
14     if (StringUtils.isEmpty(ds)) {
15         return determinePrimaryDataSource();
16     //判断分组数据源是否包含,如果包含则从分组数据源获取返回
17     } else if (!groupDataSources.isEmpty() && groupDataSources.containsKey(ds)) {
18         log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
19         return groupDataSources.get(ds).determineDataSource();
20     //如果普通数据源包含,则从普通数据源返回
21     } else if (dataSourceMap.containsKey(ds)) {
22         log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
23         return dataSourceMap.get(ds);
24     }
25     if (strict) {
26         throw new RuntimeException("dynamic-datasource could not find a datasource named" + ds);
27     }
28     return determinePrimaryDataSource();
29 }

那么上面的DynamicDataSourceContextHolder这个类是干嘛的呢?注解@DS的值又是怎么传进来的呢?

回到最开始的自动配置类,其中有一个是配置DynamicDataSourceAnnotationAdvisor的,还设置了一个拦截器:

 1 @Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
 2 @Bean
 3 @ConditionalOnMissingBean
 4 public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
 5     //创建拦截器
 6     DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(), dsProcessor);
 7     DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);
 8     advisor.setOrder(properties.getOrder());
 9     return advisor;
10 }

DynamicDataSourceAnnotationAdvisor是用于AOP切面编程的,针对注解@DS的切面进行处理:

 1 public class DynamicDataSourceAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
 2  3     //通知
 4     private final Advice advice;
 5  6     //切入点
 7     private final Pointcut pointcut;
 8  9     public DynamicDataSourceAnnotationAdvisor(@NonNull DynamicDataSourceAnnotationInterceptor dynamicDataSourceAnnotationInterceptor) {
10         this.advice = dynamicDataSourceAnnotationInterceptor;
11         this.pointcut = buildPointcut();
12     }
13 14     @Override
15     public Pointcut getPointcut() {
16         return this.pointcut;
17     }
18 19     @Override
20     public Advice getAdvice() {
21         return this.advice;
22     }
23 24     @Override
25     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
26         if (this.advice instanceof BeanFactoryAware) {
27             ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
28         }
29     }
30 31     private Pointcut buildPointcut() {
32         //类上面添加了注解
33         Pointcut cpc = new AnnotationMatchingPointcut(DS.class, true);
34         //方法上添加了注解
35         Pointcut mpc = new AnnotationMethodPoint(DS.class);
36         //方法优于类
37         return new ComposablePointcut(cpc).union(mpc);
38     }
39 }

切入点我们都清楚了,是@DS注解。那么做了什么处理,主要看advice,也就是传进来的那个拦截器

DynamicDataSourceAnnotationInterceptor

 1 public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
 2  3     /**
 4      * The identification of SPEL.
 5      */
 6     private static final String DYNAMIC_PREFIX = "#";
 7  8     private final DataSourceClassResolver dataSourceClassResolver;
 9     private final DsProcessor dsProcessor;
10 11     public DynamicDataSourceAnnotationInterceptor(Boolean allowedPublicOnly, DsProcessor dsProcessor) {
12         dataSourceClassResolver = new DataSourceClassResolver(allowedPublicOnly);
13         this.dsProcessor = dsProcessor;
14     }
15 16     @Override
17     public Object invoke(MethodInvocation invocation) throws Throwable {
18         //找到@DS注解的属性值,也就是数据源名称
19         String dsKey = determineDatasourceKey(invocation);
20         //把数据源名称push到当前线程的栈
21         DynamicDataSourceContextHolder.push(dsKey);
22         try {
23             //执行当前方法
24             return invocation.proceed();
25         } finally {
26             //从栈里释放数据源
27             DynamicDataSourceContextHolder.poll();
28         }
29     }
30 31     //这个是使用责任链模式进行一些处理,可以先不管他
32     private String determineDatasourceKey(MethodInvocation invocation) {
33         String key = dataSourceClassResolver.findDSKey(invocation.getMethod(), invocation.getThis());
34         return (!key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor.determineDatasource(invocation, key) : key;
35     }
36 }

这里也有一个DynamicDataSourceContextHolder,这样就跟前面获取数据连接关联起来了,最后我们看一下这个类的源码:

 1 /**
 2  * 核心基于ThreadLocal的切换数据源工具类
 3  *
 4  * @author TaoYu Kanyuxia
 5  * @since 1.0.0
 6  */
 7 public final class DynamicDataSourceContextHolder {
 8  9     /**
10      * 为什么要用链表存储(准确的是栈)
11      * <pre>
12      * 为了支持嵌套切换,如ABC三个service都是不同的数据源
13      * 其中A的某个业务要调B的方法,B的方法需要调用C的方法。一级一级调用切换,形成了链。
14      * 传统的只设置当前线程的方式不能满足此业务需求,必须使用栈,后进先出。
15      * </pre>
16      */
17     private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") {
18         @Override
19         protected Deque<String> initialValue() {
20             return new ArrayDeque<>();
21         }
22     };
23 24     private DynamicDataSourceContextHolder() {
25     }
26 27     /**
28      * 获得当前线程数据源
29      *
30      * @return 数据源名称
31      */
32     public static String peek() {
33         return LOOKUP_KEY_HOLDER.get().peek();
34     }
35 36     /**
37      * 设置当前线程数据源
38      * <p>
39      * 如非必要不要手动调用,调用后确保最终清除
40      * </p>
41      *
42      * @param ds 数据源名称
43      */
44     public static void push(String ds) {
45         LOOKUP_KEY_HOLDER.get().push(StringUtils.isEmpty(ds) ? "" : ds);
46     }
47 48     /**
49      * 清空当前线程数据源
50      * <p>
51      * 如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称
52      * </p>
53      */
54     public static void poll() {
55         Deque<String> deque = LOOKUP_KEY_HOLDER.get();
56         deque.poll();
57         if (deque.isEmpty()) {
58             LOOKUP_KEY_HOLDER.remove();
59         }
60     }
61 62     /**
63      * 强制清空本地线程
64      * <p>
65      * 防止内存泄漏,如手动调用了push可调用此方法确保清除
66      * </p>
67      */
68     public static void clear() {
69         LOOKUP_KEY_HOLDER.remove();
70     }
71 }

这里为什么使用栈,主要是会存在嵌套切换数据源的情况,也就是最里面那层数据源应该先释放,最外面那层的数据源应该最后释放,所以需要用栈的数据结构。

整体流程

可能大家还是有点晕,毕竟有点绕,很正常。那么想研究透彻一点,我建议大家自己打开IDEA,参考我写的去研究一下。这里我画个整体的流程图,能有个大概的思路:

总结

源码解析能提高读代码的能力,读代码的能力我觉得是很重要的,因为当我们加入一个新公司的时候,对项目不熟悉,那么就需要从文档,代码上面去了解项目。读懂代码才能去修改、扩展。

这篇文章介绍的这个框架的源码解析只是涉及核心代码,所以不是很难,有兴趣的同学可以自己多看几遍。多数据源的应用在日常项目中也是很常见的场景。

非常感谢你的阅读,希望这篇文章能给到你帮助和启发。





posted @ 2022-10-19 11:50  Boblim  阅读(1856)  评论(0编辑  收藏  举报