疫情在家无聊和我一起学习Shardingsphere中Spi的应用及与dubbo spi的区别

这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党

为什么要学习Shardingsphere spi

之前我们都简单研究过 java spi和 dubbo 的spi机制。那么可能有小伙伴会问既然我们都知道了duboo 的spi机制为什么还要研究Shardingsphere的spi机制呢?
其实原因很简单:

  1. Shardingsphere源码更简单,更容易我们学习
  2. Shardingsphere中的spi机制实现的也比较优雅,核心代码很少,更贴合我们平时项目使用,仅仅只有spi的封装,更简洁,不像dubbo中可能还增加了ioc相关的功能等。

不明白java spi 和dubbo spi的可以看看我之前的博文

Shardingsphere spi

我们这里还是简单说一下java spi机制的一些缺点

  1. 多个并发多线程使用ServiceLoader类的实例是不安全的
  2. 每次获取元素需要遍历全部元素,不能按需加载。
  3. 加载不到实现类时抛出并不是真正原因的异常,错误很难定位
  4. 获取某个实现类的方式不够灵活,只能通过 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上做了扩展

  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;
}
  • 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方法本质上其实也是调用的ShardingSphereServiceLoadergetSingletonServiceInstances方法

  • 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 中的getTypegetTypeAliases去匹配,这就是为什么每个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去寻找指定实现类,不用每次使用都重新加载所有实现类,也解决了并发加载问题。
但是相比 dubboShardingsphere spi实现的更简洁,更容易使用。
后续我们在自己编写需要有spi扩展的时候完全可以参考Shardingsphere这一套实现方式。因为实现的比较简单,但是也很好用。
后续我们后机会可以基于spi 写一个可扩展的配置文件解析器,让大家明白spi的强大与实际应用场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值