ShardingSphere如何完成與Spring家族無縫整合的?

公众号-JavaEdge發表於2024-05-21

系統整合即ShardingSphere 和 Spring 框架的整合。

ShardingSphere 實現兩種系統整合機制

  • 名稱空間(namespace)機制,透過擴充套件 Spring Schema 來實現與 Spring 框架的整合
  • 編寫自定義 starter 元件完成與 Spring Boot 的整合

1 基於名稱空間整合 Spring

擴充套件性角度,基於 XML Schema 的擴充套件機制常見而實用。Spring允許我們自定義 XML 結構,並且用自己的 Bean 解析器解析。透過對 Spring Schema 的擴充套件,ShardingSphere 可以完成與 Spring 框架的有效整合。

1.1 基於名稱空間整合 Spring 的通用開發流程

基於名稱空間機制實現與 Spring 的整合,開發通常採用固定流程:

  • 編寫業務物件
  • 編寫XSD檔案
  • 編寫BeanDefinitionParser實現類
  • 編寫NamespaceHandler實現類
  • 編寫 spring.handlers 和 spring.schemas 配置檔案

2 ShardingSphere 整合 Spring

ShardingSphere存在兩個“spring-namespace”結尾的程式碼工程:

  • sharding-jdbc-spring-namespace

  • sharding-jdbc-orchestration-spring-namespace

    關注編排治理相關功能的整合,相對簡單。名稱空間機制的實現過程也基本一致,因此,以 sharding-jdbc-spring-namespace 為例討論。

sharding-jdbc-spring-namespace又包含:

  • 普通分片
  • 讀寫分離
  • 資料脫敏

三塊核心功能的整合內容,實現也都是採用類似方式,因此也不重複說明,以普通分片為例介紹。

專門用於與 Spring 進行整合的

SpringShardingDataSource 類,即業務物件類:

public class SpringShardingDataSource extends ShardingDataSource {
    
    public SpringShardingDataSource(final Map<String, DataSource> dataSourceMap, final ShardingRuleConfiguration shardingRuleConfiguration, final Properties props) throws SQLException {
        super(dataSourceMap, new ShardingRule(shardingRuleConfiguration, dataSourceMap.keySet()), props);
    }
}

只是對 ShardingDataSource 的簡單封裝,無任何實際操作。

配置項標籤的定義類

簡單的工具類,定義標籤的名稱。ShardingSphere的這些類都以“BeanDefinitionParserTag”結尾,如ShardingDataSourceBeanDefinitionParserTag:

public final class ShardingDataSourceBeanDefinitionParserTag {
    public static final String ROOT_TAG = "data-source";
    public static final String SHARDING_RULE_CONFIG_TAG = sharding-rule";
    public static final String PROPS_TAG = "props";
	public static final String DATA_SOURCE_NAMES_TAG = "data-source-names";
	public static final String DEFAULT_DATA_SOURCE_NAME_TAG = "default-data-source-name";
    public static final String TABLE_RULES_TAG = "table-rules"; 
    …
}

定義一批 Tag、Attribute。可以對照如下所示的基於 XML 的配置示例來對這些定義的配置項進行理解:

<sharding:data-source id="shardingDataSource">
        <sharding:sharding-rule data-source-names="ds0,ds1">
            <sharding:table-rules>
                <sharding:table-rule …/>
	            <sharding:table-rule …/>
	            …
            </sharding:table-rules>
            …
        </sharding:sharding-rule>
</sharding:data-source>

在 sharding-jdbc-spring-namespace 程式碼工程的 META-INF/namespace 資料夾找到 sharding.xsd 檔案,其基本結構:

