本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!
系统集成,即ShardingSphere 和 Spring 框架的集成。
ShardingSphere实现两种系统集成机制:
扩展性角度,基于 XML Schema 的扩展机制常见而实用。Spring允许我们自定义 XML 结构,并且用自己的 Bean 解析器解析。通过对 Spring Schema 的扩展,ShardingSphere 可以完成与 Spring 框架的有效集成。
基于命名空间机制实现与 Spring 的整合,开发通常采用固定流程:
ShardingSphere存在两个“spring-namespace”结尾的代码工程:
关注编排治理相关功能的集成,相对简单。命名空间机制的实现过程也基本一致,因此,以 sharding-jdbc-spring-namespace 为例讨论。
sharding-jdbc-spring-namespace又包含:
三块核心功能的集成内容,实现也都是采用类似方式,因此也不重复说明,以普通分片为例介绍。
专门用于与 Spring 进行集成的业务对象类:
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:
有了业务对象类、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 参数进行赋值。
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 进行系统集成的实现过程介绍完。
ShardingSphere提供:
Spring Boot先关注 META-INF 的 spring.factories 文件。Spring Boot 提供
类似SPI,只不过以服务接口命名的文件放在 META-INF/spring.factories,Key也为EnableAutoConfiguration。
SpringFactoriesLoader查找所有 META-INF/spring.factories 下的配置文件,把EnableAutoConfiguration项对应的配置项,通过反射实例化为配置类,并加载到容器。
如sharding-jdbc-spring-boot-starter中的文件内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.shardingsphere.shardingjdbc.spring.boot.SpringBootConfiguration
SpringBootConfiguration类就会在 Spring Boot 启动过程中,都通过 SpringFactoriesLoader 被加载到运行时环境。
需扫描的包路径位于另一工程sharding-spring-boot-util的org.apache.shardingsphere.spring.boot.converter 包。
@EnableConfigurationProperties:使添加 @ConfigurationProperties 的类生效。
@EnableConfigurationProperties包含四个具体ConfigurationProperties,如SpringBootShardingRuleConfigurationProperties定义,直接继承 sharding-core-common 的 YamlShardingRuleConfiguration:
@ConfigurationProperties(prefix = "spring.shardingsphere.sharding")
public class SpringBootShardingRuleConfigurationProperties extends YamlShardingRuleConfiguration {
}
@ConditionalOnProperty,只有当所提供的属性属于 true 时才实例化 Bean。
@AutoConfigureBefore用在类名上,标识在加载当前类之前需要加载注解中所设置的配置类。明确在加载 SpringBootConfiguration 类之前,SpringBoot会先加载DataSourceAutoConfiguration,创建各种 DataSource。
ShardingSphere对外入口就是各种DataSource,因此SpringBootConfiguration中提供一批创建不同 DataSource的入口方法,如shardingDataSource:
@Bean
@Conditional(ShardingRuleCondition.class)
public DataSource shardingDataSource() throws SQLException {
return ShardingDataSourceFactory.createDataSource(dataSourceMap, new ShardingRuleConfigurationYamlSwapper().swap(shardingRule), props.getProps());
}
该方法的注解:
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();
}
}
标准SpringBootCondition,实现 getMatchOutcome 抽象方法。代表一种用于注册类或加载 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
如需要实现一个自定义的框架或工具类,最好能与 Spring 集成,以便提供最低学习和维护成本。集成过程有固定开发步骤,就可模仿ShardingSphere中做法。
Q:ShardingSphere集成SpringBoot时,SpringBootConfiguration上的注解有哪些作用?
A:通常用如下注解:
@Configuration
:这个类是一个 Spring 配置类,可以定义 Spring beans。@EnableAutoConfiguration
:启用 Spring Boot 的自动配置机制,尝试根据类路径下的 jar 包和已定义的 beans 自动配置 Spring 应用程序。@ComponentScan
:告诉 Spring 扫描 @Component
(包括 @Service
, @Repository
, @Controller
等) 注解所在的包,以便发现和注册这些 beans。典型的 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"));
}
}
通过这些注解,可以快速且简洁地配置和使用 ShardingSphere 与 Spring Boot 集成,利用 Spring Boot 的自动配置和管理功能,加上 ShardingSphere 的分库分表和读写分离功能,构建一个高效且可扩展的分布式数据库系统。