Skip to content

原理分析

liuqingyao edited this page Mar 22, 2017 · 9 revisions

1.自定义DataSource

Spring JDBC的datasource中有个抽象类:AbstractRoutingDataSource(抽象类)具备动态切换DataSource(接口)的特性 AbstractRoutingDataSource extends AbstractDataSource implements DataSource

AbstractRoutingDataSource的属性

    private Map<Object, Object> targetDataSources;
    private Object defaultTargetDataSource;
    private boolean lenientFallback = true;
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    private Map<Object, DataSource> resolvedDataSources;//这个属性很重要,说明具备多数据源能力
    private DataSource resolvedDefaultDataSource;

AbstractRoutingDataSource的关键方法

protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        //下两行代码实现了数据源的切换,而用哪个数据源最终由determineCurrentLookupKey决定(返回了一个key)
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
        if(dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }

        if(dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        } else {
            return dataSource;
        }
}
//留给我们做扩展用的
protected abstract Object determineCurrentLookupKey();

MultiDataSource的实现细节

package com.company.component.datasource.multi_datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.HashMap;
import java.util.Map;

/**
 * User: yaoliuqing
 * Time: 14-9-26 下午4:46
 */
public class MultipleDataSource extends AbstractRoutingDataSource {

    //保证线程安全
    private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<String>();

    private static final Map<String, String> packageDataSource = new HashMap<String, String>();

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

    public static void usePackageDataSource(String pkgName) {
        dataSourceKey.set(packageDataSource.get(pkgName));
    }

    //实现AbstractRoutingDataSource的determineTargetDataSource
    protected Object determineCurrentLookupKey() {
        String dsName = dataSourceKey.get();
        //阅后即焚
        dataSourceKey.remove();
        return dsName;
    }

    public Map<String, String> getPackageDataSource() {
        return MultipleDataSource.packageDataSource;
    }

    public void setPackageDataSource(Map<String, String> packageDataSource) {
        MultipleDataSource.packageDataSource.putAll(packageDataSource);
    }

}

将原来我们在配置中使用的数据源替换成我们自定义的数据源

2.自定义注解

有了自定义数据源后,我们也要更方便地指定编码中具体某个方法使用哪个数据源 >(其实不自定义数据源也是可以实现多数据源在同一个项目中使用的,非常麻烦和不灵活以至于我已经记不得怎么用了^_^) 如果通过注解就可以指定某个方法用哪个数据源,岂不方便?!特别是如果你工程正处于数据库拆分,数据库不停加从库的场景下。 于是定义了一个注解
package com.company.component.datasource.multi_datasource.annotation;

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

/**
 * User: yaoliuqing
 * Time: 14-9-25 下午5:27
 * 指定操作的数据库
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {

    /**
     * value dataSource的名称
     */
    String value();

}

3.实现切面(根据注解选择DataSource)

AOP的东西就直接上代码吧

package com.company.component.datasource.multi_datasource.aspect;

import java.lang.reflect.Method;

import com.company.component.datasource.multi_datasource.MultipleDataSource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import com.company.component.datasource.multi_datasource.annotation.DataSource;

/**
 * User: yaoliuqing
 * Time: 14-9-25 下午4:03
 */
public class MultipleDataSourceAspect {

    private static final Logger LOG = LoggerFactory.getLogger(MultipleDataSourceAspect.class);

    public Object doAround(ProceedingJoinPoint jp) throws Throwable {

        if (LOG.isDebugEnabled()) {
            LOG.debug("MultipleDataSourceAspectAdvice invoked!");
        }

        Signature signature = jp.getSignature();

        String dataSourceKey = getDataSourceKey(signature);

        if (StringUtils.hasText(dataSourceKey)) {
            //指定自定义数据源使用具体哪个数据源
            MultipleDataSource.setDataSourceKey(dataSourceKey);
        }

        Object jpVal = jp.proceed();

        return jpVal;


    }

    private String getDataSourceKey(Signature signature) {
        if (signature == null) return null;

        if (signature instanceof MethodSignature) {

            //检测方法级注解
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            if (method.isAnnotationPresent(DataSource.class)) {
                return dsSettingInMethod(method);
            }

            //类级注解
            Class declaringClazz = method.getDeclaringClass();
            if (declaringClazz.isAnnotationPresent(DataSource.class)) {
                try {
                    return dsSettingInConstructor(declaringClazz);
                } catch (Exception e) {
                    LOG.error("获取构造方法的DataSource注解失败", e);
                }
            }

            //包级注解,为了配置方便,包注解和类以及方法注解方式不同
            Package pkg = declaringClazz.getPackage();
            dsSettingInPackage(pkg);

        }

        return null;
    }

    private String dsSettingInMethod(Method method) {
        DataSource dataSource = method.getAnnotation(DataSource.class);
        return dataSource.value();
    }

    private String dsSettingInConstructor(Class clazz) {
        DataSource dataSource = (DataSource) clazz.getAnnotation(DataSource.class);
        return dataSource.value();
    }

    private void dsSettingInPackage(Package pkg) {
        if (LOG.isDebugEnabled()) {
            LOG.debug(pkg.getName());
        }
        MultipleDataSource.usePackageDataSource(pkg.getName());
    }


}

因为我们此时DAO层绑定的数据源是自定义的,所以可以通过指定dataSourceKey来指定dataSource。 在切面实现了对包级,类级,方法级的配置检查(包级比较鸡肋实践中基本不用)

Clone this wiki locally