<xsd:schema xmlns="http://shardingsphere.apache.org/schema/shardingsphere/sharding"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:beans="http://www.springframework.org/schema/beans"
            xmlns:encrypt="http://shardingsphere.apache.org/schema/shardingsphere/encrypt"
            targetNamespace="http://shardingsphere.apache.org/schema/shardingsphere/sharding"
            elementFormDefault="qualified" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://shardingsphere.apache.org/schema/shardingsphere/encrypt http://shardingsphere.apache.org/schema/shardingsphere/encrypt/encrypt.xsd">
    <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans.xsd" />
    <xsd:import namespace="http://shardingsphere.apache.org/schema/shardingsphere/encrypt" schemaLocation="http://shardingsphere.apache.org/schema/shardingsphere/encrypt/encrypt.xsd"/>
    <xsd:element name="data-source">
        <xsd:complexType>
            <xsd:all>
                <xsd:element ref="sharding-rule" />
                <xsd:element ref="props" minOccurs="0" />
            </xsd:all>
            <xsd:attribute name="id" type="xsd:string" use="required" />
        </xsd:complexType>
	</xsd:element>
	…
</xsd:schema>

“data-source”這 element包含“sharding-rule”和“props”這兩個子 element。

“data-source”還包含一個“id”屬性。對“sharding-rule”,可有很多內嵌的屬性,sharding.xsd 檔案中對這些屬性都做了定義。

sharding.xsd 中透過使用 xsd:import 標籤還引入兩個 namespace:

  • Spring 中的http://www.springframework.org/schema/beans
  • ShardingSphere 自身的http://shardingsphere.apache.org/schema/shardingsphere/encrypt,該名稱空間的定義位於與 sharding.xsd 同目錄下的 encrypt.xsd檔案中

有了業務物件類、XSD 檔案的定義,來看 NamespaceHandler 實現類 ShardingNamespaceHandler:

public final class ShardingNamespaceHandler extends NamespaceHandlerSupport {
  @Overridepublic void init() {        registerBeanDefinitionParser(ShardingDataSourceBeanDefinitionParserTag.ROOT_TAG, new ShardingDataSourceBeanDefinitionParser());  registerBeanDefinitionParser(ShardingStrategyBeanDefinitionParserTag.STANDARD_STRATEGY_ROOT_TAG, new ShardingStrategyBeanDefinitionParser());  …   } }

直接使用 registerBeanDefinitionParser 方法來完成標籤項與具體的 BeanDefinitionParser 類之間的對應關係。

看ShardingDataSourceBeanDefinitionParser#parseInternal:

@Override
protected AbstractBeanDefinition parseInternal(final Element element, final ParserContext parserContext) {
	    //構建針對 SpringShardingDataSource 的 BeanDefinitionBuilder
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(SpringShardingDataSource.class);
       //解析建構函式中的 DataSource 引數
        factory.addConstructorArgValue(parseDataSources(element));
//解析建構函式中 ShardingRuleConfiguration 引數        factory.addConstructorArgValue(parseShardingRuleConfiguration(element));
       //解析建構函式中 Properties 引數
        factory.addConstructorArgValue(parseProperties(element, parserContext));
        factory.setDestroyMethodName("close");
        return factory.getBeanDefinition();
}

自定義一個 BeanDefinitionBuilder 並將其繫結到前面定義的業務物件類 SpringShardingDataSource。然後,透過三個 addConstructorArgValue 方法的呼叫,分別為 SpringShardingDataSource 建構函式中所需的 dataSourceMap、shardingRuleConfiguration 以及 props 引數進行賦值。

parseDataSources方法

private Map<String, RuntimeBeanReference> parseDataSources(final Element element) {
        Element shardingRuleElement = DomUtils.getChildElementByTagName(element, ShardingDataSourceBeanDefinitionParserTag.SHARDING_RULE_CONFIG_TAG);
        List<String> dataSources = Splitter.on(",").trimResults().splitToList(shardingRuleElement.getAttribute(ShardingDataSourceBeanDefinitionParserTag.DATA_SOURCE_NAMES_TAG));
        Map<String, RuntimeBeanReference> result = new ManagedMap<>(dataSources.size());
        for (String each : dataSources) {
            result.put(each, new RuntimeBeanReference(each));
        }
        return result;
}

獲取配置的“ds0,ds1”字串並拆分,然後基於每個代表具體 DataSource 的名稱構建 RuntimeBeanReference 物件並進行返回,這樣就可以把在 Spring 容器中定義的其他 Bean 載入到 BeanDefinitionBuilder。

