自定义注解实现(AOP Aspect)-多租户多商户多数据源动态切换

根据header中的租户/商户标识或者指定的参数(租户/商户标识)动态切换数据源

public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal<String> dataSourceKey = ThreadLocal.withInitial(() -> "defaultDataSource");

    public static Map<Object, Object> dataSourcesMap = new ConcurrentHashMap<>(10);

    static {
        dataSourcesMap.put("defaultDataSource", SpringUtils.getBean("defaultDataSource"));
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSource.dataSourceKey.get();
    }

    public static void setDataSource(String dataSource) {
        DynamicDataSource.dataSourceKey.set(dataSource);
        DynamicDataSource dynamicDataSource = (DynamicDataSource) SpringUtils.getBean("dataSource");
        dynamicDataSource.afterPropertiesSet();
    }

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

    public static void clear() {
        DynamicDataSource.dataSourceKey.remove();
    }

    public static void setDataSourceKey(String dataSource) {
        DynamicDataSource.dataSourceKey.set(dataSource);
    }

    public static void putIfAbsent(String url, DruidDataSource druidDataSource) {
        DynamicDataSource.dataSourceKey.set(url);
        Object oldDruidDataSource = DynamicDataSource.dataSourcesMap.putIfAbsent(url, druidDataSource);
        if (druidDataSource != oldDruidDataSource) {
            addDruidDataSource();
        }
    }

    private static synchronized void addDruidDataSource() {
        DynamicDataSource dynamicDataSource = (DynamicDataSource) SpringUtils.getBean("dataSource");
        dynamicDataSource.afterPropertiesSet();
    }
}

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource defaultDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    @DependsOn({"springUtils", "defaultDataSource"})
    public DynamicDataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(DynamicDataSource.dataSourcesMap);
        return dynamicDataSource;
    }
}
@Component
@Aspect
@Order(0)//配置Spring注解事务时,在事务之前切换数据源
@Slf4j
public class DynamicDataSourceAop {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private IOperateService iOperateService;


    @Pointcut("@annotation(com.bp.annotation.SwitchDataBase)")
    public void pointCut() {
        System.out.println("找到切面");
    }


