論持久戰之Spring多數據源管理源碼分析

Spring多數據源管理實現原理

[TOC]

應用場景:

大部分單一架構項目連接一臺數據庫服務器,但隨著業務的增加數據庫數據量不斷飆升,數據庫達到性能瓶頸,大部分技術人員都會對數據庫主從配置;既然讀寫分離那就需要連接兩個不同的數據庫,這時候Spring多數據源管理類AbstractRoutingDataSource就要派上用場了(排除使用數據庫集群管理工具統一管理的應用場景)

源碼分析:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    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;
    ...
}

通過源碼可以看出該類是一個抽象類,定義了6個屬性。
targetDataSources:是一個map類型該屬性正是用來維護項目中多個數據源
defaultTargetDataSource:通過屬性名很直觀的可以理解它的作用(默認數據源)
lenientFallback:默認為true,無需改動
dataSourceLookup:查找數據源接口的名稱
resolvedDataSources:如果該字段沒有賦值,就是targetDataSources
resolvedDefaultDataSource:改變后的數據源

public interface DataSourceLookup {

    /**
     * Retrieve the DataSource identified by the given name.
     * @param dataSourceName the name of the DataSource
     * @return the DataSource (never {@code null})
     * @throws DataSourceLookupFailureException if the lookup failed
     */
    DataSource getDataSource(String dataSourceName) throws DataSourceLookupFailureException;

}

該類是一個interface并且只有一個方法getDataSource,通過方法的參數名稱應該清楚傳入一個字符類型的數據源名稱獲取DataSource

深入理解:

使用數據源的目的就是要獲取Connection,接下來就從AbstractRoutingDataSource的getConnection方法一探究竟。

@Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }

直接進入determineTargetDataSource方法

/**
     * Retrieve the current target DataSource. Determines the
     * {@link #determineCurrentLookupKey() current lookup key}, performs
     * a lookup in the {@link #setTargetDataSources targetDataSources} map,
     * falls back to the specified
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
     * @see #determineCurrentLookupKey()
     */
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        //該方法是一個抽象方法,返回要從resolvedDataSources查找key,該方法還會實現檢查線程綁定事務上下文。
        Object lookupKey = determineCurrentLookupKey();
        //從resolvedDataSources中取出數據源并返回
        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 + "]");
        }
        return dataSource;
    }

代碼實現

實現AbstractRoutingDataSource重寫determineCurrentLookupKey

/**
 * @author yangzhao
 * Created by 17/2/7.
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceName = DataSourceContextHolder.getDataSourceName();
        return dataSourceName;
    }
}

定義DataSourceContextHolder

/**
 * 該類內部維護了{@link ThreadLocal}
 * @author yangzhao
 * Created by 17/2/7.
 */
public class DataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    /**
     * @Description: 設置數據源類型
     * @param dataSourceName  數據源名稱
     * @return void
     * @throws
     */
    public static void setDataSourceName(String dataSourceName) {contextHolder.set(dataSourceName);}

    /**
     * @Description: 獲取數據源名稱
     * @param
     * @return String
     * @throws
     */
    public static String getDataSourceName() {
        return contextHolder.get();
    }

    /**
     * @Description: 清除數據源名稱
     * @param
     * @return void
     * @throws
     */
    public static void clearDataSource() {
        contextHolder.remove();
    }
}

通過ThreadLocal類使每個線程獲取獨立的數據源,防止并發訪問時獲取錯誤的數據源

基于SpringAop實現數據源動態切換

注解類DataSource
/**
 * 數據源
 * Created by yangzhao on 17/2/7.
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "defaultSource";
}
增強類(DataSouceAdvisor)
/**
 * 增強類
 * 實現MethodInterceptor接口,通過反射動態解析方法是否標注@DataSource {@link DataSource}注解。
 * 如果已標注@DataSource注解取值,set到{@link DataSourceContextHolder}
 * @author yangzhao
 *         create by 17/10/20
 */