最後,在 META-INF 目錄提供spring.schemas 檔案:

http\://shardingsphere.apache.org/schema/shardingsphere/sharding/sharding.xsd=META-INF/namespace/sharding.xsd
http\://shardingsphere.apache.org/schema/shardingsphere/masterslave/master-slave.xsd=META-INF/namespace/master-slave.xsd
http\://shardingsphere.apache.org/schema/shardingsphere/encrypt/encrypt.xsd=META-INF/namespace/encrypt.xsd

spring.handlers 內容:

http\://shardingsphere.apache.org/schema/shardingsphere/sharding=org.apache.shardingsphere.shardingjdbc.spring.namespace.handler.ShardingNamespaceHandler
http\://shardingsphere.apache.org/schema/shardingsphere/masterslave=org.apache.shardingsphere.shardingjdbc.spring.namespace.handler.MasterSlaveNamespaceHandler
http\://shardingsphere.apache.org/schema/shardingsphere/encrypt=org.apache.shardingsphere.shardingjdbc.spring.namespace.handler.EncryptNamespaceHandler

ShardingSphere 中基於名稱空間機制與 Spring 進行系統整合的實現過程介紹完。

3 自定義 starter 整合 Spring Boot

與基於名稱空間實現方式一樣,ShardingSphere 提供:

  • sharding-jdbc-spring-boot-starter
  • sharding-jdbc-orchestration-spring-boot-starter

Spring Boot 工程先關注 META-INF 資料夾下的 spring.factories 檔案。Spring Boot 提供

3.1 SpringFactoriesLoader 類

執行機制類似SPI 機制,只不過以服務介面命名的檔案放在 META-INF/spring.factories,對應 Key 為 EnableAutoConfiguration。

SpringFactoriesLoader 查詢所有 META-INF/spring.factories 下的配置檔案,把 Key=EnableAutoConfiguration 對應配置項透過反射,例項化為配置類並載入到容器。如sharding-jdbc-spring-boot-starter 中的檔案內容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.shardingsphere.shardingjdbc.spring.boot.SpringBootConfiguration

SpringBootConfiguration類就會在 Spring Boot 啟動過程中都能夠透過 SpringFactoriesLoader 被載入到執行時環境。

① SpringBootConfiguration 中的註解

@Configuration
@ComponentScan("org.apache.shardingsphere.spring.boot.converter")
@EnableConfigurationProperties({
        SpringBootShardingRuleConfigurationProperties.class, 
        SpringBootMasterSlaveRuleConfigurationProperties.class, SpringBootEncryptRuleConfigurationProperties.class, SpringBootPropertiesConfigurationProperties.class})
@ConditionalOnProperty(prefix = "spring.shardingsphere", name = "enabled", havingValue = "true", matchIfMissing = true)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@RequiredArgsConstructor
public class SpringBootConfiguration implements EnvironmentAware

@Configuration 註解屬於 Spring 容器管理的內容,表明該類是一個配置類,可啟動元件掃描,將帶 @Bean 註解的實體例項化為 bean。

Spring 容器管理的@ComponentScan 註解:掃描基於 @Component 等註解所標註的類所在包下的所有需要注入的類,並把相關 Bean 定義批次載入到IoC容器。

Spring Boot 應用程式中同樣需要這個功能。這裡需要進行掃描的包路徑位於另一程式碼工程 sharding-spring-boot-util 的 org.apache.shardingsphere.spring.boot.converter 包。

然後,看到 @EnableConfigurationProperties 註解:使新增 @ConfigurationProperties 註解的類生效。Spring Boot 中,若一個類只用 @ConfigurationProperties 註解,然後該類沒有在掃描路徑下或沒使用 @Component 等註解,就會導致無法被掃描為 bean,就須在配置類使用 @EnableConfigurationProperties 註解去指定這個類,才能使 @ConfigurationProperties 生效,並作為一個 bean 新增進 spring 容器中。

這裡的 @EnableConfigurationProperties 註解包含四個具體的 ConfigurationProperties。

如SpringBootShardingRuleConfigurationProperties定義,直接繼承 sharding-core-common 的 YamlShardingRuleConfiguration:

@ConfigurationProperties(prefix = "spring.shardingsphere.sharding")
public class SpringBootShardingRuleConfigurationProperties extends YamlShardingRuleConfiguration {
}

SpringBootConfiguration 上的下一註解是 @ConditionalOnProperty,只有當所提供的屬性屬於 true 時才例項化 Bean。

@AutoConfigureBefore用在類名上,標識在載入當前類之前需要載入註解中所設定的配置類。明確在載入 SpringBootConfiguration 類之前,Spring Boot 會先載入 DataSourceAutoConfiguration。這作用與建立各種 DataSource 相關。

② SpringBootConfiguration 中的功能

ShardingSphere對外入口就是各種 DataSource,因此SpringBootConfiguration中提供一批建立不同 DataSource 的入口方法,如shardingDataSource:

@Bean
@Conditional(ShardingRuleCondition.class)
public DataSource shardingDataSource() throws SQLException {
        return ShardingDataSourceFactory.createDataSource(dataSourceMap, new ShardingRuleConfigurationYamlSwapper().swap(shardingRule), props.getProps());
}

該方法上新增兩個註解:

  • @Bean
  • @Conditional 註解,只有滿足指定條件的情況下才載入這 Bean。@Conditional 註解設定了一個 ShardingRuleCondition:
public final class ShardingRuleCondition extends SpringBootCondition {
    
    @Override
    public ConditionOutcome getMatchOutcome(final ConditionContext conditionContext, final AnnotatedTypeMetadata annotatedTypeMetadata) {
        boolean isMasterSlaveRule = new MasterSlaveRuleCondition().getMatchOutcome(conditionContext, annotatedTypeMetadata).isMatch();
        boolean isEncryptRule = new EncryptRuleCondition().getMatchOutcome(conditionContext, annotatedTypeMetadata).isMatch();
        return isMasterSlaveRule || isEncryptRule ? ConditionOutcome.noMatch("Have found master-slave or encrypt rule in environment") : ConditionOutcome.match();
    }
}

ShardingRuleCondition 是一個標準的 SpringBootCondition,實現 getMatchOutcome 抽象方法。SpringBootCondition 的作用就是代表一種用於註冊類或載入 Bean 的條件。ShardingRuleCondition 類的實現上分別呼叫了 MasterSlaveRuleCondition 和 EncryptRuleCondition 來判斷是否滿足這兩個 SpringBootCondition。對ShardingRuleCondition,只有在兩個條件都不滿足的情況下才被載入。

SpringBootConfiguration 實現 Spring 的 EnvironmentAware 介面。在 Spring Boot 中,當一個類實現了 EnvironmentAware 介面並重寫setEnvironment,在程式碼工程啟動時就可獲得 application.properties 配置檔案中各個配置項的屬性值。SpringBootConfiguration#setEnvironment :

@Override
public final void setEnvironment(final Environment environment) {
        String prefix = "spring.shardingsphere.datasource.";
        for (String each : getDataSourceNames(environment, prefix)) {
            try {
                dataSourceMap.put(each, getDataSource(environment, prefix, each));
            } catch (final ReflectiveOperationException ex) {
                throw new ShardingException("Can't find datasource type!", ex);
            } catch (final NamingException namingEx) {
                throw new ShardingException("Can't find JNDI datasource!", namingEx);
            }
        }
}

獲取“spring.shardingsphere.datasource.name”或“spring.shardingsphere.datasource.names”配置項,然後根據該配置項中所指定的 DataSource 資訊構建新的 DataSource 並載入到 dataSourceMap 這個 LinkedHashMap。

spring.shardingsphere.datasource.names=ds0,ds1
 
spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost/ds0
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=root
 
spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds1.url=jdbc:mysql://localhost/ds1
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=root

至此,整個 SpringBootConfiguration 的實現過程介紹完畢。

3.2 原始碼解析到開發

如需要實現一個自定義的框架或工具類,從面向開發人員的角度講,最好能與 Spring 等主流的開發框架進行整合,以便提供最低的學習和維護成本。與 Spring 框架的整合過程都有固定的開發步驟,就可模仿 ShardingSphere 中的做法自己實現這些步驟。

