注:数据源注入方式以AOP方式写入

一、首先获取当前配置文件默认数据源及数据库中配置的所有数据源

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.sql.DataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;

/**
 * 数据源配置类
 *
 * @author Lynch
 */
@Slf4j
@Configuration
public class DataSourceComponent implements EnvironmentAware {
    private Environment evn;

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

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


    @Primary//不加这个会报错。
    @Bean(name = "multiDataSource")
    public MultiRouteDataSource exampleRouteDataSource() {
        MultiRouteDataSource multiDataSource = new MultiRouteDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        // 获取所有数据源配置
        String typeStr = evn.getProperty("spring.datasource.dynamic.datasource.master.type");
        String url = evn.getProperty("spring.datasource.dynamic.datasource.master.url");
        String name = evn.getProperty("spring.datasource.dynamic.datasource.master.name");
        String username = evn.getProperty("spring.datasource.dynamic.datasource.master.username");
        String password = evn.getProperty("spring.datasource.dynamic.datasource.master.password");
        Map defauleDataSourceProperties = new HashMap();
        defauleDataSourceProperties.put("url",url);
        defauleDataSourceProperties.put("name",name);
        defauleDataSourceProperties.put("username",username);
        defauleDataSourceProperties.put("password",password);

        evn.getProperty("spring.datasource.dynamic.datasource.master");
        Class<? extends DataSource> clazz = getDataSourceType(typeStr);
        // 绑定默认数据源参数 也就是主数据源
        System.out.println("当前数据源"+defauleDataSourceProperties);
        DataSource consumerDatasource, defaultDatasource = bind(clazz, defauleDataSourceProperties);
        targetDataSources.put("master", defaultDatasource);
        // 获取其他数据源配置
        Connection conn = null;
        try {
            conn = defaultDatasource.getConnection();
            String sql = "select * from sys_tenant ";
            PreparedStatement preparedStatement = conn.prepareStatement(sql);
            ResultSet rs = preparedStatement.executeQuery();
            while (rs.next()) {
                String key = rs.getString("id");
                defauleDataSourceProperties.put("name", key);
                defauleDataSourceProperties.put("url", rs.getString("url"));
                defauleDataSourceProperties.put("username", rs.getString("username"));
                defauleDataSourceProperties.put("password", rs.getString("password"));
                // 绑定参数
                consumerDatasource = bind(clazz, defauleDataSourceProperties);
                targetDataSources.put(key, consumerDatasource);
            }
            rs.close();
            preparedStatement.close();
            conn.close();
        } catch (SQLException e) {
            log.error(e.getMessage());
        }
        // 将该bean注册为datasource,不使用springboot自动生成的datasource
        log.info("注册数据源成功,一共注册{}个数据源", targetDataSources.keySet().size());
        multiDataSource.setTargetDataSources(targetDataSources);
        multiDataSource.setDefaultTargetDataSource(defaultDatasource);
        return multiDataSource;
    }


    /**
     * 注册ServletRegistrationBean
     *
     * @return
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean reg = new ServletRegistrationBean();
        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("allow", ""); // 白名单 return reg;
        reg.addInitParameter("loginUsername", "duan");
        reg.addInitParameter("loginPassword", "123456");
        reg.addInitParameter("resetEnable", "false");
        return reg;
    }

    /**
     * 注册FilterRegistrationBean
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
        filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
        filterRegistrationBean.addInitParameter("DruidWebStatFilter", "/*");
        return filterRegistrationBean;
    }

    /**
     * 通过字符串获取数据源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) {
            throw new IllegalArgumentException("can not resolve class with type: " + typeStr); //无法通过反射获取class对象的情况则抛出异常,该情况一般是写错了,所以此次抛出一个runtimeexception
        }
    }

    private <T extends DataSource> T bind(Class<T> 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();
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.evn = environment;
    }
}

二、未注入数据源所需要的工具类及配置文件

/**
 * 数据源上下文
 *
 * @author Lynch
 */
public class DataSourceContext {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSource(String value) {
        contextHolder.set(value);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * DataSource路由类
 *
 * 重写的函数决定了最后选择的DataSource
 *
 * @author Lynch
 */
public class MultiRouteDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 通过绑定线程的数据源上下文实现多数据源的动态切换,有兴趣的可以去查阅资料或源码
        return DataSourceContext.getDataSource();
    }
}

三、添加APO拦截所有controller请求

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.config.mybatis.config.DataSourceContext;
import org.jeecg.config.mybatis.util.IgnoreDynamicData;
import org.jeecg.modules.system.service.ISysTenantService;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;

@Component
@Aspect
@Order(-1)
@Slf4j
public class BaseDynamicDataSourceAspect {

    @Resource
    private ISysTenantService sysTenantService;


    /**
     * 普通Controller切面(interfaces)
     */
    @Pointcut("execution(public * org.jeecg.modules.interfaces.*.controller.*.*(..))")
    public void addInterfaces(){}

    /**
     * 普通Controller切面(cas、message、moitor、ngalain、oss、quartz、system)
     */
    @Pointcut("execution(public * org.jeecg.modules.*.controller.*.*(..))")
    public void addCas(){}

    @Around("addInterfaces() || addCas()")
    public Object Interceptor(ProceedingJoinPoint pjp){
        Object result = null;
        MethodSignature signature = (MethodSignature)pjp.getSignature();
        Annotation anoIgnore=signature.getMethod().getAnnotation(IgnoreDynamicData.class);
        try {
            //如果是bpm操作业务数据 获取方法中的数据源参数作为拦截目标
            if(anoIgnore!=null){
                result=pjp.proceed();
            }else{
                //获取当前用户
                LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
                log.info("当前用户:"+sysUser);
                //获取用户所对应的数据源
                if(sysUser == null ){
                    log.error("获取动态数据源为空,使用默认数据源:master");
                }else{
                    if(sysUser.getTenantId() == null || sysUser.getTenantId().equals("")){
                        log.error("获取动态数据源为空,使用默认数据源:master");
                    }else{
                        DataSourceContext.setDataSource(sysUser.getTenantId());
                        log.info("当前数据源ID:"+sysUser.getTenantId());
                    }

                }
                result=pjp.proceed();
            }
        } catch (Throwable throwable) {
            log.error("获取动态数据源错误,还是原来的数据源");
            return result;
        }
        return result;
    }


}

说明:此处的tenantId与DataSourceComponent类中遍历绑定数据源中的key必须对应

四、AOP所需要的配置文件

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 动态数据源注解出现这个注解的方法。
 * john
 */
@Target({ElementType.METHOD})
@Retention(RUNTIME)
@Documented
public @interface DynamicData {
    String value() default "";
    String dbname() default "";
    String schema() default "";
}
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 需要被忽略动态数据源的方法
 * john
 */
@Target({ElementType.METHOD})
@Retention(RUNTIME)
@Documented
public @interface IgnoreDynamicData {
    String value() default "";
}

流程说明:
1.项目初始化加载默认数据源。
2.根据默认数据元查询数据库中配置的所有数据源进行初始化操作
3.使用AOP与Around将所有Controller中的请求进行拦截
4.LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
根据当前的登录用户(项目中设置的时全局的,用户登陆后回将登录用户设置未全局用户),查询用 户所配置的tenantId。并进行切换数据源