日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

SpringBoot AOP構(gòu)建多數(shù)據(jù)源的切換實(shí)踐

 鷹兔牛熊眼 2020-03-16

來(lái)源:Java知音

針對(duì)微服務(wù)架構(gòu)中常用的設(shè)計(jì)模塊,通常我們都會(huì)需要使用到druid作為我們的數(shù)據(jù)連接池,當(dāng)架構(gòu)發(fā)生擴(kuò)展的時(shí)候 ,通常面對(duì)的數(shù)據(jù)存儲(chǔ)服務(wù)器也會(huì)漸漸增加,從原本的單庫(kù)架構(gòu)逐漸擴(kuò)展為復(fù)雜的多庫(kù)架構(gòu)。

當(dāng)在業(yè)務(wù)層需要涉及到查詢(xún)多種同數(shù)據(jù)庫(kù)的場(chǎng)景下,我們通常需要在執(zhí)行sql的時(shí)候動(dòng)態(tài)指定對(duì)應(yīng)的datasource。

而Spring的AbstractRoutingDataSource則正好為我們提供了這一功能點(diǎn),下邊我將通過(guò)一個(gè)簡(jiǎn)單的基于springboot+aop的案例來(lái)實(shí)現(xiàn)如何通過(guò)自定義注解切換不同的數(shù)據(jù)源進(jìn)行讀數(shù)據(jù)操作,同時(shí)也將結(jié)合部分源碼的內(nèi)容進(jìn)行講解。

首先我們需要自定義一個(gè)專(zhuān)門(mén)用于申明當(dāng)前java應(yīng)用程序所需要使用到哪些數(shù)據(jù)源信息:

package mutidatasource.annotation;

import mutidatasource.config.DataSourceConfigRegister;
import mutidatasource.enums.SupportDatasourceEnum;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/**
 * 注入數(shù)據(jù)源
 *
 * @author idea
 * @data 2020/3/7
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DataSourceConfigRegister.class)
public @interface AppDataSource {

    SupportDatasourceEnum[] datasourceType();
}

這里為了方便,我將測(cè)試中使用的數(shù)據(jù)源地址都配置在來(lái)enum里面,如果后邊需要靈活處理的話(huà),可以將這些配置信息抽取出來(lái)放在一些配置中心上邊。

package mutidatasource.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * 目前支持的數(shù)據(jù)源信息
 *
 * @author idea
 * @data 2020/3/7
 */
@AllArgsConstructor
@Getter
public enum SupportDatasourceEnum {

    PROD_DB('jdbc:mysql://localhost:3306/db-prod?useUnicode=true&characterEncoding=utf8','root','root','db-prod'),

    DEV_DB('jdbc:mysql://localhost:3306/db-dev?useUnicode=true&characterEncoding=utf8','root','root','db-dev'),

    PRE_DB('jdbc:mysql://localhost:3306/db-pre?useUnicode=true&characterEncoding=utf8','root','root','db-pre');

    String url;
    String username;
    String password;
    String databaseName;

    @Override
    public String toString() {
        return super.toString().toLowerCase();
    }
}

之所以要?jiǎng)?chuàng)建這個(gè)@AppDataSource注解,是要在springboot的啟動(dòng)類(lèi)上邊進(jìn)行標(biāo)注:

package mutidatasource;

import mutidatasource.annotation.AppDataSource;
import mutidatasource.enums.SupportDatasourceEnum;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author idea
 * @data 2020/3/7
 */
@SpringBootApplication
@AppDataSource(datasourceType = {SupportDatasourceEnum.DEV_DB, SupportDatasourceEnum.PRE_DB, SupportDatasourceEnum.PROD_DB})
public class SpringApplicationDemo {

    public static void main(String[] args) {
        SpringApplication.run(SpringApplicationDemo.class);
    }

}

借助springboot的ImportSelector 自定義一個(gè)注冊(cè)器來(lái)獲取啟動(dòng)類(lèi)頭部的注解所指定的數(shù)據(jù)源類(lèi)型:

package mutidatasource.config;

import lombok.extern.slf4j.Slf4j;
import mutidatasource.annotation.AppDataSource;
import mutidatasource.core.DataSourceContextHolder;
import mutidatasource.enums.SupportDatasourceEnum;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;

/**
 * @author idea
 * @data 2020/3/7
 */
@Slf4j
@Component
public class DataSourceConfigRegister implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(AppDataSource.class.getName()));
        System.out.println('#######  datasource import #######');
        if (null != attributes) {
            Object object = attributes.get('datasourceType');
            SupportDatasourceEnum[] supportDatasourceEnums = (SupportDatasourceEnum[]) object;
            for (SupportDatasourceEnum supportDatasourceEnum : supportDatasourceEnums) {
                DataSourceContextHolder.addDatasource(supportDatasourceEnum);
            }
        }
        return new String[0];
    }


}

