【源码】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分片配置文件解析原理

9、【源码】Sharding-JDBC源码分析之Yaml分片配置原理(一)

前言

在前一篇博文【源码】Sharding-JDBC源码分析之Yaml分片配置文件解析原理-CSDN博客从源码角度分享了分片配置文件解析的原理,分片配置Yaml文件使用SnakeYaml进行解析,返回一个YamlRootConfiguration对象。本篇从源码的角度继续分享分片配置信息的原理。

YamlRootConfiguration

YamlRootConfiguration是整个配置文件的根节点配置信息,源码如下:

package org.apache.shardingsphere.infra.yaml.config.pojo;

/**
 * Yaml分片配置的根配置
 */
@Getter
@Setter
public final class YamlRootConfiguration implements YamlConfiguration {
    
    // 数据库名
    private String databaseName;
    
    /**
     * Schema name.
     * 
     * @deprecated Should use databaseName, schemaName will remove in next version.
     */
	// 在新版本将会移除,同databaseName
    @Deprecated
    private String schemaName;
    
    // 数据源信息,使用Map存储。key为逻辑数据源名称,value为对应数据源的配置信息(如url、username等)
    private Map<String, Map<String, Object>> dataSources = new HashMap<>();
    
    // 规则配置,Collection集合类型
    private Collection<YamlRuleConfiguration> rules = new LinkedList<>();
    
    // 模式配置
    private YamlModeConfiguration mode;
    
    // 属性配置
    private Properties props = new Properties();

    public String getDatabaseName() {
        return Strings.isNullOrEmpty(databaseName) ? schemaName : databaseName;
    }
}

在YamlRootConfiguration中定义的属性为配置的key,属性类型为对应值的信息。在分片配置文件中可以配置的根节点信息为:

databaseName: #数据库名,String类型
dataSources: #数据源,Map<String, Map<String, Object>>类型
rules: # 规则,Collection<YamlRuleConfiguration>类型
props: # 属性,Properties类型
mode: # 模式,YamlModeConfiguration类型

2.1 databaseName

数据库名称,默认为logic_db

2.2 dataSources

数据源集合,key为逻辑数据源名称,value为对应数据源的配置信息。

YamlRootConfiguration根配置对象是在YamlShardingSphereDataSourceFactory的createDateSource()方法中,通过YamlEngine.unmarshal()方法解析获得的。代码如下:

package org.apache.shardingsphere.driver.api.yaml;

public final class YamlShardingSphereDataSourceFactory {

    
    // yaml中dataSources配置信息转换器
    private static final YamlDataSourceConfigurationSwapper DATA_SOURCE_SWAPPER = new YamlDataSourceConfigurationSwapper();

    /**
     * 创建ShardingSphereDataSource对象
     * @param yamlBytes yaml配置字符串
     */
    public static DataSource createDataSource(final byte[] yamlBytes) throws SQLException, IOException {
        // 解析yaml配置文件,获取YamlRootConfiguration
        YamlRootConfiguration rootConfig = YamlEngine.unmarshal(yamlBytes, YamlRootConfiguration.class);
        // 调用DATA_SOURCE_SWAPPER.swapToDataSources(rootConfig.getDataSources(),
        // 将Yaml中配置的dataSources信息转换为对应的DataSources对象
        return createDataSource(DATA_SOURCE_SWAPPER.swapToDataSources(rootConfig.getDataSources()), rootConfig);
    }
    
    // 省略其他
}

获取之后,执行YamlDataSourceConfigurationSwapper的swapToDataSources()方法,根据配置的dataSources信息创建DataSource对象。

2.2.1 YamlDataSourceConfigurationSwapper

YamlDataSourceConfigurationSwapper的相关源码如下:

package org.apache.shardingsphere.infra.yaml.config.swapper.resource;

/**
 * Yaml的dataSource配置转换
 */
public final class YamlDataSourceConfigurationSwapper {
    
    private static final String DATA_SOURCE_CLASS_NAME_KEY = "dataSourceClassName";
    
