手撸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
    @ConditionalOnMissingBean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
        Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
        return new YmlDynamicDataSourceProvider(datasourceMap);
    }
 
    /**
     * 注册自己的动态多数据源DataSource
     * @param dynamicDataSourceProvider 各种数据源连接池创建者
     * @return DataSource
     */
    @Bean
    @ConditionalOnMissingBean
    public 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
    @ConditionalOnMissingBean
    public 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)
    @Bean
    public Advisor dynamicTransactionAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("@annotation(com.baomidou.dynamic.datasource.annotation.DSTransactional)");
        return new DefaultPointcutAdvisor(pointcut, new DynamicTransactionAdvisor());
    }
 
    /**
     * 动态参数解析器链
     * @return DsProcessor
     */
    @Bean
    @ConditionalOnMissingBean
    public 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全局参数配置
     */
    @NestedConfigurationProperty
    private DruidConfig druid = new DruidConfig();
    /**
     * HikariCp全局参数配置
     */
    @NestedConfigurationProperty
    private 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
    @ConditionalOnMissingBean
    public 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<>();
    @Setter
    private DynamicDataSourceProvider provider;
    @Setter
    private Class<? extends DynamicDataSourceStrategy> strategy = LoadBalanceDynamicDataSourceStrategy.class;
    @Setter
    private String primary = "master";
    @Setter
    private Boolean strict = false;
    @Setter
    private Boolean p6spy = false;
    @Setter
    private Boolean seata = false;
 
    @Override
    public 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);
        }
    }
 
    @Override
    public 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");
    }
 
    @Override
    public 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
    @ConditionalOnMissingBean
    public 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;

    @Override
    public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
        return createDataSource(dataSourceProperty, properties.getPublicKey());
    }

    @Override
    public 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;
    }

    @Override
    public 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();
    }
 
    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }
 
    @Override
    public Advice getAdvice() {
        return this.advice;
    }
 
    @Override
    public 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;
        }
 
        @Override
        public ClassFilter getClassFilter() {
            return ClassFilter.TRUE;
        }
 
        @Override
        public 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;
            }
 
            @Override
            public 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;
    }
 
    @Override
    public 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的一些机制的使用,还有其中涉及到的设计模式和编码方式

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值