Spring Boot动态创建Bean,DynamicDataSource实现读写分离的多数据源

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

一. 从Spring 3.0开始,增加了一种新的途径来配置Bean Definition,这就是通过Java Code配置Bean Definition。

   与XML和Annotation两种配置方式不同点在于:

   前两种方式XML和Annotation的配置方式为预定义方式,即开发人员通过XML文件或者Annotation预定义配置Bean的各种属性后,启动Spring容器,Spring容器会首先解析这些配置属性,生成对应的Bean Definition,装入到DefaultListtableBeanFactory对象的属性容器中,以此同时,Spring框架也会定义内部使用的Bean定义,如Bean名为:org.springframework.context.annotation.internalConfigurationAnnotationProcessor”的 ConfigurationClassPostProcessor 定义。而后此刻不会做任何Bean Definition的解析动作,Spring框架会根据前两种配置,过滤出BeanDefinitionRegistryPostProcessor 类型的Bean定义,并通过Spring框架生成对应的Bean对象(如 ConfigurationClassPostProcessor 实例)。。结合 Spring 上下文源码可知这个对象是一个 processor 类型工具类,Spring 容器会在实例化开发人员所定义的 Bean 前先调用该 processor 的 postProcessBeanDefinitionRegistry(…) 方法。此处实现基于 Java Code 配置Bean Definition的处理。



   基于 Java Code 的配置方式,其执行原理不同于前两种。它是在 Spring 框架已经解析了基于 XML 和 Annotation 配置后,通过加入 BeanDefinitionRegistryPostProcessor 类型的 processor 来处理配置信息,让开发人员通过 Java 编程方式定义一个 Java 对象。其优点在于可以将配置信息集中在一定数量的 Java 对象中,同时通过 Java 编程方式,比基于 Annotation 方式具有更高的灵活性。并且该配置方式给开发人员提供了一种非常好的范例来增加用户自定义的解析工具类。其主要缺点在于与 Java 代码结合紧密,配置信息的改变需要重新编译 Java 代码,另外这是一种新引入的解析方式,需要一定的学习成本。

二.提及一点的就是,Spring框架有3个主要的Hook类,分别是:

  1. org.springframework.context.ApplicationContextAware 它的setApplicationContext 方法将在Spring启动之前第一个被调用。我们用来同时启动Jdon框架。

  2. org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor 它的postProcessBeanDefinitionRegistry 和 postProcessBeanFactory 方法是第二和第三被调用,它们在Bean初始化创建之前启动,如果Spring的bean需要的其他第三方中的组件,我们在这里将其注入给Spring。

  3. org.springframework.context.EnvironmentAware 这里介绍凡是被spring管理的类,实现接口 EnvironmentAware 重写方法 setEnvironment 可以在工程启动时,获取到系统环境变量和application配置文件中的变量。

  • @ConditionOnClass表明该@Configuration仅仅在一定条件下才会被加载,这里的条件是 DataDruidConfig.class位于类路径上

  • @EnableConfigurationProperties将Spring Boot的配置文件(application.properties)中的spring.datasource.druid.*属性映射为DataDruidProperties并注入到 DataDruidConfig中。

  • @ConditionalOnMissingBean说明Spring Boot仅仅在当前上下文中不存在DataDruidConfig对象时,才会实例化一个Bean。这个逻辑也体现了Spring Boot的另外一个特性——自定义的Bean优先于框架的默认配置,我们如果显式的在业务代码中定义了一个DataDruidConfig对象,那么Spring Boot就不再创建。

  1. org.springframework.context.ApplicationListener 用于在初始化完成后做一些事情,当Spring所有XML或元注解的Bean都启动被创建成功了,这时会调用它的唯一方法onApplicationEvent。

三.数据库参数配置管理实现类,基于DataSource Druid 实现,具体属于配置如下:

package top.suven.core.db;



/**
* DruidDataSource 数据库连接管理对象的相关参数属性配置对象;
* 数据库连接池的相结合配置文件类型定义参数;
*/

public interface DataDruidConfig {


   /**
    * DruidDataSource 对应属性的配置文件参数;可对应变更和修改
    * 根据自己的项目配置文件规则,只需要调整***PREFIX对应的参数即可;
    */
     String DATASOURCE_DRUID_PREFIX = "suven.datasource.druid.";

   /**
    * 数据库对象的模块的url,username,password的前缀
    * 根据自己的项目配置文件规则,只需要调整***PREFIX对应的参数即可;
    * suven.datasource.%s.%s.url        //eg: suven.datasource.assets.master.url
    * suven.datasource.%s.%s.username   //eg: suven.datasource.assets.master.username
    * suven.datasource.%s.%s.password;  //eg: suven.datasource.assets.master.password
    */
     String DATASOURCE_MODULE_PREFIX = "suven.datasource.";
     String DATASOURCDE_DRUID_FORMAT = "%s.%s.";



   /**
    * 通过 BeanDefinitionBuilder类,初始化DruidDataSource对应的属性参考
    */


     String URL                                        = "url";
     String USERNAME                                   = "username";
     String PASSWORD                                  = "password";

     String DRIVER_CLASSNAME                            = "driverClassName";
     String INITIALIZE                                = "initialize";
     String DBTYPE                                     = "dbType";
     String MAXACTIVE                                = "maxActive";
     String INITIALSIZE                              = "initialSize";
     String MAXWAIT                                   = "maxWait";
     String MINIDLE                                   = "minIdle";
     String TIME_BETWEENE_VICTION_RUNS_MILLIS             = "timeBetweenEvictionRunsMillis";
     String MIN_EVICTABLE_IDLE_TIME_MILLIS                 = "minEvictableIdleTimeMillis";
     String VALIDATION_QUERY                           = "validationQuery";
     String TEST_WHILEIDLE                            = "testWhileIdle";
     String TEST_ON_BORROW                             = "testOnBorrow";
     String  TEST_ON_RETURN                             = "testOnReturn";
     String POOL_PREPARED_STATEMENTS                      = "poolPreparedStatements";
     String CONNECTION_PROPERTIES                        =    "connectionProperties";
     String FILTERS                                     = "filters";

