[图像描述](https://res.cloudinary.com/practicaldev/image/fetch/s--Wq9eRXge--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/phd62v4hi28k41td0gws.png)

为什么要学习ShardingSphere的 SPI?

您可能已经熟悉Java和Dubbo的 SPI(Service Provider Interface)机制,那么您可能会疑惑“为什么要学习ShardingSphere的SPI机制?”原因很简单:

  1. ShardingSphere的源码更简单,更容易适配。

2、ShardingSphere的SPI机制执行相当流畅,日常操作所需的代码较少。与 Dubbo 的 SPI 机制及其与IoC相关的附加特性不同,ShardingSphere 中的 SPI 机制只保留了基本结构,使用起来毫不费力。

了解 ShardingSphere 的 SPI

我们还不得不提到Java SPI机制中发现的一些缺点:

1.ServiceLoader类的实例有多个并发线程使用不安全。

2.每次获取一个元素,都需要遍历所有元素,不能按需加载。

  1. 实现类加载失败时,提示异常,不说明真实原因,导致错误难以定位。

4、获取实现类的方式不够灵活。只能通过Iterator形式获取,不能根据一个参数获取对应的实现类。

有鉴于此,让我们看看ShardingSphere是如何用简单的方式解决这些问题的。

加载SPI类

Dubbo 是直接重写了自己的 SPI,包括 SPI 文件名和文件配置方式,与 JDK形成鲜明对比。让我们简单比较一下这两种用法的区别:

Java SPI

META-INF/services文件夹下添加接口实现类

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

进入全屏模式 退出全屏模式

达博SPI

将接口的实现类添加到文件夹META-INF/services中,通过keyvalue进行配置,如下例:

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

进入全屏模式 退出全屏模式

现在我们可以看到 Dubbo 的 Java SPI 和 JDK SPI 是完全不同的。

ShardingSphere如何轻松扩展JDK SPI?

与 Dubbo 的实现理念不同,ShardingSphere 以更少的代码扩展了 JDK SPI。

1.配置与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;
}
public interface TypedSPI {
    /**
     * Get type.
     * 
     * @return type
     */
    String getType();
    /**
     * Get type aliases.
     *
     * @return type aliases
     */
    default Collection<String> getTypeAliases() {
        return Collections.emptyList();
    }
}

进入全屏模式 退出全屏模式

StatelessTypedSPI接口取自TypedSPI,使用多个接口来满足单一接口负责的原则。TypedSPIMap的键,其中子类需要指定自己的 SPI。

这里不需要关心DialectTableMetaDataLoader接口定义了哪些方法,只需要关注SPI如何加载子类即可。如果是 Java SPI,要加载子类,只需在META-INF/services中使用完整的类名来定义它。

[图像描述](https://res.cloudinary.com/practicaldev/image/fetch/s--q0h6UMuK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/9nzzkyhn91dpzvbkapxy.png)

如您所见,它与本机 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());
    }
}

进入全屏模式 退出全屏模式

这里可以看到使用了一个静态块,在类加载过程中,所有的DialectTableMetaDataLoader个实现类都通过ShardingSphereServiceLoader.register注册了。通过使用TypedSPIRegistry.findRegisteredService,我们可以得到我们指定的spi扩展类。

TypedSPIRegistry.findRegisteredService(final Class<T> spiClass, final String type)

进入全屏模式 退出全屏模式

所以我们只需要注意ShardingSphereServiceLoader.registerypedSPIRegistry.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.
     *
     * eparam 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<>();

进入全屏模式 退出全屏模式

And registering is pretty simple too, just use the SPI api embedded in java.

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方法本质上是对ShardingSphereServiceLoadergetSingletonServiceInstancesmethod的调用。

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 源代码。相信您已经注意到,使用 ShardingSphere 的 SPI 比使用 Dubbo 的 SPI 机制更容易、更简单。

总结

ShardingSphere 和 Dubbo 的 SPI 都满足按键查找指定实现类的需求,无需每次使用都重新加载所有实现类,解决并发加载问题。但是相比 Dubbo,ShardingSphere SPI 更加精简,使用更加方便。

您可以稍后在编写自己的 SPI 扩展时参考 ShardingSphere 的实现,因为它实现起来更简单,使用起来也很优雅。您可以编写一个基于SPI的可扩展配置文件解析器,以便我们了解SPI的能力以及它的应用场景。

Apache ShardingSphere 项目链接:

ShardingSphere Github

ShardingSphere 推特

ShardingSphere Slack

贡献者指南

Logo

ModelScope旨在打造下一代开源的模型即服务共享平台,为泛AI开发者提供灵活、易用、低成本的一站式模型服务产品,让模型应用更简单!

更多推荐