专栏首页爱撸猫的杰spring boot 多数据源加载原理

spring boot 多数据源加载原理

git代码:https://gitee.com/wwj912790488/multiple-data-sources

DynamicDataSourceAspect切面 必须定义@Order(-10),保证该aop在@Transaction之前执行

配置如下,分别加载三个数据库配置

1.利用ImportBeanDefinitionRegistrar和EnvironmentAware 加载注册多个数据源bean
package org.spring.boot.multiple.ds;

import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

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

/**
 * @author donghongchen
 * @create 2017-09-04 15:34
 * <p>
 * 动态数据源注册
 **/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    //如果配置文件中未指定数据源类型,使用默认值
    private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";

    private ConversionService conversionService = new DefaultConversionService();

    private PropertyValues dataSourcePropertyValues;
    //默认数据源
    private DataSource defaultDataSource;

    private Map<String, DataSource> customDataSources = new HashMap<>();

    /**
     * 加载多数据源配置
     *
     * @param environment
     */
    @Override
    public void setEnvironment(Environment environment) {
        initDefaultDataSource(environment);
        initCustomDataSources(environment);
    }

    private void initDefaultDataSource(Environment environment) {
        //读取主数据源
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, "spring.datasource.");
        Map<String, Object> dsMap = new HashMap<>();
        dsMap.put("type", propertyResolver.getProperty("type"));
        dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
        dsMap.put("url", propertyResolver.getProperty("url"));
        dsMap.put("username", propertyResolver.getProperty("username"));
        dsMap.put("password", propertyResolver.getProperty("password"));
        //创建数据源
        defaultDataSource = buildDataSource(dsMap);
        dataBinder(defaultDataSource, environment);
    }


    private void initCustomDataSources(Environment environment) {
        //读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, "custom.datasource.");
        String dsPrefixs = propertyResolver.getProperty("names");
        if (null == dsPrefixs || "".equals(dsPrefixs)) {
            return;
        }
        String[] dsPrefixsArr = dsPrefixs.split(",");
        for (String dsPrefix : dsPrefixsArr) {
            Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
            DataSource ds = buildDataSource(dsMap);
            customDataSources.put(dsPrefix, ds);
            dataBinder(ds, environment);
        }
    }

    private DataSource buildDataSource(Map<String, Object> dsMap) {
        Object type = dsMap.get("type");
        if (type == null) {
            type = DATASOURCE_TYPE_DEFAULT;
        }
        Class<? extends DataSource> dataSourceType;
        try {
            dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
            String driverClassName = dsMap.get("driverClassName").toString();
            String url = dsMap.get("url").toString();
            String username = dsMap.get("username").toString();
            String password = dsMap.get("password").toString();

            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            dataSource.setDriverClassName(driverClassName);

            return dataSource;

//            DataSourceBuilder factory = DataSourceBuilder.create().
//                    driverClassName(driverClassName).type(dataSourceType).url(url).username(username).password(password);
//            return factory.build();

        } catch (ClassNotFoundException ex) {
            logger.error(ex.getMessage(), ex);
        }
        return null;
    }

    private void dataBinder(DataSource dataSource, Environment environment) {
        RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
        dataBinder.setConversionService(conversionService);
        dataBinder.setIgnoreNestedProperties(false);
        dataBinder.setIgnoreInvalidFields(false);
        dataBinder.setIgnoreUnknownFields(true);
        if (dataSourcePropertyValues == null) {
            Map<String, Object> rpr = new RelaxedPropertyResolver(environment, "spring.datasource").
                    getSubProperties(".");
            Map<String, Object> values = new HashMap<>(rpr);
            //排除已经设置的属性
            values.remove("type");
            values.remove("driverClassName");
            values.remove("url");
            values.remove("username");
            values.remove("password");
            dataSourcePropertyValues = new MutablePropertyValues(values);
        }
        dataBinder.bind(dataSourcePropertyValues);
    }


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> targetDataSource = new HashMap<>();
        //将主数据源添加到更多数据源中
        targetDataSource.put("dataSource", defaultDataSource);
        DynamicDataSourceContextHolder.dataSourceIDS.add("dataSource");

        //添加更多数据源
        targetDataSource.putAll(customDataSources);
        for (String key : customDataSources.keySet()) {
            DynamicDataSourceContextHolder.dataSourceIDS.add(key);
        }
        //创建DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        //添加属性
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSource);
        registry.registerBeanDefinition("dataSource", beanDefinition);

    }


}