     String ENABLED                                     = "enabled";

     String datasource_druid_master                    = "master";
     String datasource_druid_slave                     ="slave";
	  String datasource_druid_frame                     ="druid";
     String datasource_master_name                     = "MasterDataSource";
     String datasource_slave_name                      =  "SlaveDataSource";

     String datasource_param_config_enabled            = "config.enabled";


   /**
    * 初化所有数据源DataSourceAutoConfiguration类配置开关,默认为falase
    * suven.datasource.druid.frame.enabled=true
    */
     String datasource_druid_config_enabled                     =   DATASOURCE_DRUID_PREFIX +  datasource_param_config_enabled ;//"suven.datasource.druid.frame.enabled";


   /**
    * 初化指定模块数据源DataSourceGroupNameEnum类配置开关,默认为 true
    * suven.datasource.user.master.enabled=true
    */
     String datasource_druid_master_enabled  =                    DATASOURCE_MODULE_PREFIX +  DATASOURCDE_DRUID_FORMAT + ENABLED;//   "suven.datasource.%s.%s.enabled";

   /**
    * 初化所有数据源DataSourceAutoConfiguration类配置 从数据库的总开关,
    * 对应数据库的从数据库集合开关;默认为 true,
    * 若存在对应的从数据库的配置会自动加载,若需要关闭可以将该配置设计为false,或删除对应配置
    * suven.datasource.druid.user.slave.enabled=true
    */
     String datasource_druid_slave_enabled =                           DATASOURCE_MODULE_PREFIX + DATASOURCDE_DRUID_FORMAT + ENABLED;//    "suven.datasource.druid.%s.slave.enabled";

     String datasource_druid_url =                                        DATASOURCE_MODULE_PREFIX + DATASOURCDE_DRUID_FORMAT + URL;//    "suven.datasource.%s.%s.url";// = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
     String datasource_druid_username =                                   DATASOURCE_MODULE_PREFIX +  DATASOURCDE_DRUID_FORMAT + USERNAME;//   "suven.datasource.%s.%s.username";//suven.datasource.assets.master.username = redfinger
     String datasource_druid_password =                                   DATASOURCE_MODULE_PREFIX + DATASOURCDE_DRUID_FORMAT +  PASSWORD;//    "suven.datasource.%s.%s.password";// suven.datasource.assets.master.password = redfinger





     String datasource_druid_initialSize =                            DATASOURCE_DRUID_PREFIX + INITIALSIZE;// "suven.datasource.druid.initialSize";
     String datasource_druid_minIdle =                                 DATASOURCE_DRUID_PREFIX + MINIDLE;//"suven.datasource.druid.minIdle";
     String datasource_druid_maxActive =                              DATASOURCE_DRUID_PREFIX + MAXACTIVE;// "suven.datasource.druid.maxActive";
     String datasource_druid_maxWait =                                DATASOURCE_DRUID_PREFIX + MAXWAIT;// "suven.datasource.druid.maxWait";
     String datasource_druid_timeBetweenEvictionRunsMillis =          DATASOURCE_DRUID_PREFIX + TIME_BETWEENE_VICTION_RUNS_MILLIS;//  "suven.datasource.druid.timeBetweenEvictionRunsMillis";
     String datasource_druid_minEvictableIdleTimeMillis =              DATASOURCE_DRUID_PREFIX + MIN_EVICTABLE_IDLE_TIME_MILLIS;// "suven.datasource.druid.minEvictableIdleTimeMillis";
     String datasource_druid_validationQuery =                         DATASOURCE_DRUID_PREFIX + VALIDATION_QUERY;// "suven.datasource.druid.validationQuery";
     String datasource_druid_testWhileIdle =                              DATASOURCE_DRUID_PREFIX + TEST_WHILEIDLE;//  "suven.datasource.druid.testWhileIdle";
     String datasource_druid_testOnBorrow =                              DATASOURCE_DRUID_PREFIX + TEST_ON_BORROW;//  "suven.datasource.druid.testOnBorrow";
     String datasource_druid_testOnReturn =                             DATASOURCE_DRUID_PREFIX + TEST_ON_RETURN;//    "suven.datasource.druid.testOnReturn";
     String datasource_druid_poolPreparedStatements =                   DATASOURCE_DRUID_PREFIX + POOL_PREPARED_STATEMENTS;//    "suven.datasource.druid.poolPreparedStatements";
     String datasource_druid_filters =                                   DATASOURCE_DRUID_PREFIX + FILTERS;//    "suven.datasource.druid.filters";
     String datasource_druid_connectionProperties =                       DATASOURCE_DRUID_PREFIX + CONNECTION_PROPERTIES;//    "suven.datasource.druid.connectionProperties";

}

四.继承AbstractRoutingDataSource数据源多数据库动态路由切换,将 DataSourceGroupNameEnum 数据源组 生成bean对象注入到spring管理容器中;再根据模块名称字符串 作为模块key 从targetDataSources 的Map中获取指定模块的数据库的连接池;从而通过动态桥接的设计模式来达到数据源连接池动态切换原理;

package top.suven.core.db;

import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * 获取数据源
 * 
 * @author suven
 * @version 2018/6/15 13:18
 */

