ShardingSphere源码解析之微内核架构(下)

通过上一篇的介绍,我们对微内核架构模式以及JDK所提供的SPI机制有了一定的了解。在此基础上,今天我们就来深入分析ShardingSphere中所用到的微内核架构的实现原理。

1. ShardingSphere中的微内核架构基础实现机制

我们发现,在ShardingSphere源码的根目录下,存在一个独立的工程shardingsphere-spi。显然,从命名上看,这个工程中应该包含了ShardingSphere实现SPI的相关代码。我们快速浏览该工程,发现里面只有一个接口定义和两个工具类。

我们先来看这个接口定义TypeBasedSPI,如下所示:

public interface TypeBasedSPI {   

    String getType();   

    Properties getProperties();   

    void setProperties(Properties properties);

}

从定位上讲,这个接口在ShardingSphere中应该是一个顶层接口,它的类层结构如下所示,显然,我们在上一篇提到的用于生成分布式主键的ShardingKeyGenerator、用于数据脱敏的ShardingEncryptor、用于分布式事务的ShardingTransactionManager以及用于数据库治理的注册中心接口RegistryCenter都继承了该接口:

ShardingSphere源码解析之微内核架构(下)

然后,我们再来看NewInstanceServiceLoader类,从命名上看,我们也不难想象该类的作用类似于一种ServiceLoader,用于加载新的目标对象实例。NewInstanceServiceLoader代码如下所示:

public final class NewInstanceServiceLoader {   

    private static final Map<Class, Collection<Class<?>>> SERVICE_MAP = new HashMap<>();   

    //通过ServiceLoader获取新的SPI服务实例并注册到SERVICE_MAP中   

    public static <T> void register(final Class<T> service) {

        for (T each : ServiceLoader.load(service)) {

            registerServiceClass(service, each);

        }

    }   

    @SuppressWarnings("unchecked")

    private static <T> void registerServiceClass(final Class<T> service, final T instance) {

        Collection<Class<?>> serviceClasses = SERVICE_MAP.get(service);

        if (null == serviceClasses) {

            serviceClasses = new LinkedHashSet<>();

        }

        serviceClasses.add(instance.getClass());

        SERVICE_MAP.put(service, serviceClasses);

    }   

    @SneakyThrows

    @SuppressWarnings("unchecked")

    public static <T> Collection<T> newServiceInstances(final Class<T> service) {

        Collection<T> result = new LinkedList<>();

        if (null == SERVICE_MAP.get(service)) {

            return result;

        }

        for (Class<?> each : SERVICE_MAP.get(service)) {

            result.add((T) each.newInstance());

        }

        return result;

    }

}

这里首先看到了熟悉的ServiceLoader.load(service)方法,这是上一篇中介绍的JDK ServiceLoader工具类的具体应用。同时,我们注意到ShardingSphere使用了一个HashMap来保存类的定义以及类的实例之前的一对多关系,可以认为这是一种用于提高访问效率的缓存机制。

最后,我们来看一下TypeBasedSPIServiceLoader的实现,该类依赖于前面介绍的NewInstanceServiceLoader类,如下所示的方法使用NewInstanceServiceLoader获取实例类列表,并根据所传入的类型做过滤:

    //使用NewInstanceServiceLoader获取实例类列表,并根据类型做过滤

    private Collection<T> loadTypeBasedServices(final String type) {

        return Collections2.filter(NewInstanceServiceLoader.newServiceInstances(classType), new Predicate<T>() {           

            @Override

            public boolean apply(final T input) {

                return type.equalsIgnoreCase(input.getType());

            }

        });

    }

TypeBasedSPIServiceLoader对外暴露服务的接口如下所示,对通过loadTypeBasedServices方法获取的服务实例设置对应的属性然后返回:

//基于类型通过SPI创建实例

    public final T newService(final String type, final Properties props) {

        Collection<T> typeBasedServices = loadTypeBasedServices(type);

        if (typeBasedServices.isEmpty()) {

            throw new RuntimeException(String.format("Invalid `%s` SPI type `%s`.", classType.getName(), type));

        }

        T result = typeBasedServices.iterator().next();

        result.setProperties(props);

        return result;

}

同时,TypeBasedSPIServiceLoader也对外暴露了不需要传入类型的newService方法,该方法使用了loadFirstTypeBasedService工具方法来获取第一个服务实例,相关代码如下所示:

//基于默认类型通过SPI创建实例

    public final T newService() {

        T result = loadFirstTypeBasedService();

        result.setProperties(new Properties());

        return result;

    }

    private T loadFirstTypeBasedService() {

        Collection<T> instances = NewInstanceServiceLoader.newServiceInstances(classType);

        if (instances.isEmpty()) {

            throw new RuntimeException(String.format("Invalid `%s` SPI, no implementation class load from SPI.", classType.getName()));

        }

        return instances.iterator().next();

    }

这样,ShardingSphere的shardingsphere-spi工程中的内容就介绍完毕。这部分内容相当于是微内核架构的基础实现机制,下面我们来找一个典型的应用场景来看一下具体的使用方法。

