手撸dynamic源码详细讲解

本文源码解析基于3.3.1版本。只截了重点代码,如果需要看完整代码,可以去github拉取。

1 自动配置的实现

一般情况下,一个starter的最好入手点就是自动配置类,在 META-INF/spring.factories文件中指定自动配置类入口

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

在spring.factories配置文件中,可以看到这个项目的自动配置类,从核心配置类入手,可以说DynamicDataSourceAutoConfiguration这个是整个程序的main方法,spring启动时会去执行
下面简单的给出DynamicDataSourceAutoConfiguration这个类的核心部分:

/*** 动态数据源核心自动配置类*/
@Slf4j
@Configuration
@AllArgsConstructor
// 读取以spring.datasource.dynamic为前缀的配置
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
// 需要在spring boot的DataSource bean自动配置之前注入我们的DataSource bean
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
// 引入了Druid的autoConfig和各种数据源连接池的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;/*** 多数据源加载接口,默认从yml中读取多数据源配置* @return DynamicDataSourceProvider*/@Bean@ConditionalOnMissingBeanpublic DynamicDataSourceProvider dynamicDataSourceProvider() {Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();return new YmlDynamicDataSourceProvider(datasourceMap);}/*** 注册自己的动态多数据源DataSource* @param dynamicDataSourceProvider 各种数据源连接池创建者* @return DataSource*/@Bean@ConditionalOnMissingBeanpublic 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注解过的方法进行增强,达到切换数据源的目的。* @param dsProcessor 动态参数解析数据源。如果数据源名称以#开头,就会进入这个解析器链。* @return advisor*/@Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)@Bean@ConditionalOnMissingBeanpublic DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {// aop方法拦截器在方法调用前后做操作,设置动态参数解析器DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(), dsProcessor);// 使用AbstractPointcutAdvisor将pointcut和advice连接构成切面DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);advisor.setOrder(properties.getOrder());return advisor;}/*** seata分布式事务支持**/@Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "seata", havingValue = "false", matchIfMissing = true)@Beanpublic Advisor dynamicTransactionAdvisor() {AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("@annotation(com.baomidou.dynamic.datasource.annotation.DSTransactional)");return new DefaultPointcutAdvisor(pointcut, new DynamicTransactionAdvisor());}/*** 动态参数解析器链* @return DsProcessor*/@Bean@ConditionalOnMissingBeanpublic DsProcessor dsProcessor() {DsHeaderProcessor headerProcessor = new DsHeaderProcessor();DsSessionProcessor sessionProcessor = new DsSessionProcessor();DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();headerProcessor.setNextProcessor(sessionProcessor);sessionProcessor.setNextProcessor(spelExpressionProcessor);return headerProcessor;}
}

这里自动配置的几个Bean都是非常重要的
先看下自动配置类上面的注解,比较重要的有如下的:

// 读取以spring.datasource.dynamic为前缀的配置
@EnableConfigurationProperties(DynamicDataSourceProperties.class)

@EnableConfigurationProperties这个注解:使使 @ConfigurationProperties 注解的类生效,主要是用来把properties或者yml配置文件转化为bean来使用,这个在实际使用中非常实用

2 配置文件注入

在跟进DynamicDataSourceProperties中:

@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 监控检查,默认不检查*/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全局参数配置*/@NestedConfigurationPropertyprivate DruidConfig druid = new DruidConfig();/*** HikariCp全局参数配置*/@NestedConfigurationPropertyprivate HikariCpConfig hikari = new HikariCpConfig();/*** 全局默认publicKey*/private String publicKey = CryptoUtils.DEFAULT_PUBLIC_KEY_STRING;/*** aop 切面是否只允许切 public 方法*/private boolean allowedPublicOnly = true;
}

