文章目录
- MybatisPlus 多数据源 @DS 选择深入源码理解原理
MybatisPlus 多数据源 @DS 选择深入源码理解原理
数据源的选择,拦截器为DynamicDataSourceAnnotationInterceptor
这里利用了一个MethodInterceptor接口,我们看看,我们可以看到这个包是org.aopalliance.intercept通过搜索得知
aopalliance是对AOP和Java有浓厚兴趣的软件开发人员联合成立的开源项目,Spring是按照AOP联盟的规范做的实现,可见Spring是一个集众多基础框架于一身的伟大软件。aopalliance包里面只有接口,没有任何实现,这就是一个规范定义。
下面借用了一张图,spring aop实现了aopalliance的接口
现在我们知道了这个MethodInterceptor会拦截方法,类似于apesctJ里的around环绕通知。我们看看mybatisplus的拦截器是怎么处理的
核心代码就这三行,在方法执行前先确定数据源推入到DynamicDataSourceContextHolder,然后再执行方法,再poll出用过的这个数据源,这个玩意是用来持有当前线程要用哪个数据源的。废话不多说直接看源码
这个玩意很简单,就是用来持有当前线程调每个方法时要用哪个数据源,在方法执行之前把这个方法要用的数据源字符串压入栈,执行完弹出。在数据保存的时候他用的Deque,我们看到初始化的时候用的new ArrayDeque<>(),顺便看看这个ArrayDeque底层,其实就是一个对象数组、队列头、尾,默认数组长度16
那么再看下它进入方法之前是怎么获取数据源字符串的,determineDatasource方法
这个方法也很简单,获取当前被执行方法,如果这个方法上有DS注解,那就用这个注解,没有的话再去看这个方法所在类上有没有DS注解,有的话就用这个注解。有了注解之后还做了个判定是不是动态数据源表达式(DYNAMIC_PREFIX开头的,就是#开头的),如果是用了动态表达式的再执行动态表达式解析。我们点进去看这个抽象类DsProcessor,抽象方法doDetermineDatasource是有三个实现的:请求头处理器,Session处理器,表达式处理器
请求头和Session处理器很简单,就是从请求头里去拿,Session里拿直接的字符串,而表达式处理器则麻烦一些,在这里不深入了,想看的去看DsSpelExpressionProcessor类,就截个图略微看一下把
前面我们看到拦截器是实现MethodInterceptor实现的,那拦截的是哪些方法呢,我们看源码里的自动配置类,我们去看每个框架的时候都可以从关键功能或者自动配置文件去作为入口,在里面我们可以看到有个DynamicDataSourceAnnotationAdvisor,动态数据源注解通知,我们看源码里怎么写的
/*** 动态数据源核心自动配置类** @author TaoYu Kanyuxia* @see DynamicDataSourceProvider* @see DynamicDataSourceStrategy* @see DynamicRoutingDataSource* @since 1.0.0*/
@Slf4j
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class})
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class DynamicDataSourceAutoConfiguration {private final DynamicDataSourceProperties properties;@Bean@ConditionalOnMissingBeanpublic DynamicDataSourceProvider dynamicDataSourceProvider() {Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();return new YmlDynamicDataSourceProvider(datasourceMap);}@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;}@Bean@ConditionalOnMissingBeanpublic DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor();interceptor.setDsProcessor(dsProcessor);DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);advisor.setOrder(properties.getOrder());return advisor;}@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@ConditionalOnBean(DynamicDataSourceConfigure.class)public DynamicDataSourceAdvisor dynamicAdvisor(DynamicDataSourceConfigure dynamicDataSourceConfigure, DsProcessor dsProcessor) {DynamicDataSourceAdvisor advisor = new DynamicDataSourceAdvisor(dynamicDataSourceConfigure.getMatchers());advisor.setDsProcessor(dsProcessor);advisor.setOrder(Ordered.HIGHEST_PRECEDENCE);return advisor;}
}
我们可以看到源码里用spring aop的AnnotationMatchingPointcut 注解匹配切入点来对 方法/类上加了@DS注解的方法做了拦截。
在自动配置类最下面还有个AOP配置是DynamicDataSourceAdvisor,我们看源码理解,构建切入点Pointcut的时候用的DynamicJdkRegexpMethodPointcut,这个是继承的spring aop的JdkRegexpMethodPointcut,并多了matchesCache和ds两个字段。其实就是利用正则表达式来匹配方法,然后决定数据源。配置在配置文件中。而我们平时一般用注解的方式比较多。
/*** Copyright © 2018 organization baomidou* <pre>* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.* <pre/>*/
package com.baomidou.dynamic.datasource.aop;import com.baomidou.dynamic.datasource.matcher.ExpressionMatcher;
import com.baomidou.dynamic.datasource.matcher.Matcher;
import com.baomidou.dynamic.datasource.matcher.RegexMatcher;
import com.baomidou.dynamic.datasource.processor.DsProcessor;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.Setter;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author TaoYu* @since 1.2.0*/
public class DynamicDataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {/*** The identification of SPEL*/private static final String DYNAMIC_PREFIX = "#";@Setterprivate DsProcessor dsProcessor;private Advice advice;private Pointcut pointcut;private Map<String, String> matchesCache = new HashMap<>();public DynamicDataSourceAdvisor(List<Matcher> matchers) {this.pointcut = buildPointcut(matchers);this.advice = buildAdvice();}private Advice buildAdvice() {return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {try {Method method = invocation.getMethod();String methodPath = invocation.getThis().getClass().getName() + "." + method.getName();String key = matchesCache.get(methodPath);if (key != null && !key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) {key = dsProcessor.determineDatasource(invocation, key);}DynamicDataSourceContextHolder.push(key);return invocation.proceed();} finally {DynamicDataSourceContextHolder.poll();}}};}@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(List<Matcher> matchers) {ComposablePointcut composablePointcut = null;for (Matcher matcher : matchers) {if (matcher instanceof RegexMatcher) {RegexMatcher regexMatcher = (RegexMatcher) matcher;Pointcut pointcut = new DynamicJdkRegexpMethodPointcut(regexMatcher.getPattern(), regexMatcher.getDs(), matchesCache);if (composablePointcut == null) {composablePointcut = new ComposablePointcut(pointcut);} else {composablePointcut.union(pointcut);}} else {ExpressionMatcher expressionMatcher = (ExpressionMatcher) matcher;Pointcut pointcut = new DynamicAspectJExpressionPointcut(expressionMatcher.getExpression(), expressionMatcher.getDs(),matchesCache);if (composablePointcut == null) {composablePointcut = new ComposablePointcut(pointcut);} else {composablePointcut.union(pointcut);}}}return composablePointcut;}
}
数据源的确定,数据源的选择都已经知道了,我们看下数据源的加载,在自动配置类里有个DynamicDataSourceProvider我们点进去看其实就是个保存数据源名称和数据源包装对象的map罢了。而这个数据源的配置从哪里来的呢,是从DynamicDataSourceProperties来的,看下面截图,其实就是你在配置文件里配的spring.datasource.dynamic这个前缀下的配置。
我们去Nacos配置里配的就是这样的配置,至此MybatisPlus多数据源的原理你就完整掌握啦