2. 微内核架构在ShardingSphere中的应用

我们回顾《ShardingSphere源码解析之SQL解析引擎(二)》中的内容,讲到具体的SQLParser的生成由SQLParserFactory负责,SQLParserFactory定义如下:

public final class SQLParserFactory {   

    public static SQLParser newInstance(final String databaseTypeName, final String sql) {

    //通过SPI机制加载所有扩展

    for (SQLParserEntry each : NewInstanceServiceLoader.newServiceInstances(SQLParserEntry.class)) {

        …

    }

可以看到,这里并没有使用前面介绍的TypeBasedSPIServiceLoader来加载实例,而是直接使用了更为底层的NewInstanceServiceLoader。

这里引入的SQLParserEntry接口就位于shardingsphere-sql-parser-spi 工程的org.apache.shardingsphere.sql.parser.spi包中。显然,从包的命名上看,该接口是一个SPI接口,定义如下:

public interface SQLParserEntry {   

    String getDatabaseTypeName();   

    Class<? extends Lexer> getLexerClass();   

    Class<? extends SQLParser> getParserClass();

}

SQLParserEntry接口有一批实现,其类层结构如下所示:

ShardingSphere源码解析之微内核架构(下)

我们先来看针对Mysql的代码工程shardingsphere-sql-parser-mysql,我们在META-INF/services目录下找到了一个org.apache.shardingsphere.sql.parser.spi.SQLParserEntry文件,如下所示:

ShardingSphere源码解析之微内核架构(下)

可以看到这里指向了org.apache.shardingsphere.sql.parser.MySQLParserEntry类。然后我们再来到Oracle的代码工程shardingsphere-sql-parser-oracle,在META-INF/services目录下同样找到了一个org.apache.shardingsphere.sql.parser.spi.SQLParserEntry文件,如下所示:

ShardingSphere源码解析之微内核架构(下)

显然,这里应该指向org.apache.shardingsphere.sql.parser.OracleParserEntry类。通过这种方式,系统在运行时就会根据类路径动态加载SPI。

我们注意到SQLParserEntry接口的类层结构中实际上并没有使用到TypeBasedSPI,而是完全基于JDK原生的SPI机制。接下来,我们来找一个使用TypeBasedSPI的示例,比方说ConfigCenter(位于org.apache.shardingsphere.orchestration.config.api包下),该接口的声明如下所示:

public interface ConfigCenter extends TypeBasedSPI

可以看到ConfigCenter接口继承了TypeBasedSPI接口,而在ShardingSphere中也存在一批ConfigCenter接口的实现,其类层结构如下所示:

ShardingSphere源码解析之微内核架构(下)

以ApolloConfigCenter为例,我们来看来它的使用方法,我们在sharding-orchestration-core工程的org.apache.shardingsphere.orchestration.internal.configcenter中找到ConfigCenterServiceLoader类,该类扩展了前面提到的TypeBasedSPIServiceLoader类:

public final class ConfigCenterServiceLoader extends TypeBasedSPIServiceLoader<ConfigCenter> {   

    static {

        NewInstanceServiceLoader.register(ConfigCenter.class);

    }   

    public ConfigCenterServiceLoader() {

        super(ConfigCenter.class);

    }   

    //基于SPI加载ConfigCenter

    public ConfigCenter load(final ConfigCenterConfiguration configCenterConfig) {

        Preconditions.checkNotNull(configCenterConfig, "Config center configuration cannot be null.");

        ConfigCenter result = newService(configCenterConfig.getType(), configCenterConfig.getProperties());

        result.init(configCenterConfig);

        return result;

    }

}

首先,在ConfigCenterServiceLoader类中通过NewInstanceServiceLoader.register(ConfigCenter.class)语句将所有ConfigCenter注册到系统中,这一步会通过JDK的ServiceLoader工具类加载类路径中的所有ConfigCenter实例。

然后,我们可以看到上面的load方法中,通过父类TypeBasedSPIServiceLoader的newService方法基于类型创建了SPI实例。

不难想象,在sharding-orchestration-config-apollo工程的META-INF/services目录下应该存在一个名为org.apache.shardingsphere.orchestration.config.api.ConfigCenter的配置文件,指向ApolloConfigCenter类,如下所示:

ShardingSphere源码解析之微内核架构(下)

其他的ConfigCenter实现也是一样,大家可以自行查阅sharding-orchestration-config-zookeeper-curator等工程中的SPI配置文件。

至此,我们对ShardingSphere中的微内核架构有了一个全面的了解。实际上,相较Dubbo等框架,ShardingSphere中微内核架构的实现并不复杂,基本就是对JDK中SPI机制的封装。

更多内容可以关注我的公众号:程序员向架构师转型。

ShardingSphere源码解析之微内核架构(下)ShardingSphere源码解析之微内核架构(下) 天涯兰的博客 博客专家 发布了94 篇原创文章 · 获赞 9 · 访问量 11万+ 私信 关注
上一篇:ShardingSphere源码解析之路由引擎(四)


下一篇:ShardingSphere的发展历程