可以发现,就是将spring.datasource.dynamic开头的配置文件注入并创建properties对象,需要注意的是,使用了@NestedConfigurationProperty嵌套了其他的配置类。如果不清楚嵌套的其他配置类是什么,看下DynamicDataSourceProperties这个类中的嵌套properties类。比如DruidConfig,代码较长就不全部粘贴进来了,有兴趣跟进看下。重点是它下面有个toProperties方法,为了实现yml配置中每个dataSource下面的durid可以独立配置(若不独立配置,则使用全局配置),根据全局配置(druid数据池)和独立配置(每个数据源下单独的druid数据源配置)结合转换为Properties,然后在DruidDataSourceCreator类(下面回讲,作用是创建不同的数据源)中根据这个配置创建druid连接池

3 如何集成多种数据池并创建

集成连接池配置项是通过DynamicDataSourceProperties配置类实现的,其中除了上面提到的druid嵌套配置,还有hikari等,但是如何通过这些配置项生成真正的数据源连接池?让我们来看creator包下的类
image.png
见名知意,能看出这些就是根据DynamicDataSourceProperties中的配置,生成对应连接池,这里具体实现暂且不看,先看下不同数据源的数据池对象是怎么保存并在使用时获取的
还是最开始的自动配置类中的方法:

@Bean@ConditionalOnMissingBeanpublic 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;}

这里创建了个DynamicRoutingDataSource,该类实现了InitializingBean接口,在bean初始化时做一些操作。

@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean {private static final String UNDERLINE = "_";/*** 所有数据库*/private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();/*** 分组数据库*/private final Map<String, GroupDataSource> groupDataSources = new ConcurrentHashMap<>();@Setterprivate DynamicDataSourceProvider provider;@Setterprivate Class<? extends DynamicDataSourceStrategy> strategy = LoadBalanceDynamicDataSourceStrategy.class;@Setterprivate String primary = "master";@Setterprivate Boolean strict = false;@Setterprivate Boolean p6spy = false;@Setterprivate Boolean seata = false;@Overridepublic 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, GroupDataSource> 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) {DataSource oldDataSource = dataSourceMap.put(ds, dataSource);// 新数据源添加到分组this.addGroupDataSource(ds, dataSource);// 关闭老的数据源if (oldDataSource != null) {try {closeDataSource(oldDataSource);} catch (Exception e) {log.error("dynamic-datasource - remove the database named [{}]  failed", ds, e);}}log.info("dynamic-datasource - load a datasource named [{}] success", ds);}/*** 新数据源添加到分组** @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 {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);}}/*** 删除数据源** @param ds 数据源名称*/public synchronized void removeDataSource(String ds) {if (!StringUtils.hasText(ds)) {throw new RuntimeException("remove parameter could not be empty");}if (primary.equals(ds)) {throw new RuntimeException("could not remove primary datasource");}if (dataSourceMap.containsKey(ds)) {DataSource dataSource = dataSourceMap.remove(ds);try {closeDataSource(dataSource);} catch (Exception e) {log.error("dynamic-datasource - remove the database named [{}]  failed", ds, e);}if (ds.contains(UNDERLINE)) {String group = ds.split(UNDERLINE)[0];if (groupDataSources.containsKey(group)) {DataSource oldDataSource = groupDataSources.get(group).removeDatasource(ds);if (oldDataSource == null) {if (log.isWarnEnabled()) {log.warn("fail for remove datasource from group. dataSource: {} ,group: {}", ds, group);}}}}log.info("dynamic-datasource - remove the database named [{}] success", ds);} else {log.warn("dynamic-datasource - could not find a database named [{}]", ds);}}/*** 关闭数据源。* <pre>*    从3.2.0开启,如果是原生或使用 DataSourceCreator 创建的数据源会包装成ItemDataSource。*    ItemDataSource保留了最原始的数据源,其可直接关闭。*    如果不是DataSourceCreator创建的数据源则只有尝试解包装再关闭。* </pre>*/private void closeDataSource(DataSource dataSource) throws Exception {if (dataSource instanceof ItemDataSource) {((ItemDataSource) dataSource).close();} else {if (seata && dataSource instanceof DataSourceProxy) {DataSourceProxy dataSourceProxy = (DataSourceProxy) dataSource;dataSource = dataSourceProxy.getTargetDataSource();}if (p6spy && dataSource instanceof P6DataSource) {Field realDataSourceField = P6DataSource.class.getDeclaredField("realDataSource");realDataSourceField.setAccessible(true);dataSource = (DataSource) realDataSourceField.get(dataSource);}Class<? extends DataSource> clazz = dataSource.getClass();Method closeMethod = clazz.getDeclaredMethod("close");closeMethod.invoke(dataSource);}}@Overridepublic void destroy() throws Exception {log.info("dynamic-datasource start closing ....");for (Map.Entry<String, DataSource> item : dataSourceMap.entrySet()) {closeDataSource(item.getValue());}log.info("dynamic-datasource all closed success,bye");}@Overridepublic void afterPropertiesSet() throws Exception {// 检查开启了配置但没有相关依赖checkEnv();// 添加并分组数据源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");}}private void checkEnv() {if (p6spy) {try {Class.forName("com.p6spy.engine.spy.P6DataSource");log.info("dynamic-datasource detect P6SPY plugin and enabled it");} catch (Exception e) {throw new RuntimeException("dynamic-datasource enabled P6SPY ,however without p6spy dependency", e);}}if (seata) {try {Class.forName("io.seata.rm.datasource.DataSourceProxy");log.info("dynamic-datasource detect ALIBABA SEATA and enabled it");} catch (Exception e) {throw new RuntimeException("dynamic-datasource enabled ALIBABA SEATA,however without seata dependency", e);}}}
}