好的,現(xiàn)在我們已經(jīng)能夠獲取到對(duì)應(yīng)的數(shù)據(jù)源類(lèi)型信息了,這里你會(huì)看到一個(gè)叫做DataSourceContextHolder的角色。這個(gè)對(duì)象主要是用于對(duì)每個(gè)請(qǐng)求線(xiàn)程的數(shù)據(jù)源信息做統(tǒng)一的分配和管理。

在多并發(fā)場(chǎng)景下,為了防止不同線(xiàn)程請(qǐng)求的數(shù)據(jù)源出現(xiàn)“互竄”情況,通常我們都會(huì)使用到threadlocal來(lái)做處理。為每一個(gè)線(xiàn)程都分配一個(gè)指定的,屬于其內(nèi)部的副本變量,當(dāng)當(dāng)前線(xiàn)程結(jié)束之前,記得將對(duì)應(yīng)的線(xiàn)程副本也進(jìn)行銷(xiāo)毀。

package mutidatasource.core;

import mutidatasource.enums.SupportDatasourceEnum;

import java.util.HashSet;

/**
 * @author idea
 * @data 2020/3/7
 */
public class DataSourceContextHolder {

    private static final HashSet<SupportDatasourceEnum> dataSourceSet = new HashSet<>();

    private static final ThreadLocal<String> databaseHolder = new ThreadLocal<>();

    public static void setDatabaseHolder(SupportDatasourceEnum supportDatasourceEnum) {
        databaseHolder.set(supportDatasourceEnum.toString());
    }

    /**
     * 取得當(dāng)前數(shù)據(jù)源
     *
     * @return
     */
    public static String getDatabaseHolder() {
        return databaseHolder.get();
    }

    /**
     * 添加數(shù)據(jù)源
     *
     * @param supportDatasourceEnum
     */
    public static void addDatasource(SupportDatasourceEnum supportDatasourceEnum) {
        dataSourceSet.add(supportDatasourceEnum);
    }

    /**
     * 獲取當(dāng)期應(yīng)用所支持的所有數(shù)據(jù)源
     *
     * @return
     */
    public static HashSet<SupportDatasourceEnum> getDataSourceSet() {
        return dataSourceSet;
    }

    /**
     * 清除上下文數(shù)據(jù)
     */
    public static void clear() {
        databaseHolder.remove();
    }

}

spring內(nèi)部的AbstractRoutingDataSource動(dòng)態(tài)路由數(shù)據(jù)源里面有一個(gè)抽象方法叫做
determineCurrentLookupKey,這個(gè)方法適用于提供給開(kāi)發(fā)者自定義對(duì)應(yīng)數(shù)據(jù)源的查詢(xún)key。

package mutidatasource.core;

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

/**
 * @author idea
 * @data 2020/3/7
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = DataSourceContextHolder.getDatabaseHolder();
        return dataSource;
    }
}

這里我使用的druid數(shù)據(jù)源,所以配置數(shù)據(jù)源的配置類(lèi)如下:這里面我默認(rèn)該應(yīng)用配置類(lèi)PROD數(shù)據(jù)源,用于測(cè)試使用。

package mutidatasource.core;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import mutidatasource.enums.SupportDatasourceEnum;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.HashSet;

/**
 * @author idea
 * @data 2020/3/7
 */
@Slf4j
@Component
public class DynamicDataSourceConfiguration {


    @Bean
    @Primary
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        System.out.println('init datasource');
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //設(shè)置原始數(shù)據(jù)源
        HashMap<Object, Object> dataSourcesMap = new HashMap<>();
        HashSet<SupportDatasourceEnum> dataSet = DataSourceContextHolder.getDataSourceSet();
        for (SupportDatasourceEnum supportDatasourceEnum : dataSet) {
            DataSource dataSource = this.createDataSourceProperties(supportDatasourceEnum);
            dataSourcesMap.put(supportDatasourceEnum.toString(), dataSource);
        }
        dynamicDataSource.setTargetDataSources(dataSourcesMap);
        dynamicDataSource.setDefaultTargetDataSource(createDataSourceProperties(SupportDatasourceEnum.PRE_DB));
        return dynamicDataSource;
    }

    private synchronized DataSource createDataSourceProperties(SupportDatasourceEnum supportDatasourceEnum) {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(supportDatasourceEnum.getUrl());
        druidDataSource.setUsername(supportDatasourceEnum.getUsername());
        druidDataSource.setPassword(supportDatasourceEnum.getPassword());
        //具體配置
        druidDataSource.setMaxActive(100);
        druidDataSource.setInitialSize(5);
        druidDataSource.setMinIdle(1);
        druidDataSource.setMaxWait(30000);
        //間隔多久才進(jìn)行一次檢測(cè),檢測(cè)需要關(guān)閉的空閑連接,單位是毫秒
        druidDataSource.setTimeBetweenConnectErrorMillis(60000);
        return druidDataSource;
    }


}

