【源码】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 {
            // 注册到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对象;

详见: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)方法;

以上为本篇分享的全部内容。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值