这个类就是核心动态数据源组件。它将DataSource维护在map里,这里重点看如何创建数据源连接池。它所做的操作就是:afterPropertiesSet这个方法在当前Bean对象所有属性设置之后执行的,从provider获取创建好的数据源map,然后解析这个map对其分组。下面来看看这个provider里面是如何创建这个数据源map的
还是返回最开始的自动配置类,看下provider对象创建:

    @Bean@ConditionalOnMissingBeanpublic DynamicDataSourceProvider dynamicDataSourceProvider() {Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();return new YmlDynamicDataSourceProvider(datasourceMap);}

在自动装配中注入的这个YmlDynamicDataSourceProvider,是通过yml读取配置文件生成的,调用构造方法时传递进去DynamicDataSourceProperties中的 数据源名称和数据源下面配置(url,username,password等等)的map映射集合
继续跟进,看下YmlDynamicDataSourceProvider中的内容,除了传递进来的**Map<String, DataSourceProperty>**外,有且只有一个方法:

@Override
public Map<String, DataSource> loadDataSources() {return createDataSourceMap(dataSourcePropertiesMap);
}

我们继续跟进,一直到DefaultDataSourceCreator这个类的createDataSource方法为止,上问提到过creator就是用来根据properties配置创建数据池对象的,下面看下这个类的代码,重点是createDataSource方法:

@Slf4j
@Setter
public class DefaultDataSourceCreator implements DataSourceCreator {private DynamicDataSourceProperties properties;private List<DataSourceCreator> creators;@Overridepublic DataSource createDataSource(DataSourceProperty dataSourceProperty) {return createDataSource(dataSourceProperty, properties.getPublicKey());}@Overridepublic DataSource createDataSource(DataSourceProperty dataSourceProperty, String publicKey) {DataSourceCreator dataSourceCreator = null;for (DataSourceCreator creator : this.creators) {if (creator.support(dataSourceProperty)) {dataSourceCreator = creator;break;}}if (dataSourceCreator == null) {throw new IllegalStateException("creator must not be null,please check the DataSourceCreator");}DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty, publicKey);this.runScrip(dataSource, dataSourceProperty);return wrapDataSource(dataSource, dataSourceProperty);}private void runScrip(DataSource dataSource, DataSourceProperty dataSourceProperty) {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);}}}private DataSource wrapDataSource(DataSource dataSource, DataSourceProperty dataSourceProperty) {String name = dataSourceProperty.getPoolName();DataSource targetDataSource = dataSource;Boolean enabledP6spy = properties.getP6spy() && dataSourceProperty.getP6spy();if (enabledP6spy) {targetDataSource = new P6DataSource(dataSource);log.debug("dynamic-datasource [{}] wrap p6spy plugin", name);}Boolean enabledSeata = properties.getSeata() && dataSourceProperty.getSeata();SeataMode seataMode = properties.getSeataMode();if (enabledSeata) {if (SeataMode.XA == seataMode) {targetDataSource = new DataSourceProxyXA(dataSource);} else {targetDataSource = new DataSourceProxy(dataSource);}log.debug("dynamic-datasource [{}] wrap seata plugin transaction mode [{}]", name, seataMode);}return new ItemDataSource(name, dataSource, targetDataSource, enabledP6spy, enabledSeata, seataMode);}public void setDataSourceCreators(List<DataSourceCreator> dataSourceCreator) {this.creators = dataSourceCreator;}@Overridepublic boolean support(DataSourceProperty dataSourceProperty) {return true;}
}

