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,参考我写的去研究一下。这里我画个整体的流程图,能有个大概的思路:

总结
源码解析能提高读代码的能力,读代码的能力我觉得是很重要的,因为当我们加入一个新公司的时候,对项目不熟悉,那么就需要从文档,代码上面去了解项目。读懂代码才能去修改、扩展。
这篇文章介绍的这个框架的源码解析只是涉及核心代码,所以不是很难,有兴趣的同学可以自己多看几遍。多数据源的应用在日常项目中也是很常见的场景。
非常感谢你的阅读,希望这篇文章能给到你帮助和启发。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?