为什么要学习Shardingsphere
spi
之前我们都简单研究过 java spi和 dubbo 的spi机制。那么可能有小伙伴会问既然我们都知道了duboo 的spi机制为什么还要研究Shardingsphere
的spi机制呢?其实原因很简单:
-
Shardingsphere
源码更简单,更容易我们学习 -
Shardingsphere
中的spi机制实现的也比较优雅,核心代码很少,更贴合我们平时项目使用,仅仅只有spi的封装,更简洁,不像dubbo中可能还增加了ioc相关的功能等。
不明白java spi 和dubbo spi的可以看看我之前的博文
Shardingsphere spi
我们这里还是简单说一下java spi机制的一些缺点
-
多个并发多线程使用ServiceLoader类的实例是不安全的 -
每次获取元素需要遍历全部元素,不能按需加载。 -
加载不到实现类时抛出并不是真正原因的异常,错误很难定位 -
获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类
基于这些问题我们来看看Shardingsphere
是如何简洁的解决这些问题的
加载spi类
dubbo对于自己的spi直接是重写了,用法和jdk可以说是完全不一样,包括spi的文件名,以及文件配置方式 我们这里还是简单对比下dubbo 和 java spi的使用区别
java spi
在文件夹META-INF/services
下添加接口的实现类
org.apache.dubbo.OptimusPrime
org.apache.dubbo.Bumblebee
dubbo spi
在文件夹META-INF/dubbo
下添加接口的实现类以key,value的方式配置 类似如下
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
可以看到dubbo 的spi基本和原先的jdk spi完全不一致了
Shardingsphere
是如何更简洁的扩展jdk spi
与dubbo实现理念不同的是 Shardingsphere
用了更少的代码在jdk spi上做了扩展
-
首先是配置方式完全和java spi一致
我们以DialectTableMetaDataLoader
接口的实现类为例
-
DialectTableMetaDataLoader.class
public interface DialectTableMetaDataLoader extends StatelessTypedSPI {
/**
* Load table meta data.
*
* @param dataSource data source
* @param tables tables
* @return table meta data map
* @throws SQLException SQL exception
*/
Map<String, TableMetaData> load(DataSource dataSource, Collection<String> tables) throws SQLException;
}
-
TypedSPI.class
public interface TypedSPI {
/**
* Get type.
*
* @return type
*/
String getType();
/**
* Get type aliases.
*
* @return type aliases
*/
default Collection<String> getTypeAliases() {
return Collections.emptyList();
}
}
StatelessTypedSPI接口继承于TypedSPI,多接口继承用于满足接口职责单一原则,其中TypedSPI就是子类需要指定自己spi中的Map中的key
这里我们完全无需关心DialectTableMetaDataLoader
接口定义的是什么方法,我们重点是要关心子类的如何通过spi加载的。这里如果是java spi,我们需要如何加载子类呢?很简单,在META-INF/services
中通过全类名定义就行了可以看到完全和java 原生的spi配置方式一致。那么是如何解决原生java spi的缺点的呢?
工厂设计模式的使用
在对于每一个接口需要通过spi扩展并创建的时候,一般会有一个类似的xxDataLoaderFactory
来创建获取指定的spi扩展类
DialectTableMetaDataLoaderFactory
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class DialectTableMetaDataLoaderFactory {
static {
ShardingSphereServiceLoader.register(DialectTableMetaDataLoader.class);
}
/**
* Create new instance of dialect table meta data loader.
*
* @param databaseType database type
* @return new instance of dialect table meta data loader
*/
public static Optional<DialectTableMetaDataLoader> newInstance(final DatabaseType databaseType) {
return TypedSPIRegistry.findRegisteredService(DialectTableMetaDataLoader.class, databaseType.getName());
}
}
这里可以看到这里使用了静态代码块,在类加载的时候就通过方法ShardingSphereServiceLoader.register
注册了DialectTableMetaDataLoader
的所有实现类,我们需要获取我们的指定的spi 扩展类就通过方法TypedSPIRegistry.findRegisteredService
去获取
TypedSPIRegistry.findRegisteredService(final Class<T> spiClass, final String type)
所以我们核心就看看ShardingSphereServiceLoader.register``和
ypedSPIRegistry.findRegisteredService`方法即可
ShardingSphereServiceLoader
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ShardingSphereServiceLoader {
private static final Map<Class<?>, Collection<Object>> SERVICES = new ConcurrentHashMap<>();
/**
* Register service.
*
* @param serviceInterface service interface
*/
public static void register(final Class<?> serviceInterface) {
if (!SERVICES.containsKey(serviceInterface)) {
SERVICES.put(serviceInterface, load(serviceInterface));
}
}
private static <T> Collection<Object> load(final Class<T> serviceInterface) {
Collection<Object> result = new LinkedList<>();
for (T each : ServiceLoader.load(serviceInterface)) {
result.add(each);
}
return result;
}
/**
* Get singleton service instances.
*
* @param service service class
* @param <T> type of service
* @return service instances
*/
@SuppressWarnings("unchecked")
public static <T> Collection<T> getSingletonServiceInstances(final Class<T> service) {
return (Collection<T>) SERVICES.getOrDefault(service, Collections.emptyList());
}
/**
* New service instances.
*
* @param service service class
* @param <T> type of service
* @return service instances
*/
@SuppressWarnings("unchecked")
public static <T> Collection<T> newServiceInstances(final Class<T> service) {
if (!SERVICES.containsKey(service)) {
return Collections.emptyList();
}
Collection<Object> services = SERVICES.get(service);
if (services.isEmpty()) {
return Collections.emptyList();
}
Collection<T> result = new ArrayList<>(services.size());
for (Object each : services) {
result.add((T) newServiceInstance(each.getClass()));
}
return result;
}
private static Object newServiceInstance(final Class<?> clazz) {
try {
return clazz.getDeclaredConstructor().newInstance();
} catch (final ReflectiveOperationException ex) {
throw new ServiceLoaderInstantiationException(clazz, ex);
}
}
}
可以看到所有的spi 类都是放在SERVICES
这个属性中
private static final Map<Class<?>, Collection<Object>> SERVICES = new ConcurrentHashMap<>();
而注册也是很简单,直接调用java 默认的spi api
public static void register(final Class<?> serviceInterface) {
if (!SERVICES.containsKey(serviceInterface)) {
SERVICES.put(serviceInterface, load(serviceInterface));
}
}
private static <T> Collection<Object> load(final Class<T> serviceInterface) {
Collection<Object> result = new LinkedList<>();
for (T each : ServiceLoader.load(serviceInterface)) {
result.add(each);
}
return result;
}
TypedSPIRegistry
TypedSPIRegistry
中的findRegisteredService
方法本质上其实也是调用的ShardingSphereServiceLoader
的getSingletonServiceInstances
方法
-
TypedSPIRegistry
public static <T extends StatelessTypedSPI> Optional<T> findRegisteredService(final Class<T> spiClass, final String type) {
for (T each : ShardingSphereServiceLoader.getSingletonServiceInstances(spiClass)) {
if (matchesType(type, each)) {
return Optional.of(each);
}
}
return Optional.empty();
}
private static boolean matchesType(final String type, final TypedSPI typedSPI) {
return typedSPI.getType().equalsIgnoreCase(type) || typedSPI.getTypeAliases().contains(type);
}
这里可以看到通过扩展类也就是通过
TypedSPI
中的getType
或getTypeAliases
去匹配,这就是为什么每个spi需要去实现TypedSPI
接口
我们这里再来看看ShardingSphereServiceLoader
中的newServiceInstances
方法
public static <T> Collection<T> newServiceInstances(final Class<T> service) {
if (!SERVICES.containsKey(service)) {
return Collections.emptyList();
}
Collection<Object> services = SERVICES.get(service);
if (services.isEmpty()) {
return Collections.emptyList();
}
Collection<T> result = new ArrayList<>(services.size());
for (Object each : services) {
result.add((T) newServiceInstance(each.getClass()));
}
return result;
}
可以看到也是非常简单的,直接在直接通过静态代码块注册的SERVICES
中找到接口的所有实现类返回
到这里Shardingsphere
的spi 源码基本就分析清晰了,是不是比dubbo的spi实现的更简单,更容易使用
总结
Shardingsphere
的spi 相比 dubbo
的spi 功能上都是满足通过key去寻找指定实现类,不用每次使用都重新加载所有实现类,也解决了并发加载问题。但是相比 dubbo
,Shardingsphere
spi实现的更简洁,更容易使用。后续我们在自己编写需要有spi扩展的时候完全可以参考Shardingsphere
这一套实现方式。因为实现的比较简单,但是也很好用。后续我们后机会可以基于spi
写一个可扩展的配置文件解析器,让大家明白spi的强大与实际应用场景
原文始发于微信公众号(小奏技术):疫情在家无聊和我一起学习Shardingsphere中Spi的应用及与dubbo spi的区别
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/29992.html