    private static final String CUSTOM_POOL_PROPS_KEY = "customPoolProps";

    /**
     * 将Yaml中配置的dataSources信息转换为对应的DataSources对象
     */
    public Map<String, DataSource> swapToDataSources(final Map<String, Map<String, Object>> yamlDataSources) {
        return swapToDataSources(yamlDataSources, true);
    }

    /**
     * 数据源配置信息转换(创建)对应的DataSource对象
     * @param yamlDataSources
     * @param cacheEnabled
     * @return
     */
    public Map<String, DataSource> swapToDataSources(final Map<String, Map<String, Object>> yamlDataSources, final boolean cacheEnabled) {
        return DataSourcePoolCreator.create(yamlDataSources.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> swapToDataSourceProperties(entry.getValue()))), cacheEnabled);
    }

    /**
     * yaml配置信息转换为DataSourceProperties对象
     */
    public DataSourceProperties swapToDataSourceProperties(final Map<String, Object> yamlConfig) {
        // 必现包含dataSourceClassName配置
        Preconditions.checkState(yamlConfig.containsKey(DATA_SOURCE_CLASS_NAME_KEY), "%s can not be null.", DATA_SOURCE_CLASS_NAME_KEY);
        // 拷贝yamlConfig,创建DataSourceProperties对象
        return new DataSourceProperties(yamlConfig.get(DATA_SOURCE_CLASS_NAME_KEY).toString(), getProperties(yamlConfig));
    }

    /**
     * 深度拷贝yamlConfig,移除dataSourceClassName配置。如果存在customPoolProps,
     * 则将其配置信息添加到拷贝后的yamlConfig,移除customPoolProps
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    private Map<String, Object> getProperties(final Map<String, Object> yamlConfig) {
        Map<String, Object> result = new HashMap<>(yamlConfig);
        // 移除dataSourceClassName配置信息
        result.remove(DATA_SOURCE_CLASS_NAME_KEY);
        // 如果包含customPoolProps自定义的池属性
        if (null != yamlConfig.get(CUSTOM_POOL_PROPS_KEY)) {
            // 将customPoolProps属性信息添加到根Map中
            result.putAll((Map) yamlConfig.get(CUSTOM_POOL_PROPS_KEY));
        }
        // 移除customPoolProps配置
        result.remove(CUSTOM_POOL_PROPS_KEY);
        return result;
    }
	
	// 省略其他
    
}

在swapToDataSources()方法中,执行如下:

2.2.1.1)遍历配置的dataSources,调用swapToDataSourceProperties(final Map<String, Object> yamlConfig),将配置的信息转换为DataSourceProperties对象;

a)首先判断配置信息中是否有dataSourceClassName,如果没有,抛参数异常;

b)调用getProperties()方法,深度拷贝配置信息,并对customPoolProps的配置信息放到根Map中。即dataSources.xxx_ds.customPoolProps.aaa = bbb,等价于dataSources.xxx_ds.aaa = bbb;

c)创建一个DataSourceProperties对象;

2.2.1.2)调用DataSourcePoolCreator.create(final Map<String, DataSourceProperties> dataSourcePropsMap, final boolean cacheEnabled),创建DataSource对象;

2.2.2 DataSourceProperties

DataSourceProperties为数据源的属性信息。源码如下:

package org.apache.shardingsphere.infra.datasource.props;

/**
 * DataSource的属性信息
 */
@Getter
public final class DataSourceProperties {

    // dataSource的类名,如com.zaxxer.hikari.HikariDataSource、DruidDataSource等全类名
    private final String dataSourceClassName;

    // connection连接属性同义名。记录连接的属性及配置信息(如url、username等)
    private final ConnectionPropertySynonyms connectionPropertySynonyms;

    // 连接池属性的同义词。记录连接池的属性及配置信息(如maxPoolSize等)
    private final PoolPropertySynonyms poolPropertySynonyms;

