写在前面
上一篇文章大致介绍了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,参考我写的去研究一下。这里我画个整体的流程图,能有个大概的思路:
总结
源码解析能提高读代码的能力,读代码的能力我觉得是很重要的,因为当我们加入一个新公司的时候,对项目不熟悉,那么就需要从文档,代码上面去了解项目。读懂代码才能去修改、扩展。
这篇文章介绍的这个框架的源码解析只是涉及核心代码,所以不是很难,有兴趣的同学可以自己多看几遍。多数据源的应用在日常项目中也是很常见的场景。
非常感谢你的阅读,希望这篇文章能给到你帮助和启发。