通过上一篇的介绍,我们对微内核架构模式以及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都继承了该接口:
然后,我们再来看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接口有一批实现,其类层结构如下所示:
我们先来看针对Mysql的代码工程shardingsphere-sql-parser-mysql,我们在META-INF/services目录下找到了一个org.apache.shardingsphere.sql.parser.spi.SQLParserEntry文件,如下所示:
可以看到这里指向了org.apache.shardingsphere.sql.parser.MySQLParserEntry类。然后我们再来到Oracle的代码工程shardingsphere-sql-parser-oracle,在META-INF/services目录下同样找到了一个org.apache.shardingsphere.sql.parser.spi.SQLParserEntry文件,如下所示:
显然,这里应该指向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接口的实现,其类层结构如下所示:
以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类,如下所示:
其他的ConfigCenter实现也是一样,大家可以自行查阅sharding-orchestration-config-zookeeper-curator等工程中的SPI配置文件。
至此,我们对ShardingSphere中的微内核架构有了一个全面的了解。实际上,相较Dubbo等框架,ShardingSphere中微内核架构的实现并不复杂,基本就是对JDK中SPI机制的封装。
更多内容可以关注我的公众号:程序员向架构师转型。
天涯兰的博客 博客专家 发布了94 篇原创文章 · 获赞 9 · 访问量 11万+ 私信 关注