    // 其他自定义的DataSource自定义属性
    private final CustomDataSourceProperties customDataSourceProperties;

    /**
     * @param dataSourceClassName 如com.zaxxer.hikari.HikariDataSource
     * @param props dataSource配置的属性信息
     */
    public DataSourceProperties(final String dataSourceClassName, final Map<String, Object> props) {
        this.dataSourceClassName = dataSourceClassName;
        // 如HikariDataSourcePoolMetaData
        Optional<DataSourcePoolMetaData> poolMetaData = TypedSPIRegistry.findRegisteredService(DataSourcePoolMetaData.class, dataSourceClassName);
        // 获取同义词,即同义名。进行配置兼容性处理
        Map<String, String> propertySynonyms = poolMetaData.isPresent() ? poolMetaData.get().getPropertySynonyms() : Collections.emptyMap();
        connectionPropertySynonyms = new ConnectionPropertySynonyms(props, propertySynonyms);
        poolPropertySynonyms = new PoolPropertySynonyms(props, propertySynonyms);
        customDataSourceProperties = new CustomDataSourceProperties(
                props, getStandardPropertyKeys(), poolMetaData.isPresent() ? poolMetaData.get().getTransientFieldNames() : Collections.emptyList(), propertySynonyms);
    }

    /**
     * 获取标准属性key
     * @return
     */
    private Collection<String> getStandardPropertyKeys() {
        Collection<String> result = new LinkedList<>(connectionPropertySynonyms.getStandardPropertyKeys());
        result.addAll(poolPropertySynonyms.getStandardPropertyKeys());
        return result;
    }

    /**
     * 获取所有标准配置信息
     * @return
     */
    public Map<String, Object> getAllStandardProperties() {
        Map<String, Object> result = new LinkedHashMap<>(
                connectionPropertySynonyms.getStandardProperties().size() + poolPropertySynonyms.getStandardProperties().size() + customDataSourceProperties.getProperties().size(), 1);
        result.putAll(connectionPropertySynonyms.getStandardProperties());
        result.putAll(poolPropertySynonyms.getStandardProperties());
        result.putAll(customDataSourceProperties.getProperties());
        return result;
    }

    /**
     * 获取所有本地属性信息
     * @return
     */
    public Map<String, Object> getAllLocalProperties() {
        Map<String, Object> result = new LinkedHashMap<>(
                connectionPropertySynonyms.getLocalProperties().size() + poolPropertySynonyms.getLocalProperties().size() + customDataSourceProperties.getProperties().size(), 1);
        result.putAll(connectionPropertySynonyms.getLocalProperties());
        result.putAll(poolPropertySynonyms.getLocalProperties());
        result.putAll(customDataSourceProperties.getProperties());
        return result;
    }
    
    // 省略其他
	
}

在DataSourceProperties构造方法中,根据配置的dataSourceClassName,使用Java SPI获取对应dataSource的DataSourcePoolMetaData元数据,根据配置的dataSourceClassName,对配置信息进行兼容性处理。

不同的DataSource处理方式不同,以com.zaxxer.hikari.HikariDataSource为例,对应的DataSourcePoolMetaData为HikariDataSourcePoolMetaData。

2.2.3 HikariDataSourcePoolMetaData

HikariDataSourcePoolMetaData的源码如下:

package org.apache.shardingsphere.infra.datasource.pool.metadata.type.hikari;

/**
 * HikariDataSource池的元数据,创建默认的池信息
 */
public final class HikariDataSourcePoolMetaData implements DataSourcePoolMetaData {

    //默认属性
    private static final Map<String, Object> DEFAULT_PROPS = new HashMap<>(6, 1);

    // 非法的属性
    private static final Map<String, Object> INVALID_PROPS = new HashMap<>(2, 1);

    // 同名属性
    private static final Map<String, String> PROP_SYNONYMS = new HashMap<>(6, 1);

    // 忽略的属性名称
    private static final Collection<String> TRANSIENT_FIELD_NAMES = new LinkedList<>();
    