上文提到过creator见名知意,这里的createDataSource方法,就是根据对应的properties对象中的属性来判断使用哪个creator去创建datasource对象,拿到这里所有的过程都明朗了,这也就是dynamic集成多种数据池创建数据源的关键

4 @DS注解是如何被拦截增强的

先总结下,上面聊了,DynamicRoutingDataSource内部维持了一个map集合存储数据源名称和数据源对象的映射,而这个map映射是provider对象进行的,然后具体的类型的datasource又是由creator进行的
那么具体是如何通过注解实现的切换数据源的,众所周知,spring中注解拦截处理离不开AOP,这里介绍代码中如何使用AOP
image.png
再次返回最开始的自动配置类,从DynamicDataSourceAutoConfiguration入口配置类中dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor)方法入手,该方法注入了一个DynamicDataSourceAnnotationAdvisor类型的bean对象
讲解之前,先简单介绍下spring中的advisor,几个概念:

  • advice: 具体进行的增强
  • pointcut:配置需要增强的规则,可以是切点表达式等
  • advisor: Advice 和 Pointcut 组成的独立的单元,并且能够传给 proxy factory 对象

我们看下DynamicDataSourceAnnotationAdvisor这个切面的代码:

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();}@Overridepublic Pointcut getPointcut() {return this.pointcut;}@Overridepublic Advice getAdvice() {return this.advice;}@Overridepublic 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);// 合并类和方法上添加的注解,类上的注解会绑定到每个方法上。return new ComposablePointcut(cpc).union(mpc);}/*** In order to be compatible with the spring lower than 5.0*/private static class AnnotationMethodPoint implements Pointcut {private final Class<? extends Annotation> annotationType;public AnnotationMethodPoint(Class<? extends Annotation> annotationType) {Assert.notNull(annotationType, "Annotation type must not be null");this.annotationType = annotationType;}@Overridepublic ClassFilter getClassFilter() {return ClassFilter.TRUE;}@Overridepublic MethodMatcher getMethodMatcher() {return new AnnotationMethodMatcher(annotationType);}private static class AnnotationMethodMatcher extends StaticMethodMatcher {private final Class<? extends Annotation> annotationType;public AnnotationMethodMatcher(Class<? extends Annotation> annotationType) {this.annotationType = annotationType;}@Overridepublic boolean matches(Method method, Class<?> targetClass) {if (matchesMethod(method)) {return true;}// Proxy classes never have annotations on their redeclared methods.if (Proxy.isProxyClass(targetClass)) {return false;}// The method may be on an interface, so let's check on the target class as well.Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);return (specificMethod != method && matchesMethod(specificMethod));}private boolean matchesMethod(Method method) {return AnnotatedElementUtils.hasAnnotation(method, this.annotationType);}}}
}

