为什么学习 ShardingSphere 的 SPI?
你可能已经熟悉 Java 和 Dubbo 的 SPI(Service Provider Interface)机制,所以你可能会想:“为什么要学习 ShardingSphere 的 SPI 机制呢?”原因非常简单:
- ShardingSphere 的源代码更简单、更容易适应。
- ShardingSphere 的 SPI 机制执行非常顺畅,日常操作所需的代码较少。与 Dubbo 的 SPI 机制以及与 IoC 相关的其他功能不同,ShardingSphere 的 SPI 只保留了基本结构,使用起来轻松自如。
理解 ShardingSphere 的 SPI
我们还必须提到 Java SPI 机制存在一些缺陷:
- 具有多个并发线程的 ServiceLoader 类实例不安全。
- 每次获取元素时,需要迭代所有元素,不能按需加载。
- 当实现类加载失败时,会提示异常而不指示真正的原因,使错误难以定位。
- 获取实现类的方式不够灵活。只能通过 Iterator 形式获取,而不能根据一个参数获取相应的实现类。
在此背景下,让我们看看 ShardingSphere 如何通过简单的方式解决这些问题。
加载 SPI 类
Dubbo 是其自己 SPI 的直接重写,包括 SPI 文件名和文件配置方式,与 JDK 形成鲜明对比。让我们简要比较一下这两者的使用:
Java SPI
将接口实现类添加到 META-INF/services
文件夹下:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
Dubbo SPI
将接口的实现类添加到 META-INF/services
文件夹下,并通过 key
、value
配置,如下例所示:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
现在我们可以看到,Dubbo 的 Java SPI 与 JDK SPI 完全不同。
ShardingSphere 如何简单地扩展 JDK SPI?
与 Dubbo 的实现理念不同,ShardingSphere 通过较少的代码扩展了 JDK SPI。
- 配置与 Java SPI 完全相同。以
DialectTableMetaDataLoader
接口实现类为例:
DialectTableMetaDataLoader.class
public interface DialectTableMetaDataLoader extends StatelessTypedSPI {
Map<String, TableMetaData> load(DataSource dataSource, Collection<String> tables) throws SQLException;
}
public interface TypedSPI {
String getType();
default Collection<String> getTypeAliases() {
return Collections.emptyList();
}
}
StatelessTypedSPI
接口从 TypedSPI
继承,多个接口用于满足单一接口责任的原则。TypedSPI
是 Map
的关键,子类需要指定自己的 SPI。
在这里,您无需关心 DialectTableMetaDataLoader
接口定义了哪些方法,只需关注子类如何通过 SPI 加载即可。如果是 Java SPI,要加载子类,只需在 META-INF/services
中定义它的全类名。
正如您所看到的,与本机 Java SPI 配置完全相同。那么它的缺点呢?
使用工厂方法模式
对于每个需要通过 SPI 扩展和创建的接口,通常都有一个类似的 xxDataLoaderFactory
用于创建和获取指定的 SPI 扩展类。
DialectTableMetaDataLoaderFactory
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class DialectTableMetaDataLoaderFactory {
public static Optional<DialectTableMetaDataLoader> newInstance(final DatabaseType databaseType) {
return TypedSPILoader.findService(DialectTableMetaDataLoader.class, databaseType.getName());
}
}
在这里,使用了静态代码块,所有的 DialectTableMetaDataLoader
实现类都是在类加载过程中通过 ShardingSphereServiceLoader.register
注册的。通过使用 TypedSPILoader.findService
,我们可以获取到我们指定的 spi 扩展类。
TypedSPILoader.findService(final Class<T> spiClass, final String type)
因此,我们只需关注 TypedSPILoader.findService
方法。
TypedSPILoader
TypedSPILoader
中的 findService
方法本质上是调用了 ShardingSphereServiceLoader
的 getSingletonServiceInstancesmethod
。
public static <T extends StatelessTypedSPI> Optional<T> findService(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);
}
在这里,类扩展是直接在 SERVICES
中通过静态代码块注册的。
现在让我们看看 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 更加容易和简单。
总结
ShardingSphere 和 Dubbo 的 SPI 都满足按键查找指定实现类的要求,而无需每次使用时重新加载所有实现类,解决了并发加载问题。然而,与 Dubbo 相比,ShardingSphere 的 SPI 更加简洁、易用。
在编写自己的 SPI 扩展时,可以在后续参考 ShardingSphere 的实现,因为它更简单、更优雅。您可以基于 SPI 编写一个可扩展的配置文件解析器,以便了解 SPI 的功能以及其应用场景。
标签:Dubbo,Java,ShardingSphere,SPI,return,final From: https://blog.51cto.com/JavaEdge/9105853