    static {
        buildDefaultProperties();
        buildInvalidProperties();
        buildPropertySynonyms();
        buildTransientFieldNames();
    }
    
    private static void buildDefaultProperties() {
        DEFAULT_PROPS.put("connectionTimeout", 30 * 1000L);
        DEFAULT_PROPS.put("idleTimeout", 60 * 1000L);
        DEFAULT_PROPS.put("maxLifetime", 30 * 70 * 1000L);
        DEFAULT_PROPS.put("maximumPoolSize", 50);
        DEFAULT_PROPS.put("minimumIdle", 1);
        DEFAULT_PROPS.put("readOnly", false);
        DEFAULT_PROPS.put("keepaliveTime", 0);
    }
    
    private static void buildInvalidProperties() {
        INVALID_PROPS.put("minimumIdle", -1);
        INVALID_PROPS.put("maximumPoolSize", -1);
    }

    /**
     * 属性的同义词,如url和jdbcUrl属于同一个配置
     */
    private static void buildPropertySynonyms() {
        PROP_SYNONYMS.put("url", "jdbcUrl");
        PROP_SYNONYMS.put("connectionTimeoutMilliseconds", "connectionTimeout");
        PROP_SYNONYMS.put("idleTimeoutMilliseconds", "idleTimeout");
        PROP_SYNONYMS.put("maxLifetimeMilliseconds", "maxLifetime");
        PROP_SYNONYMS.put("maxPoolSize", "maximumPoolSize");
        PROP_SYNONYMS.put("minPoolSize", "minimumIdle");
    }
    
    private static void buildTransientFieldNames() {
        TRANSIENT_FIELD_NAMES.add("running");
        TRANSIENT_FIELD_NAMES.add("poolName");
        TRANSIENT_FIELD_NAMES.add("closed");
    }
    
    @Override
    public Map<String, Object> getDefaultProperties() {
        return DEFAULT_PROPS;
    }
    
    @Override
    public Map<String, Object> getInvalidProperties() {
        return INVALID_PROPS;
    }
    
    @Override
    public Map<String, String> getPropertySynonyms() {
        return PROP_SYNONYMS;
    }
    
    @Override
    public Collection<String> getTransientFieldNames() {
        return TRANSIENT_FIELD_NAMES;
    }
    
    /**
     * Hikari数据源池字段的元数据。包括username、password、jdbcUrl、jdbcUrlProperties四个字段名称
     */
    @Override
    public HikariDataSourcePoolFieldMetaData getFieldMetaData() {
        return new HikariDataSourcePoolFieldMetaData();
    }

    /**
     * 返回属性校验器
     */
    @Override
    public DataSourcePoolPropertiesValidator getDataSourcePoolPropertiesValidator() {
        return new HikariDataSourcePoolPropertiesValidator();
    }
    
    @Override
    public String getType() {
        return "com.zaxxer.hikari.HikariDataSource";
    }
}

在HikariDataSourcePoolMetaData元数据中,除了定义同义名以外,还做了默认项配置。该默认配置项在上面的2.2.1.2)中创建DataSource对象时,会通过反射的方式,赋值到对应的DataSource对象上。

2.2.4 DataSourcePoolCreator

在上面的2.2.1.2)调用DataSourcePoolCreator.create(final Map<String, DataSourceProperties> dataSourcePropsMap, final boolean cacheEnabled),创建DataSource对象。

DataSourcePoolCreator的源码如下:

package org.apache.shardingsphere.infra.datasource.pool.creator;