/**
 * 将 DataSourceGroupNameEnum 数据源组 生成bean对象注入到spring管理容器中;
 * 通过 datasource_druid_enabled 对应模块数据库配置总开关,默认值为true
 * 通过 datasource_druid_slave_enabled 对应模块从数据库配置总开关,默认值为true
 * 将模块初始化后数据源信息,并将结果生成spring bean 名称缓存到DataSourceGroupNameEnum对象中
 * 将所有模块通过initDataByGroup 初始化后的数据库的聚群,初始化到目标的动态数据池子里;
 * 再根据模块名称字符串 作为模块key 从targetDataSources 的Map中获取指定模块的数据库的连接池;
 * 从而通过动态桥接的设计模式来达到数据源连接池动态切换原理;
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

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

	private boolean isDefaultTargetDataSource = true;

	private Map targetDataSources = new HashMap<>();
	private ConcurrentHashMap currentMap = new ConcurrentHashMap<>();


	/**
	 * 将所有模块通过initDataByGroup 初始化后的数据库的聚群,初始化到目标的动态数据池子里;
	 */
	public void setTargetDataSources(){
		super.setTargetDataSources(targetDataSources);
	}


	public void setTargetDataSources(Map targetDataSources){
		Object defaultTargetDataSource = targetDataSources.entrySet().iterator().next();
		if(isDefaultTargetDataSource){
			this.setDefaultTargetDataSource(defaultTargetDataSource);
			isDefaultTargetDataSource =false;
		}
		this.targetDataSources.putAll(targetDataSources);
		this.setTargetDataSources();
	}



	/**
	 * DataSourceGroupNameEnum 枚举类型的实现初始化指定模块数据源信息;
	 * @param dataSourceGroupNameEnum
	 */
	public void initDataByGroup(DataSourceGroupNameEnum dataSourceGroupNameEnum,ApplicationContext applicationContext){
		Map targetDataSources = new HashMap<>();

		DataSourceGroup group = DataSourceGroupNameEnum.getDataSourceGroupByName(dataSourceGroupNameEnum);
		if(null == group || null == group.getMasterSources()){
			return;
		}
		String master = group.getMasterSources();
		DataSource defaultTargetDataSource = applicationContext.getBean(master,DruidDataSource.class);
		targetDataSources.put(master, defaultTargetDataSource);
		List list = group.getSlaveSources();
		if(null != list && !list.isEmpty()){
			for (String slave : list){
				try{
					DataSource datasource = applicationContext.getBean(slave,DruidDataSource.class);
					if(null == datasource){
						continue;
					}
					targetDataSources.put(slave,datasource );
				}catch (Exception e){
					logger.info("初始化[{}】 slave 数据源失败,检查slave开关是否开启!", slave);
				}
			}
		}
		if(isDefaultTargetDataSource){
			this.setDefaultTargetDataSource(defaultTargetDataSource);
		}

		this.targetDataSources.putAll(targetDataSources);

	}

	// 获取数据源名称,采用轮询的方式实现
	@Override
	protected Object determineCurrentLookupKey() {
		DataChooseParam dataSource = DataSourceHolder.getDataSource();
		DataSourceHolder.clear();
		if (null == dataSource) {
			throw new IllegalArgumentException("Property 'determineCurrentLookupKey' is DataSourceHolder.getDataSource() not  dataSource:[" + dataSource + "]");

		}
		DataSourceGroup dataSourceGroup = DataSourceGroupNameEnum.getDataSourceGroupByName(dataSource.getGroupName());

		if (null == dataSourceGroup) {
			throw new IllegalArgumentException("Property 'determineCurrentLookupKey' is DataSourceGroupNameEnum.getDataSourceGroupByName() not  dataSourceGroup:[" + dataSourceGroup + "]");
		}
		String dataSourceKey = null;
//		Map>  map = dataSourceGroupMap.get(dataSource.getGroupName());
		DataSourceEnum dataEnum = dataSource.getDataType();
		if (null == dataEnum) {
			dataEnum = DataSourceEnum.MASTER;
		}

		if (DataSourceEnum.MASTER == dataSource.getDataType()) {
			dataSourceKey = dataSourceGroup.getMasterSources();
			dataSource.setDataClient(dataSourceKey);
			return dataSourceKey;
		}
		if (DataSourceEnum.SLAVE == dataSource.getDataType()) {
			List list = dataSourceGroup.getSlaveSources();
			if (null == list || list.isEmpty()) {
				dataSourceKey = dataSourceGroup.getMasterSources();
				dataSource.setDataClient(dataSourceKey);
				return dataSourceKey;
			}
//			throw new IllegalArgumentException("Property 'determineCurrentLookupKey' is DataSourceHolder.map.get(dataType) list isEmpty or null ");
			int size = list.size();
			if (size == 1) {
				dataSourceKey = list.get(0);
			} else {
				AtomicInteger counter = currentMap.get(dataEnum.name());
				if (counter == null) {
					counter = new AtomicInteger(0);
					currentMap.put(dataEnum.name(), counter);
				} else {
					if (counter.incrementAndGet() >= size) {
						counter.set(0);
					}
				}
				dataSourceKey = list.get(counter.intValue());
			}
		}
		dataSource.setDataClient(dataSourceKey);
		return dataSourceKey;

	}


五.结合 Spring 上下文源码可知这个对象是一个 processor 类型工具类,Spring 容器会在实例化开发人员所定义的 Bean 前先调用该 processor 的 postProcessBeanDefinitionRegistry(…) 方法,下面我们来完成一个,自己通过java代码创建bean,并注册为Spring管理。

本例中,我们创建一个接口,然后创建该接口的2个实现类,分别命名不同的名字,然后在需要注入的地方使用@Qualifier 指定注入对应的实例。

package top.suven.core.db;

