自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開發(fā) 后端
下邊我將通過一個(gè)簡(jiǎn)單的基于springboot+aop的案例來實(shí)現(xiàn)如何通過自定義注解切換不同的數(shù)據(jù)源進(jìn)行讀數(shù)據(jù)操作,同時(shí)也將結(jié)合部分源碼的內(nèi)容進(jìn)行講解。

 [[318595]]

針對(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ù)層需要涉及到查詢多種同數(shù)據(jù)庫(kù)的場(chǎng)景下,我們通常需要在執(zhí)行sql的時(shí)候動(dòng)態(tài)指定對(duì)應(yīng)的datasource。

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

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

  1. package mutidatasource.annotation;  
  2. import mutidatasource.config.DataSourceConfigRegister;  
  3. import mutidatasource.enums.SupportDatasourceEnum;  
  4. import org.springframework.context.annotation.Import;  
  5. import org.springframework.stereotype.Component;  
  6. import java.lang.annotation.*;  
  7. /**  
  8.  * 注入數(shù)據(jù)源  
  9.  *  
  10.  * @author idea  
  11.  * @data 2020/3/7  
  12.  */  
  13. @Target({ElementType.METHOD,ElementType.TYPE})  
  14. @Retention(RetentionPolicy.RUNTIME)  
  15. @Documented  
  16. @Import(DataSourceConfigRegister.class)  
  17. public @interface AppDataSource {  
  18.     SupportDatasourceEnum[] datasourceType();  

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

  1. package mutidatasource.enums;  
  2. import lombok.AllArgsConstructor;  
  3. import lombok.Getter;  
  4. import lombok.NoArgsConstructor;  
  5. /**  
  6.  * 目前支持的數(shù)據(jù)源信息  
  7.  *  
  8.  * @author idea  
  9.  * @data 2020/3/7  
  10.  */  
  11. @AllArgsConstructor  
  12. @Getter  
  13. public enum SupportDatasourceEnum {  
  14.     PROD_DB("jdbc:mysql://localhost:3306/db-prod?useUnicode=true&characterEncoding=utf8","root","root","db-prod"), 
  15.     DEV_DB("jdbc:mysql://localhost:3306/db-dev?useUnicode=true&characterEncoding=utf8","root","root","db-dev"),  
  16.     PRE_DB("jdbc:mysql://localhost:3306/db-pre?useUnicode=true&characterEncoding=utf8","root","root","db-pre");  
  17.     String url;  
  18.     String username;  
  19.     String password;  
  20.     String databaseName;  
  21.     @Override  
  22.     public String toString() {  
  23.         return super.toString().toLowerCase();  
  24.     }  

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

  1. package mutidatasource;  
  2. import mutidatasource.annotation.AppDataSource;  
  3. import mutidatasource.enums.SupportDatasourceEnum;  
  4. import org.springframework.boot.SpringApplication;  
  5. import org.springframework.boot.autoconfigure.SpringBootApplication;  
  6. /**  
  7.  * @author idea  
  8.  * @data 2020/3/7  
  9.  */  
  10. @SpringBootApplication  
  11. @AppDataSource(datasourceType = {SupportDatasourceEnum.DEV_DB, SupportDatasourceEnum.PRE_DB, SupportDatasourceEnum.PROD_DB})  
  12. public class SpringApplicationDemo {  
  13.     public static void main(String[] args) {  
  14.         SpringApplication.run(SpringApplicationDemo.class);  
  15.     }  

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

  1. package mutidatasource.config;  
  2. import lombok.extern.slf4j.Slf4j;  
  3. import mutidatasource.annotation.AppDataSource;  
  4. import mutidatasource.core.DataSourceContextHolder;  
  5. import mutidatasource.enums.SupportDatasourceEnum;  
  6. import org.springframework.context.annotation.ImportSelector;  
  7. import org.springframework.core.annotation.AnnotationAttributes;  
  8. import org.springframework.core.type.AnnotationMetadata;  
  9. import org.springframework.stereotype.Component;  
  10. /**  
  11.  * @author idea  
  12.  * @data 2020/3/7  
  13.  */  
  14. @Slf4j  
  15. @Component  
  16. public class DataSourceConfigRegister implements ImportSelector {  
  17.     @Override  
  18.     public String[] selectImports(AnnotationMetadata annotationMetadata) {  
  19.         AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(AppDataSource.class.getName())); 
  20.          System.out.println("#######  datasource import #######");  
  21.         if (null != attributes) {  
  22.             Object object = attributes.get("datasourceType");  
  23.             SupportDatasourceEnum[] supportDatasourceEnums = (SupportDatasourceEnum[]) object;  
  24.             for (SupportDatasourceEnum supportDatasourceEnum : supportDatasourceEnums) {  
  25.                 DataSourceContextHolder.addDatasource(supportDatasourceEnum);  
  26.             }  
  27.         }  
  28.         return new String[0];  
  29.     }  

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

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

  1. package mutidatasource.core;  
  2. import mutidatasource.enums.SupportDatasourceEnum;  
  3. import java.util.HashSet;  
  4. /**  
  5.  * @author idea  
  6.  * @data 2020/3/7  
  7.  */  
  8. public class DataSourceContextHolder {  
  9.     private static final HashSet<SupportDatasourceEnum> dataSourceSet = new HashSet<>();  
  10.     private static final ThreadLocal<String> databaseHolder = new ThreadLocal<>();  
  11.     public static void setDatabaseHolder(SupportDatasourceEnum supportDatasourceEnum) {  
  12.         databaseHolder.set(supportDatasourceEnum.toString()); 
  13.     }  
  14.     /**  
  15.      * 取得當(dāng)前數(shù)據(jù)源  
  16.      *  
  17.      * @return  
  18.      */  
  19.     public static String getDatabaseHolder() {  
  20.         return databaseHolder.get();  
  21.     }  
  22.     /**  
  23.      * 添加數(shù)據(jù)源  
  24.      *  
  25.      * @param supportDatasourceEnum  
  26.      */  
  27.     public static void addDatasource(SupportDatasourceEnum supportDatasourceEnum) {  
  28.         dataSourceSet.add(supportDatasourceEnum);  
  29.     }  
  30.     /**  
  31.      * 獲取當(dāng)期應(yīng)用所支持的所有數(shù)據(jù)源  
  32.      *  
  33.      * @return  
  34.      */  
  35.     public static HashSet<SupportDatasourceEnum> getDataSourceSet() {  
  36.         return dataSourceSet;  
  37.     }  
  38.     /**  
  39.      * 清除上下文數(shù)據(jù)  
  40.      */  
  41.     public static void clear() {  
  42.         databaseHolder.remove();  
  43.     }  

spring內(nèi)部的AbstractRoutingDataSource動(dòng)態(tài)路由數(shù)據(jù)源里面有一個(gè)抽象方法叫做

determineCurrentLookupKey,這個(gè)方法適用于提供給開發(fā)者自定義對(duì)應(yīng)數(shù)據(jù)源的查詢key。 

  1. package mutidatasource.core;  
  2. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;  
  3. /**  
  4.  * @author idea  
  5.  * @data 2020/3/7  
  6.  */  
  7. public class DynamicDataSource extends AbstractRoutingDataSource {  
  8.     @Override  
  9.     protected Object determineCurrentLookupKey() {  
  10.         String dataSource = DataSourceContextHolder.getDatabaseHolder();  
  11.         return dataSource;  
  12.     }  

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

  1. package mutidatasource.core;  
  2. import com.alibaba.druid.pool.DruidDataSource;  
  3. import lombok.extern.slf4j.Slf4j;  
  4. import mutidatasource.enums.SupportDatasourceEnum;  
  5. import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;  
  6. import org.springframework.context.annotation.Bean;  
  7. import org.springframework.context.annotation.Primary;  
  8. import org.springframework.stereotype.Component;  
  9. import javax.sql.DataSource;  
  10. import java.util.HashMap;  
  11. import java.util.HashSet;  
  12. /**  
  13.  * @author idea  
  14.  * @data 2020/3/7  
  15.  */  
  16. @Slf4j  
  17. @Component  
  18. public class DynamicDataSourceConfiguration {  
  19.     @Bean  
  20.     @Primary  
  21.     @ConditionalOnMissingBean  
  22.     public DataSource dataSource() {  
  23.         System.out.println("init datasource");  
  24.         DynamicDataSource dynamicDataSource = new DynamicDataSource();  
  25.         //設(shè)置原始數(shù)據(jù)源  
  26.         HashMap<Object, Object> dataSourcesMap = new HashMap<>();  
  27.         HashSet<SupportDatasourceEnum> dataSet = DataSourceContextHolder.getDataSourceSet();  
  28.         for (SupportDatasourceEnum supportDatasourceEnum : dataSet) {  
  29.             DataSource dataSource = this.createDataSourceProperties(supportDatasourceEnum);  
  30.             dataSourcesMap.put(supportDatasourceEnum.toString(), dataSource); 
  31.         }  
  32.         dynamicDataSource.setTargetDataSources(dataSourcesMap);  
  33.         dynamicDataSource.setDefaultTargetDataSource(createDataSourceProperties(SupportDatasourceEnum.PRE_DB));  
  34.         return dynamicDataSource;  
  35.     }  
  36.     private synchronized DataSource createDataSourceProperties(SupportDatasourceEnum supportDatasourceEnum) {  
  37.         DruidDataSource druidDataSource = new DruidDataSource();  
  38.         druidDataSource.setUrl(supportDatasourceEnum.getUrl());  
  39.         druidDataSource.setUsername(supportDatasourceEnum.getUsername());  
  40.         druidDataSource.setPassword(supportDatasourceEnum.getPassword());  
  41.         //具體配置  
  42.         druidDataSource.setMaxActive(100);  
  43.         druidDataSource.setInitialSize(5);  
  44.         druidDataSource.setMinIdle(1);  
  45.         druidDataSource.setMaxWait(30000);  
  46.         //間隔多久才進(jìn)行一次檢測(cè),檢測(cè)需要關(guān)閉的空閑連接,單位是毫秒  
  47.         druidDataSource.setTimeBetweenConnectErrorMillis(60000);  
  48.         return druidDataSource;  
  49.     }  

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

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

  1. package mutidatasource.annotation;  
  2. import mutidatasource.enums.SupportDatasourceEnum;  
  3. import java.lang.annotation.*;  
  4. /**  
  5.  * @author idea  
  6.  * @data 2020/3/7  
  7.  */  
  8. @Target({ElementType.METHOD,ElementType.TYPE})  
  9. @Retention(RetentionPolicy.RUNTIME)  
  10. @Documented  
  11. public @interface UsingDataSource {  
  12.     SupportDatasourceEnum type()  ;  

然后,借助了spring的aop來做切面攔截: 

  1. package mutidatasource.core;  
  2. import lombok.extern.slf4j.Slf4j;  
  3. import mutidatasource.annotation.UsingDataSource;  
  4. import org.aspectj.lang.JoinPoint;  
  5. import org.aspectj.lang.ProceedingJoinPoint;  
  6. import org.aspectj.lang.Signature;  
  7. import org.aspectj.lang.annotation.*;  
  8. import org.aspectj.lang.reflect.MethodSignature;  
  9. import org.springframework.context.annotation.Configuration;  
  10. import org.springframework.core.annotation.AnnotationUtils;  
  11. import org.springframework.core.annotation.Order;  
  12. import org.springframework.stereotype.Component;  
  13. import java.lang.reflect.Method;  
  14. import java.util.Arrays;  
  15. /**  
  16.  * @author idea  
  17.  * @data 2020/3/7  
  18.  */  
  19. @Slf4j  
  20. @Aspect  
  21. @Configuration  
  22. public class DataSourceAspect {  
  23.     public DataSourceAspect(){  
  24.         System.out.println("this is init");  
  25.     }  
  26.     @Pointcut("@within(mutidatasource.annotation.UsingDataSource) || " +  
  27.             "@annotation(mutidatasource.annotation.UsingDataSource)")  
  28.     public void pointCut(){  
  29.     }  
  30.     @Before("pointCut() && @annotation(usingDataSource)")  
  31.     public void doBefore(UsingDataSource usingDataSource){  
  32.         log.debug("select dataSource---"+usingDataSource.type());  
  33.         DataSourceContextHolder.setDatabaseHolder(usingDataSource.type()); 
  34.     }  
  35.     @After("pointCut()")  
  36.     public void doAfter(){  
  37.         DataSourceContextHolder.clear();  
  38.     }  

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

  1. package mutidatasource.controller;  
  2. import lombok.extern.slf4j.Slf4j;  
  3. import mutidatasource.annotation.UsingDataSource;  
  4. import mutidatasource.enums.SupportDatasourceEnum;  
  5. import org.springframework.beans.factory.annotation.Autowired;  
  6. import org.springframework.jdbc.core.JdbcTemplate;  
  7. import org.springframework.web.bind.annotation.GetMapping;  
  8. import org.springframework.web.bind.annotation.RequestMapping;  
  9. import org.springframework.web.bind.annotation.RestController;  
  10. /**  
  11.  * @author idea  
  12.  * @data 2020/3/8  
  13.  */  
  14. @RestController  
  15. @RequestMapping(value = "/test" 
  16. @Slf4j  
  17. public class TestController {  
  18.     @Autowired  
  19.     private JdbcTemplate jdbcTemplate;  
  20.     @GetMapping(value = "/testDev" 
  21.     @UsingDataSource(type=SupportDatasourceEnum.DEV_DB)  
  22.     public void testDev() {  
  23.         showData();  
  24.     }  
  25.     @GetMapping(value = "/testPre" 
  26.     @UsingDataSource(type=SupportDatasourceEnum.PRE_DB)  
  27.     public void testPre() {  
  28.         showData();  
  29.     }  
  30.     private void showData() {  
  31.         jdbcTemplate.queryForList("select * from test1").forEach(row -> log.info(row.toString()));  
  32.     }  

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

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

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

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

具體代碼如下:

  1. /**  
  2.    * Retrieve the current target DataSource. Determines the  
  3.    * {@link #determineCurrentLookupKey() current lookup key}, performs  
  4.    * a lookup in the {@link #setTargetDataSources targetDataSources} map,  
  5.    * falls back to the specified  
  6.    * {@link #setDefaultTargetDataSource default target DataSource} if necessary. 
  7.    * @see #determineCurrentLookupKey()  
  8.    */  
  9.   protected DataSource determineTargetDataSource() {  
  10.       Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");  
  11.       //這里面注入我們當(dāng)前線程使用的數(shù)據(jù)源  
  12.       Object lookupKey = determineCurrentLookupKey();  
  13.       //在初始化數(shù)據(jù)源的時(shí)候需要我們?nèi)ソoresolvedDataSources進(jìn)行注入  
  14.       DataSource dataSource = this.resolvedDataSources.get(lookupKey);  
  15.       if (dataSource == null && (this.lenientFallback || lookupKey == null)) {  
  16.           dataSource = this.resolvedDefaultDataSource;  
  17.       }  
  18.       if (dataSource == null) {  
  19.           throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");  
  20.       }  
  21.       return dataSource;  
  22.   }  
  23.   /**  
  24.    * Determine the current lookup key. This will typically be  
  25.    * implemented to check a thread-bound transaction context.  
  26.    * <p>Allows for arbitrary keys. The returned key needs  
  27.    * to match the stored lookup key type, as resolved by the  
  28.    * {@link #resolveSpecifiedLookupKey} method.  
  29.    */  
  30.   @Nullable  
  31.   protected abstract Object determineCurrentLookupKey(); 

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

  1. @Override  
  2.     public void afterPropertiesSet() {  
  3.         if (this.targetDataSources == null) {  
  4.             throw new IllegalArgumentException("Property 'targetDataSources' is required");  
  5.         }  
  6.         this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());  
  7.         this.targetDataSources.forEach((key, value) -> {  
  8.             Object lookupKey = resolveSpecifiedLookupKey(key);  
  9.             DataSource dataSource = resolveSpecifiedDataSource(value);  
  10.             this.resolvedDataSources.put(lookupKey, dataSource);  
  11.         });  
  12.         if (this.defaultTargetDataSource != null) {  
  13.             this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);  
  14.         }  
  15.     }  

 

責(zé)任編輯:龐桂玉 來源: Java知音
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)