/**
 * DataSource池的创建器。通过配置的信息,创建对应的DataSource(如HikariDataSource),
 * 通过反射机制,讲配置的信息赋值给DataSource
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class DataSourcePoolCreator {
    
    /**
     * 根据DataSource的属性配置信息,创建对应的DataSource对象
     */
    public static Map<String, DataSource> create(final Map<String, DataSourceProperties> dataSourcePropsMap) {
        return create(dataSourcePropsMap, true);
    }
    
    /**
     * 遍历DataSourceProperties,创建对应的DataSource对象
     */
    public static Map<String, DataSource> create(final Map<String, DataSourceProperties> dataSourcePropsMap, final boolean cacheEnabled) {
        return dataSourcePropsMap.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> create(entry.getKey(), entry.getValue(), cacheEnabled), (oldValue, currentValue) -> oldValue,
                LinkedHashMap::new));
    }
    
    /**
     * 创建DataSource
     */
    public static DataSource create(final DataSourceProperties dataSourceProps) {
        // 通过Class.forName()获得构造器,创建一个数据源对象。如HikariDataSource对象
        DataSource result = createDataSource(dataSourceProps.getDataSourceClassName());
        // 获取对应DataSource的池元数据
        Optional<DataSourcePoolMetaData> poolMetaData = TypedSPIRegistry.findRegisteredService(DataSourcePoolMetaData.class, dataSourceProps.getDataSourceClassName());
        DataSourceReflection dataSourceReflection = new DataSourceReflection(result);
        // 通过反射,把配置中的值设置到DataSource。通过反射,代码更加简洁且支持性更好、更灵活
        if (poolMetaData.isPresent()) {
            setDefaultFields(dataSourceReflection, poolMetaData.get());
            setConfiguredFields(dataSourceProps, dataSourceReflection, poolMetaData.get());
            appendJdbcUrlProperties(dataSourceProps.getCustomDataSourceProperties(), result, poolMetaData.get());
            dataSourceReflection.addDefaultDataSourceProperties();
        } else {
            setConfiguredFields(dataSourceProps, dataSourceReflection);
        }
        return result;
    }
    
    /**
     * Create data source.
     *
     * @param dataSourceName data source name
     * @param dataSourceProps data source properties
     * @param cacheEnabled cache enabled
     * @return created data source
     */
    public static DataSource create(final String dataSourceName, final DataSourceProperties dataSourceProps, final boolean cacheEnabled) {
        // 创建DataSource,并赋值
        DataSource result = create(dataSourceProps);
        if (cacheEnabled && !GlobalDataSourceRegistry.getInstance().getCachedDataSourceDataSources().containsKey(dataSourceName)) {
            // 加入到缓存
            GlobalDataSourceRegistry.getInstance().getCachedDataSourceDataSources().put(dataSourceName, result);
        }
        return result;
    }

    /**
     * 通过Class.forName()获得构造器,创建一个数据源对象
     */
    @SneakyThrows(ReflectiveOperationException.class)
    private static DataSource createDataSource(final String dataSourceClassName) {
        return (DataSource) Class.forName(dataSourceClassName).getConstructor().newInstance();
    }

    /**
     * 使用反射,添加配置的dataSourceClassName对应的默认配置信息
     */
    private static void setDefaultFields(final DataSourceReflection dataSourceReflection, final DataSourcePoolMetaData poolMetaData) {
        for (Entry<String, Object> entry : poolMetaData.getDefaultProperties().entrySet()) {
            dataSourceReflection.setField(entry.getKey(), entry.getValue());
        }
    }

    /**
     * 通过反射,设置所有本地属性信息
     * @param dataSourceProps
     * @param dataSourceReflection
     */
    private static void setConfiguredFields(final DataSourceProperties dataSourceProps, final DataSourceReflection dataSourceReflection) {
        for (Entry<String, Object> entry : dataSourceProps.getAllLocalProperties().entrySet()) {
            dataSourceReflection.setField(entry.getKey(), entry.getValue());
        }
    }

    /**
     * 使用反射,添加配置的属性
     */
    private static void setConfiguredFields(final DataSourceProperties dataSourceProps, final DataSourceReflection dataSourceReflection, final DataSourcePoolMetaData poolMetaData) {
        for (Entry<String, Object> entry : dataSourceProps.getAllLocalProperties().entrySet()) {
            String fieldName = entry.getKey();
            Object fieldValue = entry.getValue();
            if (isValidProperty(fieldName, fieldValue, poolMetaData) && !fieldName.equals(poolMetaData.getFieldMetaData().getJdbcUrlPropertiesFieldName())) {
                dataSourceReflection.setField(fieldName, fieldValue);
            }
        }
    }
    
    private static boolean isValidProperty(final String key, final Object value, final DataSourcePoolMetaData poolMetaData) {
        return !poolMetaData.getInvalidProperties().containsKey(key) || null == value || !value.equals(poolMetaData.getInvalidProperties().get(key));
    }
    
    /**
     * 附加Jdbc Url属性
     */
    @SuppressWarnings("unchecked")
    private static void appendJdbcUrlProperties(final CustomDataSourceProperties customDataSourceProps, final DataSource targetDataSource, final DataSourcePoolMetaData poolMetaData) {
        // 获取jdbc url属性名,如HikariDataSource为dataSourceProperties
        String jdbcUrlPropertiesFieldName = poolMetaData.getFieldMetaData().getJdbcUrlPropertiesFieldName();
        if (null != jdbcUrlPropertiesFieldName && customDataSourceProps.getProperties().containsKey(jdbcUrlPropertiesFieldName)) {
            Map<String, Object> jdbcUrlProps = (Map<String, Object>) customDataSourceProps.getProperties().get(jdbcUrlPropertiesFieldName);
            // 通过反射,获取DataSource池字段的元数据
            DataSourcePoolMetaDataReflection dataSourcePoolMetaDataReflection = new DataSourcePoolMetaDataReflection(targetDataSource, poolMetaData.getFieldMetaData());
            // 设置
            for (Entry<String, Object> entry : jdbcUrlProps.entrySet()) {
                dataSourcePoolMetaDataReflection.getJdbcConnectionProperties().ifPresent(optional -> optional.setProperty(entry.getKey(), entry.getValue().toString()));
            }
        }
    }
}