    /**
     * 执行方法前更换数据源
     *
     * @param joinPoint
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature sign = (MethodSignature) joinPoint.getSignature();
        Method method = sign.getMethod();
        SwitchDataBase annotation = method.getAnnotation(SwitchDataBase.class);
        if (annotation == null) {
            //获取类上的注解
            annotation = joinPoint.getTarget().getClass().getAnnotation(SwitchDataBase.class);
            if (annotation == null) {
                //获取接口上的注解
                for (Class<?> cls : joinPoint.getClass().getInterfaces()) {
                    annotation = cls.getAnnotation(SwitchDataBase.class);
                    if (annotation != null) {
                        break;
                    }
                }
            }
        }
        Long bpId = 0L;
        Object jdbcUrl = "";
        String jdbcUrlKey = "";
        if (annotation != null) {
            DruidDataSource druidDataSource = new DruidDataSource();
            int index = annotation.paramIndex();
            Object[] args = joinPoint.getArgs();
            if (CutType.token.equals(annotation.value())) {
                String token = args[index].toString();
                bpId = JWTUtil.getBpId(token);
                if (bpId == null || bpId == 0L) {
                    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                    HttpServletRequest request = attributes.getRequest();
                    bpId = JWTUtil.getBpId(request.getHeader("token"));
                }
            } else if (CutType.bpId.equals(annotation.value())) {
                bpId = Long.parseLong(args[index].toString());
            }
            if (bpId == null || bpId == 0L) {
                throw new RuntimeException("系统繁忙,请稍后重试");
            }
            ValueOperations ops = redisTemplate.opsForValue();
            jdbcUrlKey = "bop.datasource.aop.jdbcUrl" + bpId;
            jdbcUrl = ops.get(jdbcUrlKey);//bop.datasource.aop.jdbcUrl+bpId    ->    dataSourceUrl
            if (null != jdbcUrl && !StringUtil.isBlank(jdbcUrl.toString()) && null != DynamicDataSource.dataSourcesMap.get(jdbcUrl) && null != DynamicDataSource.getDataSource()) {
                DynamicDataSource.setDataSourceKey(jdbcUrl.toString());
            } else {
                Map<String, Object> dataSourceMap = new HashMap<>();
                dataSourceMap.put("bpId", bpId);
                RequestBean param = new RequestBean();
                param.setData(dataSourceMap);
                //todo 根据租户/商户查询数据源
                ResponseBean responseBean = iOperateService.getDateSourceByBpId(param);
                if (responseBean != null && ResponseConstant.SUCCESS.getCode().equals(responseBean.getCode())) {
                    JSONObject dateSource = JSONObject.parseObject(JSONObject.toJSONString(responseBean.getData()));
                    if (dateSource != null && !StringUtil.isBlank(dateSource.getString("url"))) {
                        if (null != DynamicDataSource.dataSourcesMap.get(dateSource.getString("url")) && null != DynamicDataSource.getDataSource()) {
                            DynamicDataSource.setDataSourceKey(jdbcUrl.toString());
                            ops.set(jdbcUrlKey, dateSource.getString("url"));
                            redisTemplate.expire(jdbcUrlKey, 5, TimeUnit.HOURS);
                        } else {
                            druidDataSource.setDriverClassName(dateSource.getString("driver_class_name"));
                            druidDataSource.setUrl(dateSource.getString("url"));
                            druidDataSource.setUsername(dateSource.getString("user_name"));
                            druidDataSource.setPassword(dateSource.getString("pass_word"));
                            druidDataSource.setInitialSize(1);//#初始连接数。
                            druidDataSource.setMinIdle(1);//#最小连接数 设为0表示无限制。
                            druidDataSource.setMaxActive(50);//#最大连接数 设为0表示无限制。
                            druidDataSource.setMaxWait(60000);//超时等待时
                            druidDataSource.setRemoveAbandoned(true);
                            druidDataSource.setRemoveAbandonedTimeout(100);
//                            druidDataSource.setLogAbandoned(true);
//                            druidDataSource.setTestOnBorrow(true);
                            druidDataSource.setTestWhileIdle(true);
                            druidDataSource.setTimeBetweenEvictionRunsMillis(150000);//#unit is millisecond,间隔检查150s
                            druidDataSource.setMinEvictableIdleTimeMillis(150000);//#unit is millisecond,最小空闲150s
                            druidDataSource.setMaxEvictableIdleTimeMillis(600000);//#unit is millisecond,最大空闲600s
                            druidDataSource.setKeepAlive(true);
                            druidDataSource.setValidationQuery("SELECT 1 FROM DUAL");
                            ops.set(jdbcUrlKey, dateSource.getString("url"));
                            redisTemplate.expire(jdbcUrlKey, 5, TimeUnit.HOURS);
                            DynamicDataSource.setDataSourceKey(jdbcUrl.toString());
                            DynamicDataSource.putIfAbsent(dateSource.getString("url"), druidDataSource);
                        }
                    } else {
                        throw new RuntimeException("系统繁忙,请稍后重试");
                    }
                } else {
                    throw new RuntimeException("系统繁忙,请稍后重试");
                }
            }
        }
        Object[] args = joinPoint.getArgs();
        return joinPoint.proceed(args);
    }
}

@Component
public class SpringUtils implements ApplicationContextAware {
    @Autowired
    private static ApplicationContext applicationContext;

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

    public static <T> T getBean(Class<T> clazz) {
        return SpringUtils.applicationContext.getBean(clazz);
    }

    public static Object getBean(String name) {
        return SpringUtils.applicationContext.getBean(name);
    }

    public static String getProperty(String key) {
        return SpringUtils.applicationContext.getEnvironment().getProperty(key);
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值