乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      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)似文章 更多