Sharding-JDBC系列
2、Sharding-JDBC分库分表之SpringBoot分片策略
3、Sharding-JDBC分库分表之SpringBoot主从配置
4、SpringBoot集成Sharding-JDBC-5.3.0分库分表
5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表
8、【源码】Sharding-JDBC源码分析之Yaml分片配置文件解析原理
前言
使用Sharding JDBC开发时,最核心的操作为分片规则配置,本篇从源码的角度分享Sharding JDBC分片规则配置的解析过程。
ShardingSphereDriver
在【源码】Sharding-JDBC源码分析之JDBC-CSDN博客这篇博文中,介绍了Sharding-JDBC提供了原生JDBC驱动ShardingSphereDriver。
package org.apache.shardingsphere.driver;
import org.apache.shardingsphere.driver.jdbc.core.driver.DriverDataSourceCache;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Logger;
public final class ShardingSphereDriver implements Driver {
private static final int MAJOR_DRIVER_VERSION = 5;
private static final int MINOR_DRIVER_VERSION = 1;
// 数据库连接缓存
private final DriverDataSourceCache dataSourceCache = new DriverDataSourceCache();
static {
try {
// 注册到DriverManager
DriverManager.registerDriver(new ShardingSphereDriver());
} catch (final SQLException ex) {
throw new DriverRegisterException(ex);
}
}
/**
* 在HikariDataSource等连接池中,会通过DriverManager.getDriver(url)获得该ShardingSphereDriver,
* 调用ShardingSphereDriver.connect()获得数据库连接。
*/
@Override
public Connection connect(final String url, final Properties info) throws SQLException {
return acceptsURL(url) ? dataSourceCache.get(url).getConnection() : null;
}
/**
* ShardingSphereDriver的url中必须是以jdbc:shardingsphere:开头
*/
@Override
public boolean acceptsURL(final String url) {
return null != url && url.startsWith("jdbc:shardingsphere:");
}
@Override
public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
return new DriverPropertyInfo[0];
}
@Override
public int getMajorVersion() {
return MAJOR_DRIVER_VERSION;
}
@Override
public int getMinorVersion() {
return MINOR_DRIVER_VERSION;
}
/**
* 不符合JDBC标准
* @return
*/
@Override
public boolean jdbcCompliant() {
return false;
}
@Override
public Logger getParentLogger() {
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
}
}
ShardingSphereDriver类实现了原生JDBC的Driver接口,该接口提供了connect()接口,返回一个Connection数据库连接。
在HikariDataSource等连接池中,会通过DriverManager.getDriver(url)获得该ShardingSphereDriver,调用ShardingSphereDriver.connect()获得数据库连接。
在connect()方法中,通过url从DriverDataSourceCache中获取一个DataSource,然后执行DataSource.getConnection()获取一个Connection连接。
DriverDataSourceCache
DriverDataSourceCache的源码如下:
package org.apache.shardingsphere.driver.jdbc.core.driver;
/**
* Driver中的数据源缓存。通过解析yaml中配置的数据源,创建ShardingSphereDataSource,并缓存
*/
public final class DriverDataSourceCache {
private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();
/**
* 获取一个DataSource。从Map中获取,如果不存在,则调用createDataSource创建一个DataSource
*/
public DataSource get(final String url) {
if (dataSourceMap.containsKey(url)) {
return dataSourceMap.get(url);
}
return dataSourceMap.computeIfAbsent(url, DriverDataSourceCache::createDataSource);
}
/**
* 解析yaml分片配置文件,并创建一个ShardingSphereDataSource
*/
@SuppressWarnings("unchecked")
private static <T extends Throwable> DataSource createDataSource(final String url) throws T {
try {
// 1、通过ShardingSphereDriverURL,读取sharding分片配置yaml文件信息
// 2、通过YamlShardingSphereDataSourceFactory,解析分片配置yaml文件,创建一个ShardingSphereDataSource
return YamlShardingSphereDataSourceFactory.createDataSource(new ShardingSphereDriverURL(url).toConfigurationBytes());
} catch (final IOException ex) {
throw (T) new SQLException(ex);
} catch (final SQLException ex) {
throw (T) ex;
}
}
}
1)get()方法,以url为key,从Map中获取一个DataSource,如果不存在,则调用createDataSource()创建一个DataSource;
2)createDataSource()方法,解析yaml中配置的分片规则,创建一个ShardingSphereDataSource对象,并返回;
a)通过ShardingSphereDriverURL,读取sharding分片配置yaml文件信息。
构造方法中,截取url中的jdbc:shardingsphere:字符串,获取分片配置的文件名。在toConfigurationBytes()方法中,读取配置文件,过滤掉#开头的行,转成byte[]数组返回;
b)通过YamlShardingSphereDataSourceFactory.createDateSource(),解析分片配置的yaml文件的byte[]数组,创建一个ShardingSphereDataSource;
在YamlShardingSphereDataSourceFactory.createDateSource()方法中,通过YamlEngine.unmarshal()方法,获得一个YamlRootConfiguration对象。该对象为配置的分片规则信息对象。
YamlEngine.unmarshal()方法的代码如下:
/**
* 将yaml格式的配置信息转化成对应classType的对象
*/
public static <T extends YamlConfiguration> T unmarshal(final byte[] yamlBytes, final Class<T> classType) throws IOException {
try (InputStream inputStream = new ByteArrayInputStream(yamlBytes)) {
return new Yaml(new ShardingSphereYamlConstructor(classType)).loadAs(inputStream, classType);
}
}
其中传入的classType为YamlRootConfiguration.class。
该方法先创建一个ShardingSphereYamlConstructor对象,然后创建Yaml对象,通过Yaml对象的loadAs()将文件信息封装成一个YamlRootConfiguration对象。
Yaml对象为snakeyaml包的类,该工具类用于解析yaml文件。
ShardingSphereYamlConstructor
ShardingSphereYamlConstructor的源码如下:
/**
* Sharding Sphere Yaml构造器。该类继承snake yaml包中的Constructor类,
*/
public class ShardingSphereYamlConstructor extends Constructor {
private final Map<Class<?>, Construct> typeConstructs = new HashMap<>();
private final Class<?> rootClass;
public ShardingSphereYamlConstructor(final Class<?> rootClass) {
super(rootClass, new LoaderOptions() {
{
setCodePointLimit(Integer.MAX_VALUE);
}
});
// 通过Java SPI获取ShardingSphereYamlConstruct,并添加到typeConstructs中
ShardingSphereServiceLoader.getServiceInstances(ShardingSphereYamlConstruct.class).forEach(each -> typeConstructs.put(each.getType(), each));
Map<String, Class<?>> yamlShortcuts = new HashMap<>();
// 通过Java SPI获取ShardingSphereYamlShortcuts,执行ShardingSphereYamlShortcuts.getYamlShortcuts()方法。
// 将结果添加到yamlShortcuts中
ShardingSphereServiceLoader.getServiceInstances(ShardingSphereYamlShortcuts.class).stream().map(ShardingSphereYamlShortcuts::getYamlShortcuts).forEach(yamlShortcuts::putAll);
// 将yamlShortcuts中的Construct封装成TypeDescription,添加到Constructor中
yamlShortcuts.forEach((key, value) -> addTypeDescription(new TypeDescription(value, key)));
this.rootClass = rootClass;
}
@Override
protected final Construct getConstructor(final Node node) {
return typeConstructs.getOrDefault(node.getType(), super.getConstructor(node));
}
@Override
protected Class<?> getClassForName(final String className) throws ClassNotFoundException {
Preconditions.checkArgument(className.equals(rootClass.getName()), "Class `%s` is not accepted", className);
return super.getClassForName(className);
}
}
4.1 构造方法
在ShardingSphereYamlConstructor的构造方法中,执行如下:
1)通过Java SPI获取ShardingSphereYamlConstruct,并添加到typeConstructs中;
Java SPI详见:【源码】Sharding-JDBC源码分析之SPI机制-CSDN博客
ShardingSphereYamlConstruct继承了SnakeYaml的Construct,扩展了getType()接口,用于通过Node.type和该getType()匹配,返回特定的Java对象。
实现类如下:
a)NoneShardingStrategyConfigurationYamlConstruct:无分片策略配置Yaml构造。对于无分片策略,指定返回的配置对象为YamlNoneShardingStrategyConfiguration;
b)ShardingSphereYamlConstructFixture:固定配置的Yaml构造。对于自定义的固定配置,指定返回的配置对象为CustomizedClassFixture;
2)通过Java SPI获取ShardingSphereYamlShortcuts,执行ShardingSphereYamlShortcuts.getYamlShortcuts()方法,将结果添加到yamlShortcuts中;
ShardingSphereYamlShortcuts的实现类如下:
a)ShardingSphereYamlShortcutsFixture:固定快捷方式
b)YamlRuleConfigurationShortcuts:规则快捷方式
2.1)ShardingSphereYamlShortcutsFixture的源码如下:
/**
* Yaml快捷的固定配置
*/
public final class ShardingSphereYamlShortcutsFixture implements ShardingSphereYamlShortcuts {
@Override
public Map<String, Class<?>> getYamlShortcuts() {
return Collections.singletonMap("!FIXTURE", YamlShortcutsConfigurationFixture.class);
}
}
在getYamlShortcuts()方法中,返回key为!FIXTURE,value为YamlShortcutsConfigurationFixture的Map对象。即在配置文件中,标记!FIXTURE之后的配置为YamlShortcutsConfigurationFixture的对应配置。
2.2)YamlRuleConfigurationShortcuts的源码如下:
package org.apache.shardingsphere.infra.yaml.config.shortcut;
/**
* Yaml规则配置快捷方式
*/
public final class YamlRuleConfigurationShortcuts implements ShardingSphereYamlShortcuts {
@SuppressWarnings("rawtypes")
@Override
@SneakyThrows(ReflectiveOperationException.class)
public Map<String, Class<?>> getYamlShortcuts() {
// 获取所有的规则配置转换器
Collection<YamlRuleConfigurationSwapper> swappers = ShardingSphereServiceLoader.getServiceInstances(YamlRuleConfigurationSwapper.class);
Map<String, Class<?>> result = new HashMap<>(swappers.size(), 1);
for (YamlRuleConfigurationSwapper each : swappers) {
// 获取YamlRuleConfigurationSwapper第一个实现接口的第一个泛型的类型
Class<?> yamlRuleConfigurationClass = Class.forName(((ParameterizedType) each.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0].getTypeName());
// 添加规则标识名字及对应规则配置类。如!SHARDING:YamlShardingRuleConfiguration
result.put(String.format("!%s", each.getRuleTagName()), yamlRuleConfigurationClass);
}
return result;
}
}
2.2.1)通过Java SPI获取YamlRuleConfigurationSwapper,该类为规则配置转换器,可以进行XxxRuleConfiguration和YamlXxxConfiguration的转换。如ShardingTableRuleConfiguration和YamlTableRuleConfiguration的转换。该类有18个实现类。如下:
2.2.2)遍历YamlRuleConfigurationSwapper集合,获取YamlRuleConfigurationSwapper的第一个实现接口的第一个泛型的类型,执行YamlRuleConfigurationSwapper.getRuleTagName()获取规则标识名称,添加到Map集合中,最终返回Map集合。
以下以YamlShardingRuleConfigurationSwapper实现类为例。
package org.apache.shardingsphere.sharding.yaml.swapper;
/**
* yaml中sharding规则配置转换器
*/
public final class YamlShardingRuleConfigurationSwapper implements YamlRuleConfigurationSwapper<YamlShardingRuleConfiguration, ShardingRuleConfiguration> {
// 省略其他
@Override
public Class<ShardingRuleConfiguration> getTypeClass() {
return ShardingRuleConfiguration.class;
}
/**
* 返回SHARDING
*/
@Override
public String getRuleTagName() {
return "SHARDING";
}
@Override
public int getOrder() {
return ShardingOrder.ORDER;
}
}
a)获取YamlRuleConfigurationSwapper第一个实现接口的第一个泛型的类型为YamlShardingRuleConfiguration;
b)添加到result中的信息为key=!SHARDING;value=YamlShardingRuleConfiguration
3)遍历yamlShortcuts,生成TypeDescription,执行addTypeDescription()添加到Constructor。即yamlShortcuts中存放了标签及标签对应的类。如!SHARDING:YamlShardingRuleConfiguration,即在yaml中配置的!SHARDING后面的配置信息要解析为YamlShardingRuleConfiguration对象;
4.2 重写Construct getConstructor(final Node node)方法
在该方法比较简单,如果Node的类型在typeConstructs集合中,返回自定义的Construct,否则执行父类的同名方法。
如配置的策略为none,则对应的Node类型为YamlNoneShardingStrategyConfiguration,此时返回的Construct为NoneShardingStrategyConfigurationYamlConstruct,从而指定该Node的配置信息为YamlNoneShardingStrategyConfiguration
小结
限于篇幅,本篇先分享到这里。以下做一个小结:
1)ShardingSphere通过SnakeYaml解析yaml分片策略文件;
2)自定义SnakeYaml的Constructor;
2.1)在构造方法中,添加了自定义的Construct,存放在typeConstructs集合中。如用于处理none的分片策略的Construct;
2.2)在构造方法中,添加了yaml的标签及标签解析成的Java对象。如!SHARDING应该解析为YamlShardingRuleConfiguration对象;
2.3)重写Construct getConstructor(final Node node)方法,如果node的type在typeConstructs集合中,直接返回对应的Construct,否则执行父类的getConstructor(final Node node)方法;
以上为本篇分享的全部内容。
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。