@Component("dataSourceAdvisor")
public class DataSouceAdvisor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Method method = methodInvocation.getMethod();
        Object aThis = methodInvocation.getThis();
        //設置默認數據庫
        DataSourceContextHolder.setDataSourceName("defaultSource");

        DataSource dataSource = aThis.getClass().getAnnotation(DataSource.class);
        if (dataSource!=null){
            DataSourceContextHolder.setDataSourceName(dataSource.value());
        }
        dataSource = method.getAnnotation(DataSource.class);
        if (dataSource!=null){
            DataSourceContextHolder.setDataSourceName(dataSource.value());
        }
        Object proceed = null;
        try {
            proceed = methodInvocation.proceed();
        }catch (Exception e){
            throw e;
        }finally {
            DataSourceContextHolder.clearDataSource();
        }
        return proceed;
    }
}

核心管理類(DataSourceManager真正實現切換)
/**
 * 數據源切換管理類
 *
 * @author yangzhao
 * Created by  17/2/7.
 */
@Component
public class DataSourceManager implements BeanFactoryPostProcessor {

    private final Logger logger = LogManager.getLogger(DataSourceManager.class);
    /**
     * 掃描包
     * 一般項目都是以com開頭所以這里默認為com
     */
    private String pacakgePath = "com";

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //getconfigs
        List<String> configs = getconfigs().stream().collect(Collectors.toList());

        //打印所有生成的expression配置信息
        configs.forEach(s -> logger.info(s));

        //設置aop信息
        setAopInfo(configs,beanFactory);
    }

    /**
     * 設置注冊bean動態AOP信息
     * @param configs
     * @param beanFactory
     */
    private void setAopInfo(List<String> configs, ConfigurableListableBeanFactory beanFactory) {

        if (beanFactory instanceof BeanDefinitionRegistry){
            BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;
            for (String config :configs) {
                //增強器
                RootBeanDefinition advisor = new RootBeanDefinition(DefaultBeanFactoryPointcutAdvisor.class);
                advisor.getPropertyValues().addPropertyValue("adviceBeanName",new RuntimeBeanReference("dataSourceAdvisor").getBeanName());
                //切點類
                RootBeanDefinition pointCut = new RootBeanDefinition(AspectJExpressionPointcut.class);
                pointCut.setScope(BeanDefinition.SCOPE_PROTOTYPE);
                pointCut.setSynthetic(true);
                pointCut.getPropertyValues().addPropertyValue("expression",config);

                advisor.getPropertyValues().addPropertyValue("pointcut",pointCut);
                //注冊到spring容器
                String beanName = BeanDefinitionReaderUtils.generateBeanName(advisor, beanDefinitionRegistry,false);
                beanDefinitionRegistry.registerBeanDefinition(beanName,advisor);
            }
        }

    }
    public Set<String> getconfigs() {
        Set<String> configs = new HashSet<>();
        Reflections reflections = new Reflections(new ConfigurationBuilder().addUrls(ClasspathHelper.forPackage(pacakgePath)));
        //獲取所有標記@DataSource的類
        Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(DataSource.class);
        Iterator<Class<?>> iterator = typesAnnotatedWith.iterator();
        while (iterator.hasNext()){
            Class<?> next = iterator.next();
            //獲取該類所有方法
            Method[] declaredMethods = next.getDeclaredMethods();
            for (Method method:declaredMethods){
                String classAndMethod = method.getDeclaringClass().getCanonicalName()+"."+method.getName();
                //生成expression配置
                String expression = "execution (* "+classAndMethod+"(..))";
                configs.add(expression);
            }
        }
        reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(pacakgePath)).setScanners(new MethodAnnotationsScanner()));
        //獲取所有類中標記@DataSource的方法
        Set<Method> methodsAnnotatedWith = reflections.getMethodsAnnotatedWith(DataSource.class);
        Iterator<Method> it = methodsAnnotatedWith.iterator();
        while (it.hasNext()){
            Method method = it.next();
            String classAndMethod = method.getDeclaringClass().getCanonicalName()+"."+method.getName();
            //生成expression配置
            String expression = "execution (* "+classAndMethod+"(..))";
            configs.add(expression);
        }
        return configs;
    }
}

項目地址:https://github.com/yz-java/multiple-data-sources

以上屬于原創文章,轉載請注明作者@怪咖
QQ:208275451

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。