1 为啥学习 ShardingSphere SPI?

  1. ShardingSphere 源代码更简单、更易适应
  2. ShardingSphere SPI 机制执行非常顺畅,日常操作所需代码较少。与 Dubbo SPI 及 IoC 相关其他功能不同,ShardingSphere SPI 只保留基本结构,使用轻松

2 Java SPI 机制的缺陷

  1. 具有多个并发线程的 ServiceLoader 类实例不安全
  2. 每次获取元素时,需迭代所有元素,不能按需加载
  3. 当实现类加载失败时,会提示异常而不指示真正原因,错误难定位
  4. 获取实现类的方式不够灵活。只能通过 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 文件夹,并通过 keyvalue 配置:

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 继承,多个接口用于满足单一接口责任的原则。TypedSPIMap 的关键,子类需指定自己的 SPI。

这里无需关心 DialectTableMetaDataLoader 接口定义哪些方法,只需关注子类如何通过 SPI 加载。如 Java SPI,要加载子类,只需在 META-INF/services 定义全类名。

ShardingSphere SPI为何比 Dubbo SPI更简单好用?_Java

与本机 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 的功能以及其应用场景。


作者简介:魔都国企技术专家兼架构,多家大厂后台研发和架构经验,负责复杂度极高业务系统的模块化、服务化、平台化研发工作。具有丰富带团队经验,深厚人才识别和培养的积累。

参考:

编程严选网