依赖:

<? version=\"1.0\" encoding=\"UTF-8\"?>


4.0.0

org.spring work.boot
spring-boot-starter-parent
2.1.1.RELEASE


com.tydic.ohaotian
datasourcetest
0.0.1-SNAPSHOT
datasourcetest
Demo project for Spring Boot

<properties>
    <java.version>1.8</java.version>
</properties>


<dependencies>
    <dependency>
        <groupId>org.spring work.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.spring work.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.spring work.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!--反向工程-->
    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.3.5</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.spring work.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.5</version>
            <configuration>
                <verbose>true</verbose>
                <overwrite>true</overwrite>
            </configuration>
        </plugin>
    </plugins>
</build>

配置文件:
server:
port: 8080
spring:
datasource:
master:
password: 123
url: jdbc:mysql://localhost:3306/taotao?useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username: root
type: com.zaxxer.hikari.HikariDataSource
cluster:
- key: slave1
password: BJtydic_321
url: jdbc:mysql://rm-2zestsji9xm1x0o89do.mysql.rds.aliyuncs.com:3306/zt_s_user?useUnicode=true&characterEncoding=UTF-8
idle-timeout: 20000
driver-class-name: com.mysql.jdbc.Driver
username: dev_commdb
type: com.zaxxer.hikari.HikariDataSource
mybatis:
mapper-locations: classpath:/mapper/*. #扫描xxxmapper. 文件

logging:
level:
com:
yukong:
chapter5:
repository: debug

启动类:
import com.tydic.ohaotian.datasourcetest.dataSource.DynamicDataSourceAnnotationAdvisor;
import com.tydic.ohaotian.datasourcetest.dataSource.DynamicDataSourceAnnotationInterceptor;
import com.tydic.ohaotian.datasourcetest.dataSource.DynamicDataSourceRegister;
import org.mybatis.spring.annotation.MapperScan;
import org.spring work.boot.SpringApplication;
import org.spring work.boot.autoconfigure.SpringBootApplication;
import org.spring work.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.spring work.context.annotation.Bean;
import org.spring work.context.annotation.Import;

@Import(DynamicDataSourceRegister.class)
@MapperScan(“com.tydic.ohaotian.datasourcetest.dao”)
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DatasourcetestApplication {

public static void main(String[] args) {
    SpringApplication.run(DatasourcetestApplication.class, args);
}
@Bean
public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor() {
    return new DynamicDataSourceAnnotationAdvisor(new DynamicDataSourceAnnotationInterceptor());
}

}

多数据源用到的配置类:
package com.tydic.ohaotian.datasourcetest.dataSource;

import java.lang.annotation.*;

/**

  • 切换数据注解 可以用于类或者方法级别 方法级别优先级 > 类级别
    */
    @Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSource {
    String value() default “master”; //该值即key值
    }

package com.tydic.ohaotian.datasourcetest.dataSource;

import org.aopalliance.aop.Advice;
import org.spring work.aop.Pointcut;
import org.spring work.aop.support.AbstractPointcutAdvisor;
import org.spring work.aop.support.ComposablePointcut;
import org.spring work.aop.support.annotation.AnnotationMatchingPointcut;
import org.spring work.beans.BeansException;
import org.spring work.beans.factory.BeanFactory;
import org.spring work.beans.factory.BeanFactoryAware;

/**

  • @Auther: yukong

  • @Date: 2018/8/17 09:14

  • @De ion: aop织入
    */
    public class DynamicDataSourceAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {

    private Advice advice;

    private Pointcut pointcut;

    public DynamicDataSourceAnnotationAdvisor(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 = (Pointcut) new AnnotationMatchingPointcut(DataSource.class, true);
    // 类注解
    Pointcut clpc = (Pointcut) AnnotationMatchingPointcut.forClassAnnotation(DataSource.class);
    // 方法注解
    Pointcut mpc = (Pointcut) AnnotationMatchingPointcut.forMethodAnnotation(DataSource.class);
    return new ComposablePointcut(cpc).union(clpc).union(mpc);
    }

}

package com.tydic.ohaotian.datasourcetest.dataSource;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spring work.core.annotation.AnnotationUtils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**

  • @Auther: yukong

  • @Date: 2018/8/17 09:15

  • @De ion:
    */
    public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAnnotationInterceptor.class);

    /**

    • 缓存方法注解值
      */
      private static final Map<Method, String> METHOD_CACHE = new HashMap<>();

    @Override
    public invoke(MethodInvocation invocation) throws Throwable {
    try {
    String datasource = determineDatasource(invocation);
    if (! DynamicDataSourceContextHolder.containsDataSource(datasource)) {
    logger.info(“数据源[{}]不存在,使用默认数据源 >”, datasource);
    }
    DynamicDataSourceContextHolder.setDataSourceRouterKey(datasource);
    return invocation.proceed();
    } finally {
    DynamicDataSourceContextHolder.removeDataSourceRouterKey();
    }
    }

    private String determineDatasource(MethodInvocation invocation) {
    Method method = invocation.getMethod();
    if (METHOD_CACHE.containsKey(method)) {
    return METHOD_CACHE.get(method);
    } else {
    DataSource ds = method.isAnnotationPresent(DataSource.class) ? method.getAnnotation(DataSource.class)
    : AnnotationUtils.findAnnotation(method.getDeclaringClass(), DataSource.class);
    METHOD_CACHE.put(method, ds.value());
    return ds.value();
    }
    }

}