import com.alibaba.druid.pool.DruidAbstractDataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.*;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;

import javax.sql.DataSource;
import java.util.*;

/**
 * 应用服务起动类,加载数据源实现模块组实现类
 * 数据库聚群,启动和管理的配置实现类;
 * datasource_druid_config_enabled =true.表示启动该实现类来初始化DataSourceGroupNameEnum对象管理的数据源,
 * 默认值为false,
 * 数据库的初始参数都是统一的,实现类为DruidDataConfig,统一从Environment environment 初始化获取;
 *
 * setEnvironment()-->postProcessBeanDefinitionRegistry() --> postProcessBeanFactory()
 */

@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class,
        DruidDataSourceAutoConfigure.class,JdbcTemplateAutoConfiguration.class})
@ConditionalOnProperty(name = DataDruidConfig.datasource_druid_config_enabled,  matchIfMissing = false)
public class DataSourceAutoConfig implements DataDruidConfig, EnvironmentAware, BeanDefinitionRegistryPostProcessor,ApplicationContextAware {

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

    private RelaxedPropertyResolver property;
    private DruidDataConfig druidConfig;

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.property = new RelaxedPropertyResolver(environment);
        druidConfig = new DruidDataConfig();
        logger.warn("DataSourceAutoConfiguration in DruidDataConfig info=[{}]", druidConfig.toString());

    }


    @Bean("dataSource")
    public DataSource routingDataSource() {
        DynamicDataSource dataSource = new DynamicDataSource();
        Set sourceNames = DataSourceGroupNameEnum.getSourceNames();
        if (sourceNames == null || sourceNames.isEmpty()) {
            throw  new RuntimeException("DynamicDataSource init DataSource isEmpty ");
        }
        for (DataSourceGroupNameEnum dataName : sourceNames) {
            dataSource.initDataByGroup(dataName,applicationContext);
        }
        dataSource.setTargetDataSources();

        logger.warn("Dynamic DataSource Registry --- routingDataSource Successfully ...      ");
        return dataSource;
    }



    /**
     * 将 DataSourceGroupNameEnum 数据源组 生成bean对象注入到spring管理容器中;
     * 通过 datasource_druid_enabled 对应模块数据库配置总开关,默认值为true
     * 通过 datasource_druid_slave_enabled 对应模块从数据库配置总开关,默认值为true
     * 将模块初始化后数据源信息,并将结果生成spring bean 名称缓存到DataSourceGroupNameEnum对象中
     * @param registry
     * @throws BeansException
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        Set sourceNames = null;
        sourceNames = DataSourceGroupNameEnum.getSourceNames();
        if (sourceNames == null || sourceNames.isEmpty()) {
            return;
        }
        boolean isPrimary = true;
        //从数据库配置总开关,
         boolean dataSourceSlaveEnabled = property.getProperty(
                String.format(datasource_druid_slave_enabled,datasource_druid_frame,datasource_druid_slave).trim(),
                Boolean.class,true);

        for (DataSourceGroupNameEnum dataName : sourceNames) {

            //模块数据库 主-从 配置总开关,
            boolean moduleDataSourceEnabled = property.getProperty(
                    String.format(datasource_druid_master_enabled,dataName.getValue(),datasource_druid_master).trim(),
                    Boolean.class,true);

            //模块从数据库配置总开关,
            boolean moduleDataSourceSlaveEnabled = property.getProperty(
                    String.format(datasource_druid_slave_enabled,dataName.getValue(),datasource_druid_slave).trim(),
                    Boolean.class,true);

            /**
             * 通过模块对应的配置文件获取主数据库信息,如果不存在就跳该模块的对应的所有数据库
             */
            String url = property.getProperty(String.format(datasource_druid_url,dataName.getValue(),datasource_druid_master));
            if (null == url || "".equals(url) || !moduleDataSourceEnabled) {
                continue;
            }
            String username = property.getProperty(String.format(datasource_druid_username,dataName.getValue(),datasource_druid_master).trim());
            String password = property.getProperty(String.format(datasource_druid_password,dataName.getValue(),datasource_druid_master).trim());

            /**
             * 注入到spring bean的名称生成规则;(模块文称+ MasterDataSource)
             */
            String datasourceMasterBeanName = dataName.getValue() + datasource_master_name ;

            BeanDefinitionBuilder datasourceFactory = initDatasourceBean(druidConfig,url,username,password);
            BeanDefinition beanDefinition =  datasourceFactory.getBeanDefinition();
            if(isPrimary){//设置唯一主数据库
                beanDefinition.setPrimary(true);
                isPrimary = false;
            }
            registry.registerBeanDefinition(datasourceMasterBeanName, beanDefinition);
            List slaveDataSources = new ArrayList<>();
            int i = 0 ;
            while (dataSourceSlaveEnabled && moduleDataSourceSlaveEnabled){

                String slave = i == 0 ? datasource_druid_slave : datasource_druid_slave + i;

                /**
                 * 注入到spring bean的名称生成规则;(模块文称+ SlaveDataSource + 序列号1,2,3...)
                 */
                String datasourceSlaveBeanName = dataName.getValue() + datasource_slave_name + i;
                url = property.getProperty(String.format(datasource_druid_url,dataName.getValue(),slave).trim());
                if (null == url || "".equals(url)) {
                    break;
                }
                username = property.getProperty(String.format(datasource_druid_username,dataName.getValue(),slave).trim());
                password = property.getProperty(String.format(datasource_druid_password,dataName.getValue(),slave).trim());
                datasourceFactory = initDatasourceBean(druidConfig,url,username,password);
                registry.registerBeanDefinition(datasourceSlaveBeanName, datasourceFactory.getBeanDefinition());
                slaveDataSources.add(datasourceSlaveBeanName);
                i++;

            }
            /**
             * 将模块初始化后数据源信息,并将结果生成spring bean 名称缓存到DataSourceGroupNameEnum 对象中
             */
            DataSourceGroupNameEnum.setDataSource(dataName,datasourceMasterBeanName,slaveDataSources);

            logger.warn("DataSourceAutoConfig postProcessBeanDefinitionRegistry Registry --- dataSourceName[{}] Successfully ...",datasourceMasterBeanName);

        }

    }




    /**
     * 初始化DruidDataSource对象
     * 通过BeanDefinitionBuilder生成DruidDataSource对象实现类
     * 并且通过配置文件获取对应的指定属性
     * @param url
     * @param username
     * @param password
     * @return
     */
    private BeanDefinitionBuilder initDatasourceBean(DruidDataConfig druid,String url,String username,String password){
        BeanDefinitionBuilder datasourceFactory = BeanDefinitionBuilder.genericBeanDefinition(DruidDataSource.class);
        datasourceFactory.setLazyInit(true);          //设置是否懒加载
        datasourceFactory.setScope(BeanDefinition.SCOPE_SINGLETON);       //设置scope,为单例类型
        datasourceFactory.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);  //设置是否可以被其他对象自动注入

        datasourceFactory.addPropertyValue(URL, url);
        datasourceFactory.addPropertyValue(USERNAME, username);
        datasourceFactory.addPropertyValue(PASSWORD, password);

        initDataSource(datasourceFactory,druid);
        return datasourceFactory;
    }