FAQ

Q:ShardingSphere 整合 Spring Boot 時,SpringBootConfiguration 類上的註解有哪些,分別起啥作用?

在整合 ShardingSphere 和 Spring Boot 時,通常在 SpringBootConfiguration 類上使用以下幾個註解:

  1. @SpringBootApplication

    • 作用:這是一個組合註解,包含了 @Configuration, @EnableAutoConfiguration, 和 @ComponentScan 三個註解的功能。
    • 詳細解釋:
      • @Configuration:表明這個類是一個 Spring 配置類,可以定義 Spring beans。
      • @EnableAutoConfiguration:啟用 Spring Boot 的自動配置機制,嘗試根據類路徑下的 jar 包和已定義的 beans 自動配置 Spring 應用程式。
      • @ComponentScan:告訴 Spring 掃描 @Component (包括 @Service, @Repository, @Controller 等) 註解所在的包,以便發現和註冊這些 beans。
  2. @EnableTransactionManagement

    • 作用:啟用 Spring 的註解驅動的事務管理功能,允許在方法上使用 @Transactional 註解進行事務管理。
  3. @ShardingSphereDataSource

    • 作用:這是一個 ShardingSphere 提供的註解,用於建立和配置 ShardingSphere 的資料來源,支援分庫分表和讀寫分離等功能。
    • 詳細解釋:該註解的具體作用包括配置 ShardingSphere 的資料來源策略、分片策略、讀寫分離策略等。

一個典型的 SpringBootConfiguration 類可能會如下所示:

import org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.io.File;

@SpringBootApplication
@EnableTransactionManagement
public class SpringBootConfiguration {

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

    @Bean
    public DataSource dataSource() throws Exception {
        // 假設有一個 sharding 配置檔案 sharding-databases-tables.yaml
        return YamlShardingSphereDataSourceFactory.createDataSource(new File("path/to/sharding-databases-tables.yaml"));
    }
}

詳解各個註解的作用

  1. @SpringBootApplication

    • @Configuration:將類標識為配置類,可以用來定義 Spring beans。
    • @EnableAutoConfiguration:啟用 Spring Boot 自動配置,這樣 Spring Boot 會根據類路徑下的 jar 包和已定義的 beans 自動配置 Spring 應用。
    • @ComponentScan:告訴 Spring 在指定包下掃描 @Component 註解的類並註冊這些類為 Spring beans。
  2. @EnableTransactionManagement

    • 允許使用 @Transactional 註解來管理事務,使得可以透過註解的方式宣告事務的範圍。
  3. @ShardingSphereDataSource

    • 配置 ShardingSphere 資料來源,使得可以使用 ShardingSphere 提供的分庫分表和讀寫分離功能。這個註解需要結合具體的 ShardingSphere 配置來使用,如上面的 YamlShardingSphereDataSourceFactory.createDataSource 方法。

透過這些註解,可以快速且簡潔地配置和使用 ShardingSphere 與 Spring Boot 整合,利用 Spring Boot 的自動配置和管理功能,加上 ShardingSphere 的分庫分表和讀寫分離功能,構建一個高效且可擴充套件的分散式資料庫系統。

關注我,緊跟本系列專欄文章,咱們下篇再續!

作者簡介:魔都技術專家,多家大廠後端一線研發經驗,在分散式系統、和大資料系統等方面有多年的研究和實踐經驗,擁有從零到一的大資料平臺和基礎架構研發經驗,對分散式儲存、資料平臺架構、資料倉儲等領域都有豐富實踐經驗。

各大技術社群頭部專家博主。具有豐富的引領團隊經驗,深厚業務架構和解決方案的積累。

負責:

  • 中央/分銷預訂系統效能最佳化
  • 活動&優惠券等營銷中臺建設
  • 交易平臺及資料中臺等架構和開發設計
  • 車聯網核心平臺-物聯網連線平臺、大資料平臺架構設計及最佳化

目前主攻降低軟體複雜性設計、構建高可用系統方向。

參考:

  • 程式設計嚴選網

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章