package com.tydic.ohaotian.datasourcetest.dataSource;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spring work.core.annotation.AnnotationUtils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**

  • @Auther: yukong

  • @Date: 2018/8/17 09:15

  • @De ion:
    */
    public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAnnotationInterceptor.class);

    /**

    • 缓存方法注解值
      */
      private static final Map<Method, String> METHOD_CACHE = new HashMap<>();

    @Override
    public invoke(MethodInvocation invocation) throws Throwable {
    try {
    String datasource = determineDatasource(invocation);
    if (! DynamicDataSourceContextHolder.containsDataSource(datasource)) {
    logger.info(“数据源[{}]不存在,使用默认数据源 >”, datasource);
    }
    DynamicDataSourceContextHolder.setDataSourceRouterKey(datasource);
    return invocation.proceed();
    } finally {
    DynamicDataSourceContextHolder.removeDataSourceRouterKey();
    }
    }

    private String determineDatasource(MethodInvocation invocation) {
    Method method = invocation.getMethod();
    if (METHOD_CACHE.containsKey(method)) {
    return METHOD_CACHE.get(method);
    } else {
    DataSource ds = method.isAnnotationPresent(DataSource.class) ? method.getAnnotation(DataSource.class)
    : AnnotationUtils.findAnnotation(method.getDeclaringClass(), DataSource.class);
    METHOD_CACHE.put(method, ds.value());
    return ds.value();
    }
    }

}

package com.tydic.ohaotian.datasourcetest.dataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**

  • @Auther: yukong

  • @Date: 2018/8/15 10:49

  • @De ion: 数据源上下文
    */
    public class DynamicDataSourceContextHolder {

    private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

    /**

    • 存储已经注册的数据源的key
      */
      public static List dataSourceIds = new ArrayList<>();

    /**

    • 线程级别的私有变量
      */
      private static final ThreadLocal HOLDER = new ThreadLocal<>();

    public static String getDataSourceRouterKey () {
    return HOLDER.get();
    }

    public static void setDataSourceRouterKey (String dataSourceRouterKey) {
    logger.info(“切换至{}数据源”, dataSourceRouterKey);
    HOLDER.set(dataSourceRouterKey);
    }

    /**

    • 设置数据源之前一定要先移除
      */
      public static void removeDataSourceRouterKey () {
      HOLDER.remove();
      }

    /**

    • 判断指定DataSrouce当前是否存在
    • @param dataSourceId
    • @return
      */
      public static boolean containsDataSource(String dataSourceId){
      return dataSourceIds.contains(dataSourceId);
      }

}

package com.tydic.ohaotian.datasourcetest.dataSource;