好了現(xiàn)在一個(gè)基礎(chǔ)的數(shù)據(jù)源注入已經(jīng)可以了,那么我們?cè)撊绾谓柚⒔鈦?lái)實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源的操作呢?

為此,我設(shè)計(jì)了一個(gè)叫做UsingDataSource的注解,通過(guò)利用該注解來(lái)識(shí)別當(dāng)前線(xiàn)程所需要使用的數(shù)據(jù)源操作:

package mutidatasource.annotation;

import mutidatasource.enums.SupportDatasourceEnum;

import java.lang.annotation.*;

/**
 * @author idea
 * @data 2020/3/7
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UsingDataSource {

    SupportDatasourceEnum type()  ;
}

然后,借助了spring的aop來(lái)做切面攔截:

package mutidatasource.core;

import lombok.extern.slf4j.Slf4j;
import mutidatasource.annotation.UsingDataSource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author idea
 * @data 2020/3/7
 */
@Slf4j
@Aspect
@Configuration
public class DataSourceAspect {

    public DataSourceAspect(){
        System.out.println('this is init');
    }



    @Pointcut('@within(mutidatasource.annotation.UsingDataSource) || ' +
            '@annotation(mutidatasource.annotation.UsingDataSource)')
    public void pointCut(){

    }

    @Before('pointCut() && @annotation(usingDataSource)')
    public void doBefore(UsingDataSource usingDataSource){
        log.debug('select dataSource---'+usingDataSource.type());
        DataSourceContextHolder.setDatabaseHolder(usingDataSource.type());
    }

    @After('pointCut()')
    public void doAfter(){
        DataSourceContextHolder.clear();
    }

}

測(cè)試類(lèi)如下所示:

package mutidatasource.controller;

import lombok.extern.slf4j.Slf4j;
import mutidatasource.annotation.UsingDataSource;
import mutidatasource.enums.SupportDatasourceEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author idea
 * @data 2020/3/8
 */
@RestController
@RequestMapping(value = '/test')
@Slf4j
public class TestController {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    @GetMapping(value = '/testDev')
    @UsingDataSource(type=SupportDatasourceEnum.DEV_DB)
    public void testDev() {
        showData();
    }

    @GetMapping(value = '/testPre')
    @UsingDataSource(type=SupportDatasourceEnum.PRE_DB)
    public void testPre() {
        showData();
    }

    private void showData() {
        jdbcTemplate.queryForList('select * from test1').forEach(row -> log.info(row.toString()));
    }


}

最后 啟動(dòng)springboot服務(wù),通過(guò)使用注解即可測(cè)試對(duì)應(yīng)功能。

關(guān)于AbstractRoutingDataSource 動(dòng)態(tài)路由數(shù)據(jù)源的注入原理,

可以看到這個(gè)內(nèi)部類(lèi)里面包含了多種用于做數(shù)據(jù)源映射的map數(shù)據(jù)結(jié)構(gòu)。


在該類(lèi)的最底部,有一個(gè)determineCurrentLookupKey函數(shù),也就是上邊我們所提及的使用于查詢(xún)當(dāng)前數(shù)據(jù)源key的方法。


具體代碼如下:

    /**
     * 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');
        //這里面注入我們當(dāng)前線(xiàn)程使用的數(shù)據(jù)源
        Object lookupKey = determineCurrentLookupKey();
        //在初始化數(shù)據(jù)源的時(shí)候需要我們?nèi)ソoresolvedDataSources進(jìn)行注入
        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;
    }

    /**
     * Determine the current lookup key. This will typically be
     * implemented to check a thread-bound transaction context.
     * <p>Allows for arbitrary keys. The returned key needs
     * to match the stored lookup key type, as resolved by the
     * {@link #resolveSpecifiedLookupKey} method.
     */
    @Nullable
    protected abstract Object determineCurrentLookupKey();

而在該類(lèi)的afterPropertiesSet里面,又有對(duì)于初始化數(shù)據(jù)源的注入操作,這里面的targetDataSources 正是上文中我們對(duì)在初始化數(shù)據(jù)源時(shí)候注入的信息。

    @Override
    public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException('Property 'targetDataSources' is required');
        }
        this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
        this.targetDataSources.forEach((key, value) -> {
            Object lookupKey = resolveSpecifiedLookupKey(key);
            DataSource dataSource = resolveSpecifiedDataSource(value);
            this.resolvedDataSources.put(lookupKey, dataSource);
        });
        if (this.defaultTargetDataSource != null) {
            this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
        }
    }

END

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多