//

    /**
     * 初始化数据库的默认配置参数;
     * @param datasourceFactory
     */
    private void initDataSource(BeanDefinitionBuilder datasourceFactory,DruidDataConfig druid){

        datasourceFactory.addPropertyValue(INITIALSIZE, druid.getInitialSize());
        datasourceFactory.addPropertyValue(MINIDLE, druid.getMinIdle());
        datasourceFactory.addPropertyValue(MAXACTIVE, druid.getMaxActive());
        datasourceFactory.addPropertyValue(MAXWAIT, druid.getMaxWait());
        datasourceFactory.addPropertyValue(TIME_BETWEENE_VICTION_RUNS_MILLIS,druid.getTimeBetweenEvictionRunsMillis() );
        datasourceFactory.addPropertyValue(MIN_EVICTABLE_IDLE_TIME_MILLIS, druid.getMinEvictableIdleTimeMillis() );
        datasourceFactory.addPropertyValue(VALIDATION_QUERY, druid.getValidationQuery());
        datasourceFactory.addPropertyValue(TEST_WHILEIDLE, druid.isTestWhileIdle());
        datasourceFactory.addPropertyValue(TEST_ON_BORROW, druid.isTestOnBorrow());
        datasourceFactory.addPropertyValue(TEST_ON_RETURN, druid.isTestOnReturn());
        datasourceFactory.addPropertyValue(POOL_PREPARED_STATEMENTS, druid.isPoolPreparedStatements());

        try {
            datasourceFactory.addPropertyValue(FILTERS,druid.getFilters());
        } catch (Exception e) {
            logger.error("druid configuration initialization filter", e);
        }
    }
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }


    private class DruidDataConfig {

        private int initialSize;
        private int minIdle;
        private int maxActive;
        private int maxWait;
        private long timeBetweenEvictionRunsMillis;
        private long minEvictableIdleTimeMillis;
        private String validationQuery;
        private boolean testWhileIdle;
        private boolean testOnBorrow;
        private boolean testOnReturn;
        private boolean poolPreparedStatements;
        private String filters;


        public DruidDataConfig(){
            this.setInitialSize(property.getProperty(datasource_druid_initialSize,Integer.class,DruidAbstractDataSource.DEFAULT_INITIAL_SIZE));
            this.setMinIdle(property.getProperty(datasource_druid_minIdle,Integer.class,DruidAbstractDataSource.DEFAULT_MIN_IDLE));
            this.setMaxActive(property.getProperty(datasource_druid_maxActive,Integer.class,DruidAbstractDataSource.DEFAULT_MAX_ACTIVE_SIZE));
            this.setMaxWait(property.getProperty(datasource_druid_maxWait,Integer.class,DruidAbstractDataSource.DEFAULT_MAX_WAIT));
            this.setTimeBetweenEvictionRunsMillis(property.getProperty(datasource_druid_timeBetweenEvictionRunsMillis,Long.class,DruidAbstractDataSource.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS));
            this.setMinEvictableIdleTimeMillis(property.getProperty(datasource_druid_minEvictableIdleTimeMillis,Long.class,DruidAbstractDataSource.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS));
            this.setValidationQuery(property.getProperty(datasource_druid_validationQuery,DruidAbstractDataSource.DEFAULT_VALIDATION_QUERY));
            this.setTestWhileIdle(property.getProperty(datasource_druid_testWhileIdle,Boolean.class,true));
            this.setTestOnBorrow(property.getProperty(datasource_druid_testOnBorrow,Boolean.class));
            this.setTestOnReturn(property.getProperty(datasource_druid_testOnReturn,Boolean.class));
            this.setPoolPreparedStatements(property.getProperty(datasource_druid_poolPreparedStatements,Boolean.class));
            this.setFilters(property.getProperty(datasource_druid_filters,""));
        }

        public int getInitialSize() {
            return initialSize;
        }

        public void setInitialSize(int initialSize) {
            this.initialSize = initialSize;
        }

        public int getMinIdle() {
            return minIdle;
        }

        public void setMinIdle(int minIdle) {
            this.minIdle = minIdle;
        }

        public int getMaxActive() {
            return maxActive;
        }

        public void setMaxActive(int maxActive) {
            this.maxActive = maxActive;
        }

        public int getMaxWait() {
            return maxWait;
        }

        public void setMaxWait(int maxWait) {
            this.maxWait = maxWait;
        }

        public long getTimeBetweenEvictionRunsMillis() {
            return timeBetweenEvictionRunsMillis;
        }

        public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
            this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
        }

        public long getMinEvictableIdleTimeMillis() {
            return minEvictableIdleTimeMillis;
        }

        public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
            this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
        }

        public String getValidationQuery() {
            return validationQuery;
        }

        public void setValidationQuery(String validationQuery) {
            this.validationQuery = validationQuery;
        }

        public boolean isTestWhileIdle() {
            return testWhileIdle;
        }

        public void setTestWhileIdle(boolean testWhileIdle) {
            this.testWhileIdle = testWhileIdle;
        }

        public boolean isTestOnBorrow() {
            return testOnBorrow;
        }

        public void setTestOnBorrow(boolean testOnBorrow) {
            this.testOnBorrow = testOnBorrow;
        }

        public boolean isTestOnReturn() {
            return testOnReturn;
        }

        public void setTestOnReturn(boolean testOnReturn) {
            this.testOnReturn = testOnReturn;
        }

        public boolean isPoolPreparedStatements() {
            return poolPreparedStatements;
        }

        public void setPoolPreparedStatements(boolean poolPreparedStatements) {
            this.poolPreparedStatements = poolPreparedStatements;
        }

        public String getFilters() {
            return filters;
        }

        public void setFilters(String filters) {
            this.filters = filters;
        }

        @Override
        public String toString() {
            return "DruidDataConfig{" +
                    "initialSize=" + initialSize +
                    ", minIdle=" + minIdle +
                    ", maxActive=" + maxActive +
                    ", maxWait=" + maxWait +
                    ", timeBetweenEvictionRunsMillis=" + timeBetweenEvictionRunsMillis +
                    ", minEvictableIdleTimeMillis=" + minEvictableIdleTimeMillis +
                    ", validationQuery='" + validationQuery + '\'' +
                    ", testWhileIdle=" + testWhileIdle +
                    ", testOnBorrow=" + testOnBorrow +
                    ", testOnReturn=" + testOnReturn +
                    ", poolPreparedStatements=" + poolPreparedStatements +
                    ", filters='" + filters + '\'' +
                    '}';
        }
    }

}