import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spring work.beans.MutablePropertyValues;
import org.spring work.beans.factory.support.BeanDefinitionRegistry;
import org.spring work.beans.factory.support.GenericBeanDefinition;
import org.spring work.boot.context.properties.bind.Bindable;
import org.spring work.boot.context.properties.bind.Binder;
import org.spring work.boot.context.properties.source.ConfigurationPropertyName;
import org.spring work.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.spring work.boot.context.properties.source.ConfigurationPropertySource;
import org.spring work.boot.context.properties.source.MapConfigurationPropertySource;
import org.spring work.context.EnvironmentAware;
import org.spring work.context.annotation.ImportBeanDefinitionRegistrar;
import org.spring work.core.env.Environment;
import org.spring work.core.type.Annotation data;
import org.spring work.util.StringUtils;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**

  • 动态数据源注册

  • 实现 ImportBeanDefinitionRegistrar 实现数据源注册

  • 实现 EnvironmentAware 用于读取application.yml配置
    */
    public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);

    /**

    • 配置上下文(也可以理解为配置文件的获取工具)
      */
      private Environment evn;

    /**

    • 别名
      */
      private final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();

    /**

    • 由于部分数据源配置不同,所以在此处添加别名,避免切换数据源出现某些参数无法注入的情况
      */
      static {
      aliases.addAliases(“url”, new String[]{“jdbc-url”});
      aliases.addAliases(“username”, new String[]{“user”});
      }

    /**

    • 存储我们注册的数据源
      */
      private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();

    /**

    • 参数绑定工具 springboot2.0新推出
      */
      private Binder binder;

    /**

    • ImportBeanDefinitionRegistrar接口的实现方法,通过该方法可以按照自己的方式注册bean
    • @param annotation data
    • @param beanDefinitionRegistry
      */
      @Override
      public void registerBeanDefinitions(Annotation data annotation data, BeanDefinitionRegistry beanDefinitionRegistry) {
      // 获取所有数据源配置
      Map config, defauleDataSourceProperties;
      defauleDataSourceProperties = binder.bind(“spring.datasource.master”, Map.class).get();
      // 获取数据源类型
      String typeStr = evn.getProperty(“spring.datasource.master.type”);
      // 获取数据源类型
      Class<? extends DataSource> clazz = getDataSourceType(typeStr);
      // 绑定默认数据源参数 也就是主数据源
      DataSource consumerDatasource, defaultDatasource = bind(clazz, defauleDataSourceProperties);
      DynamicDataSourceContextHolder.dataSourceIds.add(“master”);
      logger.info(“注册默认数据源成功”);
      // 获取其他数据源配置
      List configs = binder.bind(“spring.datasource.cluster”, Bindable.listOf(Map.class)).get();
      // 遍历从数据源
      for (int i = 0; i < configs.size(); i++) {
      config = configs.get(i);
      clazz = getDataSourceType((String) config.get(“type”));
      defauleDataSourceProperties = config;
      // 绑定参数
      consumerDatasource = bind(clazz, defauleDataSourceProperties);
      // 获取数据源的key,以便通过该key可以定位到数据源
      String key = config.get(“key”).toString();
      customDataSources.put(key, consumerDatasource);
      // 数据源上下文,用于管理数据源与记录已经注册的数据源key
      DynamicDataSourceContextHolder.dataSourceIds.add(key);
      logger.info(“注册数据源{}成功”, key);
      }
      // bean定义类
      GenericBeanDefinition define = new GenericBeanDefinition();
      // 设置bean的类型,此处DynamicRoutingDataSource是继承AbstractRoutingDataSource的实现类
      define.setBeanClass(DynamicRoutingDataSource.class);
      // 需要注入的参数
      MutablePropertyValues mpv = define.getPropertyValues();
      // 添加默认数据源,避免key不存在的情况没有数据源可用
      mpv.add(“defaultTargetDataSource”, defaultDatasource);
      // 添加其他数据源
      mpv.add(“targetDataSources”, customDataSources);
      // 将该bean注册为datasource,不使用springboot自动生成的datasource
      beanDefinitionRegistry.registerBeanDefinition(“datasource”, define);
      logger.info(“注册数据源成功,一共注册{}个数据源”, customDataSources.keySet().size() + 1);
      }

    /**

    • 通过字符串获取数据源class对象
    • @param typeStr
    • @return
      */
      private Class<? extends DataSource> getDataSourceType(String typeStr) {
      Class<? extends DataSource> type;
      try {
      if (StringUtils.hasLength(typeStr)) {
      // 字符串不为空则通过反射获取class对象
      type = (Class<? extends DataSource>) Class.forName(typeStr);
      } else {
      // 默认为hikariCP数据源,与springboot默认数据源保持一致
      type = HikariDataSource.class;
      }
      return type;
      } catch (Exception e) {
      //无法通过反射获取class对象的情况则抛出异常,该情况一般是写错了,所以此次抛出一个runtimeexception
      throw new IllegalArgumentException(\"can not resolve class with type: \" + typeStr);
      }
      }

    /**

    • 绑定参数,以下三个方法都是参考DataSourceBuilder的bind方法实现的,目的是尽量保证我们自己添加的数据源构造过程与springboot保持一致
    • @param result
    • @param properties
      */
      private void bind(DataSource result, Map properties) {
      ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
      Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
      // 将参数绑定到对象
      binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
      }

    private T bind(Class clazz, Map properties) {
    ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
    Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
    // 通过类型绑定参数并获得实例对象
    return binder.bind(ConfigurationPropertyName.EMPTY, Bindable.of(clazz)).get();
    }

    /**

    • @param clazz
    • @param sourcePath 参数路径,对应配置文件中的值,如: spring.datasource
    • @param
    • @return
      */
      private T bind(Class clazz, String sourcePath) {
      Map properties = binder.bind(sourcePath, Map.class).get();
      return bind(clazz, properties);
      }

    /**

    • EnvironmentAware接口的实现方法,通过aware的方式注入,此处是environment对象
    • @param environment
      */
      @Override
      public void setEnvironment(Environment environment) {
      logger.info(“开始注册数据源”);
      this.evn = environment;
      // 绑定配置器
      binder = Binder.get(evn);
      }

}

package com.tydic.ohaotian.datasourcetest.dataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spring work.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**

  • @Auther: yukong

  • @Date: 2018/8/15 10:47

  • @De ion: 动态数据源路由配置
    */
    public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    private static Logger logger = LoggerFactory.getLogger(DynamicRoutingDataSource.class);

    @Override
    protected determineCurrentLookupKey() {
    String dataSourceName = DynamicDataSourceContextHolder.getDataSourceRouterKey();
    logger.info(“当前数据源是:{}”, dataSourceName);
    return DynamicDataSourceContextHolder.getDataSourceRouterKey();
    }
    }

收藏 打印