先现在看下@DS注解的advisor实现,在buildPointcut方法里拦截了被@DS注解的方法或类,并且使用ComposablePointcut组合切入点,可以实现方法优先级大于类优先级的特性。DynamicDataSourceAnnotationAdvisor通过构造方法传过来的参数类型是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;}@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {String dsKey = determineDatasourceKey(invocation);// 把获取到的数据源标识(如master)存入本地线程DynamicDataSourceContextHolder.push(dsKey);try {return invocation.proceed();} finally {DynamicDataSourceContextHolder.poll();}}private String determineDatasourceKey(MethodInvocation invocation) {String key = dataSourceClassResolver.findDSKey(invocation.getMethod(), invocation.getThis());// 如果DS注解内容是以#开头,则解析动态最终值;否则,直接返回。return (!key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor.determineDatasource(invocation, key) : key;}
}

这是它的advice通知(也可以说是方法拦截器)执行的动作:在要切换数据源的方法执行前,将“切换的数据源”放入了holder里,等方法执行完后在finally中释放掉,完成当前数据源的切换。该类的determineDatasource()方法决定具体使用哪个数据源

5 总结

通过阅读dynamic源码,熟悉了spring aop、spring事务管理、spring boot自动配置等spring知识点,本质上dynamic的源码并不难,主要是去理解其对spring的一些机制的使用,还有其中涉及到的设计模式和编码方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/745057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CentOS无法解析部分网站(域名)

我正在安装helm软件&#xff0c;参考官方文档&#xff0c;要求下载 get-helm-3 这个文件。 但是我执行该条命令后&#xff0c;报错 连接被拒绝&#xff1a; curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 # curl: (7) Fai…

使用 pg_profile 在 Postgres 中生成性能分析报告

前言&#xff1a; postgres数据库中拥有大量的辅助插件用于帮助DBA更好的分析数据库性能或整个集群&#xff0c;包括索引、I/O、CPU和内存等&#xff0c;pg_profile是基于PostgreSQL标准统计信息视图的诊断工具&#xff0c;它类似于Oracle AWR架构&#xff0c;和Oracle一样&am…

threejs简单创建一个几何体(一)

1.下包引入 //下包 npm install three yarn add three//引入 import * as THREE from three2.创建场景,摄像机 // 1.创建场景const scene new THREE.Scene()// 2.创建摄像机//第一个参数是视角,一般在60-90之间,第二个参数是场景的尺寸,一般取显示器的宽高,第三个参数是开始位…

下载chromedrive,使用自动化

1、先看一下自己浏览器的版本 2、访问 https://googlechromelabs.github.io/chrome-for-testing/

射影几何 -- 摄像机几何 1

三维计算机视觉的主要任务是利用三维物体的二维图像所包含的信息&#xff0c;获取三维物体的空间位置与形状等几何信息&#xff0c;并在此基础上识别三维物体。 摄像机关于空间平面的投影是平面到平面的一个二维中心投影变换 对于空间物体&#xff0c;由于摄像机将三维物体表面…

单例模式( Singleton)——创建型模式

单例模式——创建型模式 什么是单例模式&#xff1f; 单例模式是一种创建型设计模式&#xff0c; 让你能够保证一个类只有一个实例&#xff0c; 并提供一个访问该实例的全局节点。简单来说如果你创建了一个对象&#xff0c; 过一会儿后你决定再创建一个新对象&#xff0c; 此…

中国京津冀太阳能光伏推进大会暨展览会

能源是国民经济发展的重要基础之一。随着国民经济的发展&#xff0c;能源的缺口增大&#xff0c;能源安全及能源在国民经济中的地位越显突出。我国是世界上少数几个能源结构以煤为INVITATION主的国家之一&#xff0c;也是世界上最大的煤炭消费国&#xff0c;燃煤造成的环境污染…

Linux操作系统——常见指令(1)

今天分享一下Linux操作系统常见一些指令。今天介绍 ls pwd cd touch mkdir rmdir rm这几个指令。 ls指令 语法 ls 选项 目录或者文件 功能 对于目录&#xff0c;该命令列出该目录下的所有子目录和文件&#xff0c;对于文件&#xff0c;将列出文件名以及其他信息。 我们常用…

【单调栈】代码随想录算法训练营第六十天 |84.柱状图中最大的矩形(待补充)

84.柱状图中最大的矩形 1、题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 2、文章讲解&#xff1a;代码随想录 3、题目&#xff1a; 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱…