六.数据源分组管理对象,主-从(多从)为一组数据源,创建一个DataSourceGroup Bean 对象

package top.suven.core.db;

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

/**
 * Created by suven on 16/8/30.
 * 封装每个项目的数据源对象;
 */
public class DataSourceGroup {

    private String groupName;
    private String masterSources;
    private List slaveSources = new ArrayList<>();
    private String masterMethod;
    private String slaveMethod;
   


    public String getMasterSources() {
        return masterSources;
    }

    public void setMasterSources(String masterSources) {
        this.masterSources = masterSources;
    }

    public List getSlaveSources() {
        return slaveSources;
    }

    public void setSlaveSources(List slaveSources) {
        this.slaveSources = slaveSources;
    }

    public void setSlaveSources(String slaveKey) {
        if(this.slaveSources == null){
            slaveSources = new ArrayList<>();
        }
        this.slaveSources.add(slaveKey);
    }

    public String getMasterMethod() {
        return masterMethod;
    }

    public void setMasterMethod(String masterMethod) {
        this.masterMethod = masterMethod;
    }

    public String getSlaveMethod() {
        return slaveMethod;
    }

    public void setSlaveMethod(String slaveMethod) {
        this.slaveMethod = slaveMethod;
    }

    public String getGroupName() {
        return groupName;
    }

    public DataSourceGroup setGroupName(String groupName) {
        this.groupName = groupName;
        return this;
    }


}

 七.项目数据源名称泛型实现类,数据库组,包括主和从数据库

package top.suven.core.db;

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

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 项目数据源名称泛型实现类,
 * 用来实现多数据源自动切换使用,配合 DataSourceAutoConfiguration 启动初化数据源注入到spring bean 中
 * 创建spring DynamicDataSource bean 对象,并注入到spring容器中,命名为dataSource;
 *
 *
 */
public enum DataSourceGroupNameEnum {

    DATA_NAME_USER("user"),//用户组数据库,包括主和从数据库
    DATA_NAME_OAUTH("oauth"),//验证组数据库,包括主和从数据库
    DATA_NAME_ASSETS("assets"),//用户资产组数据库,包括主和从数据库
    //随着项目和数据的增加多,在型这里增加属性即可
    ;

    public static Logger logger = LoggerFactory.getLogger(DataSourceGroupNameEnum.class);
    private String value;


    private static Map tbTypeMap = new LinkedHashMap<>();
    private static Map groupMap = new ConcurrentHashMap<>();
    static {

        for(DataSourceGroupNameEnum type : values()) {
            tbTypeMap.put(type.name(), type);
            groupMap.put(type.name(), new DataSourceGroup().setGroupName(type.name()));
        }
    }



    DataSourceGroupNameEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    private static DataSourceGroupNameEnum getName(String name){
        return tbTypeMap.get(name);
    }

    public static Collection getValues(){
        return tbTypeMap.values();
    }
    public static Set getSourceNames(){
        Set sourceNames = new LinkedHashSet<>();
        sourceNames.addAll(tbTypeMap.values());
        return sourceNames;

    }