根据@annotation 去动态切换数据源

package org.spring.boot.multiple.ds;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author donghongchen
 * @create 2017-09-04 14:44
 * <p>
 * 切换数据源Advice
 **/
@Aspect
@Order(-10) //保证该aop在@Transaction之前执行
@Component
public class DynamicDataSourceAspect {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * * @Before("@annotation(ds)")
     * 的意思是:@Before:在方法执行之前进行执行; @annotation(targetDataSource):会拦截注解targetDataSource的方法,否则不拦截;
     * @param point
     * @param targetDataSource
     */
    @Before("@annotation(targetDataSource)")
    public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource){
        //获取当前的指定数据源
        String dsID = targetDataSource.value();
        //如果不在我们注入的所有的数据源范围内,输出警告信息,系统自动使用默认的数据源
        if (!DynamicDataSourceContextHolder.containsDataSource(dsID)){
            logger.error("数据源["+dsID+"]不存在,使用默认的数据源 > { " + dsID+", 方法签名:"+point.getSignature()+"}");
        }else {
            logger.info("Use DataSource:   {" +dsID+", 方法签名:"+point.getSignature() +"}");
            //找到的话,那么设置动态数据源上下文
            DynamicDataSourceContextHolder.setDataSourceType(dsID);
        }
    }


    @After("@annotation(targetDataSource)")
    public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource){
        //方法执行完毕后,销毁当前数据源信息,进行垃圾回收
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}

最后对应到对应的DAO层调用 。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • springboot多数据源配置

    之前在介绍使用JdbcTemplate和Spring-data-jpa时,都使用了单数据源。在单数据源的情况下,Spring Boot的配置非常简单,只需要在a...

    爱撸猫的杰
  • 线上应用故障排查之一:高CPU占用

    (友情提示:本博文章欢迎转载,但请注明出处:hankchen,http://www.blogjava.net/hankchen)

    爱撸猫的杰
  • 五种线程池的对比与使用

    通过源码可以看出底层调用的是ThreadPoolExecutor方法,传入一个同步的阻塞队列实现缓存。

    爱撸猫的杰
  • 不要再写这样的神级代码了!

    JDK8提供的Stream虽然好用,Lambda虽然简洁,但一定不能滥用,我举一个实际遇到的例子(已做脱敏处理):

    用户1148394
  • EMA算法及其tensorflow实现

    滑动平均模型可以使模型在测试数据上更健壮(robust)的方法------滑动平均模型。在采用随机梯度下降算法训练神经网络时,使用滑动平均模型在很多应用中都可以...

    于小勇
  • 13.JAVA-包package、import使用

    之前我们学习java时,生成的class文件都是位于当前目录中,假如出现了同名文件,则会出现文件覆盖问题,因此就需要设置不同的目录(定义包),来解决同名文件冲突...

    张诺谦
  • 面向对象(二十三)-网络 Socket 理论知识

    计算机在网络上都有一个 IP地址,每个计算机都有端口,端口范围在0-65535之间。 端口,是计算机上 应用程序通讯所用的地址。

    雷潮
  • 如何选择正确的Node框架:Express,Koa还是Hapi?

    Node.js是10年前首次推出的,目前它已经成为世界上最大的开源项目,在GitHub上有+59,000颗星,下载次数超过10亿。流行度快速增长的部分原因是No...

    Fundebug
  • 被控造假、打人之后要一雪前耻!“杀马特”华裔教授推出paGAN,GoodFellow也点赞

    【新智元导读】还记得“杀马特教授”黎颢吗?他将率领团队在下周召开的SIGGRAPH中展示一项黑科技“paGAN”:每秒1000帧扫描,用单幅照片实时生成超逼真动...

    新智元
  • 数据结构与算法(二)-线性表之单链表顺序存储和链式存储

    前言:前面已经介绍过数据结构和算法的基本概念,下面就开始总结一下数据结构中逻辑结构下的分支——线性结构线性表

扫码关注云+社区

领取腾讯云代金券