【源码】Sharding-JDBC源码分析之Yaml分片配置文件解析原理
Sharding-JDBC系列
1、Sharding-JDBC分库分表的基本使用
2、Sharding-JDBC分库分表之SpringBoot分片策略
3、Sharding-JDBC分库分表之SpringBoot主从配置
4、SpringBoot集成Sharding-JDBC-5.3.0分库分表
5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表
6、【源码】Sharding-JDBC源码分析之JDBC
7、【源码】Sharding-JDBC源码分析之SPI机制
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 {// 注册到DriverManagerDriverManager.registerDriver(new ShardingSphereDriver());} catch (final SQLException ex) {throw new DriverRegisterException(ex);}}/*** 在HikariDataSource等连接池中,会通过DriverManager.getDriver(url)获得该ShardingSphereDriver,* 调用ShardingSphereDriver.connect()获得数据库连接。*/@Overridepublic Connection connect(final String url, final Properties info) throws SQLException {return acceptsURL(url) ? dataSourceCache.get(url).getConnection() : null;}/*** ShardingSphereDriver的url中必须是以jdbc:shardingsphere:开头*/@Overridepublic boolean acceptsURL(final String url) {return null != url && url.startsWith("jdbc:shardingsphere:");}@Overridepublic DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {return new DriverPropertyInfo[0];}@Overridepublic int getMajorVersion() {return MAJOR_DRIVER_VERSION;}@Overridepublic int getMinorVersion() {return MINOR_DRIVER_VERSION;}/*** 不符合JDBC标准* @return*/@Overridepublic boolean jdbcCompliant() {return false;}@Overridepublic 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文件,创建一个ShardingSphereDataSourcereturn 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;}@Overrideprotected final Construct getConstructor(final Node node) {return typeConstructs.getOrDefault(node.getType(), super.getConstructor(node));}@Overrideprotected 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 {@Overridepublic 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:YamlShardingRuleConfigurationresult.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> {// 省略其他@Overridepublic Class<ShardingRuleConfiguration> getTypeClass() {return ShardingRuleConfiguration.class;}/*** 返回SHARDING*/@Overridepublic String getRuleTagName() {return "SHARDING";}@Overridepublic 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对象;
详见:Yaml及解析框架SnakeYaml简介及TypeDescription的使用和原理-CSDN博客
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)方法;
以上为本篇分享的全部内容。
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。