将SQL数据库转换为Mysql数据库

一、准备工作 1、SQL server安装包与已经有数据的mdf、ldf数据库文件&#xff1b; 2、.net Framework安装包&#xff1b;&#xff08;用于支持SQL Server安装的组件&#xff09; 3、MySql安装包&#xff1b;&#xff08;用于目标数据库的环境安装&#xff09; 4、navicat安装包…

基于SpringBoot的“家乡特色推荐系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“家乡特色推荐系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统首页界面图 用户注册界面图 文章分享界面…

WeiPHP Notice/index接口处存在RCE漏洞

产品介绍 WeiPHP是一款基于PHP开发的开源微信公众号开发框架。它提供了丰富的功能和易于使用的接口&#xff0c;使开发者能够快速构建和管理微信公众号应用。WeiPHP支持自定义菜单、消息管理、用户管理、素材管理、支付接口等功能&#xff0c;同时还提供了插件机制和模块化开发…

【DL经典回顾】激活函数大汇总(八)(Maxout Softmin附代码和详细公式)

激活函数大汇总&#xff08;八&#xff09;&#xff08;Maxout & Softmin附代码和详细公式&#xff09; 更多激活函数见激活函数大汇总列表 一、引言 欢迎来到我们深入探索神经网络核心组成部分——激活函数的系列博客。在人工智能的世界里&#xff0c;激活函数扮演着不…

学生时期学习资源同步-1 第一学期结业考试题4

原创作者&#xff1a;田超凡&#xff08;程序员田宝宝&#xff09; 版权所有&#xff0c;引用请注明原作者&#xff0c;严禁复制转载

PBKDF2算法:保障密码安全的利器

title: PBKDF2算法&#xff1a;保障密码安全的利器 date: 2024/3/14 16:40:05 updated: 2024/3/14 16:40:05 tags: PBKDF2算法密码安全性迭代盐值密钥 PBKDF2算法起源&#xff1a; PBKDF2&#xff08;Password-Based Key Derivation Function 2&#xff09;算法是一种基于密码…

如何理解闭包

闭包是编程语言中一个重要的概念&#xff0c;特别是在函数式编程中常常会遇到。以下是对闭包的理解&#xff1a; 1. 定义&#xff1a; 闭包是一种函数&#xff0c;它引用了在其定义范围之外的自由变量&#xff08;非全局变量&#xff09;&#xff0c;并且这些引用的变量在函数…

pip 配置镜像加速安装

在使用pip安装Python第三方库时&#xff0c;默认是使用pip官网的非常慢&#xff0c;可通过配置国内镜像源加速下载速度&#xff0c;以下是如何使用国内镜像源安装Python库的两种常见方式&#xff1a; 临时使用镜像源安装 如果你只是想临时使用某个镜像源安装单个或几个库&…

基于UE官方像素实现的像素流部署程序

写了很多像素流部署的技术文章,但因为大部分做UE的研发人员对于前端都不太熟悉,很难里面NodeJS中的官方像素流插件的使用。为此,我花了两天时间对官方像素流程序进行大量优化,支持以下特性。 单程序,支持windows和linux,无任何其他第三方依赖,双击即可运行完成像素流部署…

string接口[小白理解篇]

作文目的 本文是为了加深对string底层函数的一点理解(请勿与底层源码混为一谈)&#xff0c;下面从模拟与注意项出发。 一.string 功能化模拟 1.迭代器模拟 迭代器&#xff0c;为实现简单便理解故使用指针的方式(非说明迭代器使用该方法实现)。其中的begin、end都是为了给迭代…

LVGL移植到ARM开发板(GEC6818开发板)

LVGL移植到ARM开发板&#xff08;GEC6818开发板&#xff09; 一、LVGL概述 LVGL&#xff08;Light and Versatile Graphics Library&#xff09;是一个开源的图形用户界面库&#xff0c;旨在提供轻量级、可移植、灵活和易于使用的图形用户界面解决方案。 它适用于嵌入式系统…