通过配置的数据源信息创建DataSource对象的主要流程如下:

1)遍历DataSourceProperties;

2)根据DataSourceProperties对象,通过Class.forName()获得构造器,创建对应dataSourceClassName的数据源DataSource对象;

3)通过Java SPI获取对应数据源的DataSourcePoolMetaData元数据对象;

4)通过反射,获取DataSource对象的设置方法;

5)将DataSourceProperties中的配置信息通过反射设置到DataSource对象,包括DataSourcePoolMetaData元数据对象中的默认配置信息等;

小结

限于篇幅,本篇先分享到这里。以下做一个小结:

1)分片配置文件通过SnakeYaml解析成YamlRootConfiguration对象;

分片配置文件中可以配置的根key为:

databaseName: #数据库名,String类型
dataSources: #数据源,Map<String, Map<String, Object>>类型
rules: # 规则,Collection<YamlRuleConfiguration>类型
props: # 属性,Properties类型
mode: # 模式,YamlModeConfiguration类型

2)dataSources数据源配置,可配置多个;

2.1)dataSources必现配置dataSourceClassName,根据不同的dataSourceClassName,可以设置对应属性配置。默认有:com.mchange.v2.c3p0.ComboPooledDataSource、
com.zaxxer.hikari.HikariDataSource等;

2.2)对于url、username、password等基础配置项,不同的dataSourceClass有对应的兼容性配置项;

2.3)不同的dataSourceClass添加了不同的默认配置项;

具体的配置项可参考:C3P0DataSourcePoolMetaData、HikariDataSourcePoolMetaData等

3)通过配置的dataSources创建对应的DataSource

3.1)根据配置的dataSources,每个dataSource生成一个DataSourceProperties。在DataSourceProperties中,处理了关键配置项的兼容性处理(如url、username等配置项);

3.2)根据配置的dataSourceClassName,生成对应的DataSource对象;

3.3)根据反射,将配置的信息赋值到DataSource对象中。配置的信息包括:yaml中的配置项、对应DataSource的DataSourcePoolMetaData中的默认配置;

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

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

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值