1 为啥学习 ShardingSphere SPI?
- ShardingSphere 源代码更简单、更易适应
- ShardingSphere SPI 机制执行非常顺畅,日常操作所需代码较少。与 Dubbo SPI 及 IoC 相关其他功能不同,ShardingSphere SPI 只保留基本结构,使用轻松
2 Java SPI 机制的缺陷
- 具有多个并发线程的 ServiceLoader 类实例不安全
- 每次获取元素时,需迭代所有元素,不能按需加载
- 当实现类加载失败时,会提示异常而不指示真正原因,错误难定位
- 获取实现类的方式不够灵活。只能通过 Iterator 形式获取,而不能根据一个参数获取相应实现类
ShardingSphere 如何简单解决这些问题呢。
3 加载 SPI 类
Dubbo 是其自己 SPI 的直接重写,包括 SPI 文件名和文件配置方式,与 JDK 形成鲜明对比。
3.1 Java SPI
将接口实现类添加到 META-INF/services
文件夹:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
3.2 Dubbo SPI
将接口实现类添加到 META-INF/services
文件夹,并通过 key
、value
配置:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
Dubbo SPI 与 JDK SPI 完全不同。
4 ShardingSphere 如何简单扩展 JDK SPI?
与 Dubbo 理念不同,ShardingSphere 通过较少代码扩展 JDK SPI。
配置与 Java SPI 完全相同
如 DialectTableMetaDataLoader
接口实现类:
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 配置完全相同。
那它有缺点吗?
5 工厂方法模式
每个需要通过 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
中找到接口的所有实现类返回。
6 总结
ShardingSphere 和 Dubbo 的 SPI 都满足按K查找指定实现类的要求,而无需每次使用时重新加载所有实现类,解决并发加载问题。但与 Dubbo 相比,ShardingSphere SPI 更简洁、易用。
在编写自己的 SPI 扩展时,可参考 ShardingSphere 的实现,因为更简单、优雅。可基于 SPI 编写一个可扩展的配置文件解析器,以便了解 SPI 的功能以及其应用场景。
作者简介:魔都国企技术专家兼架构,多家大厂后台研发和架构经验,负责复杂度极高业务系统的模块化、服务化、平台化研发工作。具有丰富带团队经验,深厚人才识别和培养的积累。
参考:
编程严选网