真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

SpringBoot中怎么利用AOP構(gòu)建多數(shù)據(jù)源

SpringBoot中怎么利用AOP構(gòu)建多數(shù)據(jù)源,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。

站在用戶的角度思考問(wèn)題,與客戶深入溝通,找到遼寧網(wǎng)站設(shè)計(jì)與遼寧網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、國(guó)際域名空間、雅安服務(wù)器托管、企業(yè)郵箱。業(yè)務(wù)覆蓋遼寧地區(qū)。

當(dāng)在業(yè)務(wù)層需要涉及到查詢多種同數(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è)專門用于申明當(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里面,如果后邊需要靈活處理的話,可以將這些配置信息抽取出來(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)類上邊進(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)類頭部的注解所指定的數(shù)據(jù)源類型:

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ù)源類型信息了,這里你會(huì)看到一個(gè)叫做DataSourceContextHolder的角色。這個(gè)對(duì)象主要是用于對(duì)每個(gè)請(qǐng)求線程的數(shù)據(jù)源信息做統(tǒng)一的分配和管理。

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

package mutidatasource.core;  import mutidatasource.enums.SupportDatasourceEnum;  import java.util.HashSet;  /**   * @author idea   * @data 2020/3/7   */  public class DataSourceContextHolder {      private static final HashSet dataSourceSet = new HashSet<>();      private static final ThreadLocal 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 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ù)源的查詢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ù)源的配置類如下:這里面我默認(rèn)該應(yīng)用配置類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 dataSourcesMap = new HashMap<>();          HashSet 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)前線程所需要使用的數(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è)試類如下所示:

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)部類里面包含了多種用于做數(shù)據(jù)源映射的map數(shù)據(jù)結(jié)構(gòu)。

SpringBoot中怎么利用AOP構(gòu)建多數(shù)據(jù)源

在該類的最底部,有一個(gè)determineCurrentLookupKey函數(shù),也就是上邊我們所提及的使用于查詢當(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)前線程使用的數(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.     * 

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();

而在該類的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);          }      }

關(guān)于SpringBoot中怎么利用AOP構(gòu)建多數(shù)據(jù)源問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。


標(biāo)題名稱:SpringBoot中怎么利用AOP構(gòu)建多數(shù)據(jù)源
鏈接地址:http://weahome.cn/article/peidod.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部