    //通过组名,获取数据源集合对象;
    public static DataSourceGroup getDataSourceGroupByName(String groupName){
        if(null == groupName ){
            logger.warn("DataSourceGroup getDataSourceGroupByName by groupName[{}]", groupName);
            return null;
        }
        DataSourceGroupNameEnum dataSourceGroupNameEnum = getName(groupName);

        return getDataSourceGroupByName(dataSourceGroupNameEnum);
    }
    //通过组名,获取数据源集合对象;
    public static DataSourceGroup getDataSourceGroupByName(DataSourceGroupNameEnum groupNameEnum){
        if( null == groupNameEnum){
            logger.warn("DataSourceGroup init getDataSourceGroupByName by groupNameEnum[{}]", groupNameEnum);
           return null;
        }
        DataSourceGroup dataSourceGroup = groupMap.get(groupNameEnum.name());
        return dataSourceGroup;
    }



    //将数据源对象添加到组名管理
    public static void setSlaveDataSourceKey(DataSourceGroupNameEnum groupNameEnum, String... datasourceKey){
        DataSourceGroup dataSourceGroup = groupMap.get(groupNameEnum.name());
        if( null == dataSourceGroup){
            logger.warn("DataSourceGroup init setMasterDataSourceKey by dataSourceGroup groupNameEnum[{}]", dataSourceGroup);
            return ;
        }
        if(datasourceKey.length == 1){
            dataSourceGroup.setSlaveSources(datasourceKey[0]);
        }else{
            dataSourceGroup.setSlaveSources(Arrays.asList(datasourceKey));
        }

    }

    public static void setDataSource(DataSourceGroupNameEnum groupNameEnum,String masterDatasourceKey, List slaveDatasources ){
        DataSourceGroup dataSourceGroup = groupMap.get(groupNameEnum.name());
        if( null == dataSourceGroup){
            logger.warn("DataSourceGroup init setMasterDataSourceKey by dataSourceGroup groupNameEnum[{}]", dataSourceGroup);
            return ;
        }
        dataSourceGroup.setMasterSources(masterDatasourceKey);
        if(slaveDatasources != null){
            dataSourceGroup.setSlaveSources(slaveDatasources);
        }


    }
    //将数据源对象添加到组名管理
    public static void setDataSourceKey(DataSourceGroupNameEnum groupNameEnum,String masterDatasourceKey, String... slaveDatasourceKey){

        List slaveList = new ArrayList();
        if( slaveDatasourceKey != null){
            slaveList = Arrays.asList(slaveDatasourceKey);
        }
        setDataSource(groupNameEnum,masterDatasourceKey,slaveList);
    }


}

八.通过 ThreadLocal 当前线程安全类,实现线程内,参数切换实现业务解偶; 以达到动态数据库连接池实现切换,实现多数据源原理

package top.suven.core.db;

/**
 * Created by suven on 16/9/7.
 */
public class DataChooseParam {

    private boolean isRotate;
    private String groupName;
    private DataSourceEnum dataType; // 入参数为: MASTER 或 SLAVE;
    private String dataClient;


    public DataChooseParam(){
        
    }
    public DataChooseParam(String groupName) {
        this.groupName = groupName;
        this.isRotate = true;
    }
    public DataChooseParam(String groupName, DataSourceEnum dataType) {
        this.groupName = groupName;
        this.dataType = dataType;
        this.isRotate = true;
    }

    public boolean isRotate() {
        return isRotate;
    }



    public String getGroupName() {
        return groupName;
    }



    public DataSourceEnum getDataType() {
        return dataType;
    }


    public String getDataClient() {
        return dataClient;
    }

    public void setDataClient(String dataClient) {
        this.dataClient = dataClient;
    }

    @Override
    public String toString() {
        return "DataChooseParam{" +
                "isRotate=" + isRotate +
                ", groupName='" + groupName + '\'' +
//                ", dataType='" + dataType + '\'' +
                ", dataClient='" + dataClient + '\'' +
                '}';
    }
}

  • DataSourceHolder 通过 ThreadLocal 当前线程安全类,实现线程内,参数切换实现业务解偶;以达到动态数据库连接池实现切换,实现多数据源原理
package top.suven.core.db;


/**
 * @author suven.wang
 * @version
 *
 * 通过 ThreadLocal 当前线程安全类,实现线程内,参数切换实现业务解偶;
 * 以达到动态数据库连接池实现切换,实现多数据源原理
 */
public class DataSourceHolder {
	// 数据源名称线程池
	private static final ThreadLocal holder = new ThreadLocal<>();
	



	public static void putDataSource(DataChooseParam dataChooseParam) {
		
		holder.set(dataChooseParam);
	}

	public static DataChooseParam getDataSource() {
		return holder.get();
	}

	public static void clear() {
		holder.remove();
	}
}

  • DataSourceEnum 数据类型,主-从
package top.suven.core.db;

public enum DataSourceEnum{
    MASTER, SLAVE;

}

九. 数据库相关配置文件 application-db.properties 如下:

#-----------------------------------datasource--------------------------------------
# 是否启动动态加载数据库源,默认是false,不启动该实现数据源
suven.datasource.druid.config.enabled=true

# 是否启动动态加载所有从数据库源,默认是true,支持动态读写分离
#suven.datasource.druid.slave.enabled=true

suven.datasource.druid.initialize=true 
suven.datasource.druid.dbType= postgresql
suven.datasource.druid.type = com.alibaba.druid.pool.DruidDataSource
suven.datasource.druid.driverClassName =org.postgresql.Driver
suven.datasource.druid.filters = stat
suven.datasource.druid.maxActive = 20
suven.datasource.druid.initialSize = 5
suven.datasource.druid.maxWait = 60000
suven.datasource.druid.minIdle = 5
suven.datasource.druid.timeBetweenEvictionRunsMillis = 60000
suven.datasource.druid.minEvictableIdleTimeMillis = 300000
suven.datasource.maxPoolPreparedStatementPerConnectionSize=1
suven.datasource.druid.validationQuery = select 'x';
suven.datasource.druid.testWhileIdle = true
suven.datasource.druid.testOnBorrow = false
suven.datasource.druid.testOnReturn = false
suven.datasource.druid.poolPreparedStatements = true
suven.datasource.druid.maxOpenPreparedStatements = 200

