陕西城乡建设厅网站,怎么做百度网盘链接网站,php 除了做网站,优化免费网站建设本文源码解析基于3.3.1版本。只截了重点代码#xff0c;如果需要看完整代码#xff0c;可以去github拉取。
1 自动配置的实现
一般情况下#xff0c;一个starter的最好入手点就是自动配置类#xff0c;在 META-INF/spring.factories文件中指定自动配置类入口
org.spring…本文源码解析基于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*/BeanConditionalOnMissingBeanpublic DynamicDataSourceProvider dynamicDataSourceProvider() {MapString, DataSourceProperty datasourceMap properties.getDatasource();return new YmlDynamicDataSourceProvider(datasourceMap);}/*** 注册自己的动态多数据源DataSource* param dynamicDataSourceProvider 各种数据源连接池创建者* return DataSource*/BeanConditionalOnMissingBeanpublic 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)BeanConditionalOnMissingBeanpublic 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*/BeanConditionalOnMissingBeanpublic 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 MapString, 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包下的类 见名知意能看出这些就是根据DynamicDataSourceProperties中的配置生成对应连接池这里具体实现暂且不看先看下不同数据源的数据池对象是怎么保存并在使用时获取的 还是最开始的自动配置类中的方法
BeanConditionalOnMissingBeanpublic 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 MapString, DataSource dataSourceMap new ConcurrentHashMap();/*** 分组数据库*/private final MapString, 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 MapString, DataSource getCurrentDataSources() {return dataSourceMap;}/*** 获取的当前所有的分组数据源** return 当前所有的分组数据源*/public MapString, 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.EntryString, DataSource item : dataSourceMap.entrySet()) {closeDataSource(item.getValue());}log.info(dynamic-datasource all closed success,bye);}Overridepublic void afterPropertiesSet() throws Exception {// 检查开启了配置但没有相关依赖checkEnv();// 添加并分组数据源MapString, DataSource dataSources provider.loadDataSources();for (Map.EntryString, 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对象创建: BeanConditionalOnMissingBeanpublic DynamicDataSourceProvider dynamicDataSourceProvider() {MapString, DataSourceProperty datasourceMap properties.getDatasource();return new YmlDynamicDataSourceProvider(datasourceMap);}在自动装配中注入的这个YmlDynamicDataSourceProvider是通过yml读取配置文件生成的调用构造方法时传递进去DynamicDataSourceProperties中的 数据源名称和数据源下面配置(url,username,password等等)的map映射集合 继续跟进看下YmlDynamicDataSourceProvider中的内容除了传递进来的**MapString, DataSourceProperty**外有且只有一个方法
Override
public MapString, DataSource loadDataSources() {return createDataSourceMap(dataSourcePropertiesMap);
}我们继续跟进一直到DefaultDataSourceCreator这个类的createDataSource方法为止上问提到过creator就是用来根据properties配置创建数据池对象的下面看下这个类的代码重点是createDataSource方法
Slf4j
Setter
public class DefaultDataSourceCreator implements DataSourceCreator {private DynamicDataSourceProperties properties;private ListDataSourceCreator 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(ListDataSourceCreator 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 再次返回最开始的自动配置类从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 lets 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的一些机制的使用还有其中涉及到的设计模式和编码方式