spring.datasource.druid.stat-view-servlet.enabled=false
spring.datasource.druid.statViewServlet.urlPattern=/druid/*
# \u767D\u540D\u5355\uFF1A
#spring.datasource.druid.statViewServlet.allow=
#  IP\u9ED1\u540D\u5355 (\u5B58\u5728\u5171\u540C\u65F6\uFF0Cdeny\u4F18\u5148\u4E8Eallow) : \u5982\u679C\u6EE1\u8DB3deny\u7684\u8BDD\u63D0\u793A:Sorry, you are not permitted to view this page.
#spring.datasource.druid.statViewServlet.deny=
spring.datasource.druid.statViewServlet.loginUsername=admin
spring.datasource.druid.statViewServlet.loginPassword=123456
# \u662F\u5426\u80FD\u591F\u91CD\u7F6E\u6570\u636E.
spring.datasource.druid.statViewServlet.resetEnable=false


spring.datasource.druid.web-stat-filter.enabled=false
spring.datasource.druid.webStatFilter.sessionStatEnable=false
spring.datasource.druid.webStatFilter.profileEnable=false
spring.datasource.druid.webStatFilter.urlPattern=/*
spring.datasource.druid.webStatFilter.exclusions="*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*,/server/druid/*
# \u914D\u7F6E\u65E5\u5FD7\u8F93\u51FA
spring.datasource.druid.filter.slf4j.enabled=false
spring.datasource.druid.filter.slf4j.statement-create-after-log-enabled=false
spring.datasource.druid.filter.slf4j.statement-close-after-log-enabled=false
spring.datasource.druid.filter.slf4j.result-set-open-after-log-enabled=false
spring.datasource.druid.filter.slf4j.result-set-close-after-log-enabled=false
# \u914D\u7F6E\u76D1\u63A7\u7EDF\u8BA1\u62E6\u622A\u7684filters\uFF0C\u53BB\u6389\u540E\u76D1\u63A7\u754C\u9762sql\u65E0\u6CD5\u7EDF\u8BA1\uFF0C'wall'\u7528\u4E8E\u9632\u706B\u5899
spring.datasource.filters=stat,wall,log4j
# \u901A\u8FC7connectProperties\u5C5E\u6027\u6765\u6253\u5F00mergeSql\u529F\u80FD\uFF1B\u6162SQL\u8BB0\u5F55
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# \u5408\u5E76\u591A\u4E2ADruidDataSource\u7684\u76D1\u63A7\u6570\u636E
spring.datasource.useGlobalDataSourceStat=true

spring.filter.dos.urlPatterns=/server/*
spring.filter.dos.exclusions=/druid/*,/server/druid/*



#-------------------------------------END--------------------------------------

# 根据项目需要增加数据源,减轻服务器数据库压力,只需要根据规则配置从数据源,并重启项目即可...(,1,2),其它模块也一样
#---------------------------------ASSETS--------------------------------#
#suven.datasource.assets.master.enabled=false ## 关闭指定模块的master主数据源,默认值为true
#suven.datasource.assets.slave.enabled=false  ## 关闭指定模块的所有slave从数据源,默认值为true

#master
suven.datasource.assets.master.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.assets.master.username = test
suven.datasource.assets.master.password = test

#slave
suven.datasource.assets.slave.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.assets.slave.username = test
suven.datasource.assets.slave.password = test

#slave1
suven.datasource.assets.slave1.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.assets.slave1.username = test
suven.datasource.assets.slave1.password = test

#slave2
suven.datasource.assets.slave2.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.assets.slave2.username = test
suven.datasource.assets.slave2.password = test

#slave3
suven.datasource.assets.slave3.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.assets.slave3.username = test
suven.datasource.assets.slave3.password = test


#----------------------------------END----------------------------------#



#---------------------------------OAUTH--------------------------------#
#suven.datasource.oauth.master.enabled=false ## 关闭指定模块的master主数据源,默认值为true
#suven.datasource.oauth.slave.enabled=false  ## 关闭指定模块的所有slave从数据源,默认值为true
#master
suven.datasource.oauth.master.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.oauth.master.username = test
suven.datasource.oauth.master.password = test

#slave
suven.datasource.oauth.slave.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.oauth.slave.username = test
suven.datasource.oauth.slave.password = test

#slave1
suven.datasource.oauth.slave1.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.oauth.slave1.username = test
suven.datasource.oauth.slave1.password = test
#----------------------------------END-----------------------------------#





#---------------------------------USER--------------------------------#
#suven.datasource.user.master.enabled=false ## 关闭指定模块的master主数据源,默认值为true
#suven.datasource.user.slave.enabled=false  ## 关闭指定模块的所有slave从数据源,默认值为true
#master
suven.datasource.user.master.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.user.master.username = test
suven.datasource.user.master.password = test

#slave
suven.datasource.user.slave.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.user.slave.username = test
suven.datasource.user.slave.password = test

#slave1
suven.datasource.user.slave1.url = jdbc:mysql://127.0.0.1:3306/datasource_name?autoReconnect=true&characterEncoding=utf-8
suven.datasource.user.slave1.username = test
suven.datasource.user.slave1.password = test
#----------------------------------END----------------------------------#


转载于:https://my.oschina.net/suventop/blog/1935021

你可能感兴趣的:(Spring Boot动态创建Bean,DynamicDataSource实现读写分离的多数据源)