Soul网关源码阅读(十六)—— SPI插件

16 篇文章 1 订阅

SPI 概念

SPI(Service Provider Interface),是一种模块间相互引用的机制,可以用来启用框架和替换组件,一般的流程是服务的提供者在classpath指定配置实现类的全名,由调用方读取和加载使用,调用方无需修改代码,通常以jar包的形式引入需要使用的实现,Dubbo,Soul等项目使用了SPI机制,但给使用者提供了更丰富便捷的选择,可以由用户安优先级,名称等方式选择使用那个实现。
SPI 标准流程:

  • 定义标准接口
  • 编写不同的实现,配置classpath目录指定位置
  • 使用方加载使用

在这里插入图片描述

JDK内置的SPI

JDK内置的SPI 机制 ,约定好的classpath目录下META_INFO/services/ 创建一个以服务接口命名的文件,该文件中内容为该接口的实现类(完全名称即包含package名称)通过java.util.ServiceLoader加载指定接口
举个栗子🌰

  1. 我们定义一个LearnSpi interface
    package com.cuicui.bootcamp
    public interface LearnSpi {
    void readBook();
    }

  2. 我们一个school的学习工程打包为school.jar 里面实现类:
    package com.cuicui.bootcamp
    import com.cuicui.bootcamp.LearnSpi
    public class SchoolLearnSpi implements LearnSpi {
    public void readBook(){
    System.out.println(“School readBook”);
    }
    }

需要在school工程的classpath目录下META_INFO/services/ 中新建一个名为
com.cuicui.bootcamp. LearnSpi的文件
文件的内容为
com.cuicui.bootcamp.SchoolLearnSpi

  1. 调用者可以引入school.jar 并通过java.util.ServiceLoader加载获取
    ServiceLoader serviceLoader = ServiceLoader.load(LearnSpi.class);
    for (LearnSpi LearnSpi : serviceLoader) {
    LearnSpi.readBook();
    }
    // 在终端就会打印 : School readBook

JDK默认的SPI机制在使用的时候我们没有办法获取到我们想要的特定的实现,只能通过for循环一个一个遍历匹配,也没有命名,实际很难使用。

Soul中的spi

Soul 的spi机制,是由Soul-spi这个项目负责实现,并借鉴Dubbo SPI的实现
ExtensionLoader 提供了比JDK ServiceLoader更为强大的功能。

ExtensionLoader

我们这里重点分析ExtensionLoader
类成员有:
// soul spi的扩展目录

private static final String SOUL_DIRECTORY = "META-INF/soul/";

// 不同扩展接口对应的ExtensionLoader的缓存类,静态成员

private static final Map<Class<?>, ExtensionLoader<?>> LOADERS = new ConcurrentHashMap<>();

// 扩展接口类

private final Class<T> clazz;

// 自定的Holder 类型,cachedClasses 用于存储不同扩展对应的实现类们

private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

// key 是class 名称扩展类对应的instance缓存

private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

// key 是Class对象 扩展的instance

private final Map<Class<?>, Object> joinInstances = new ConcurrentHashMap<>();

// 每一种扩展对应的default扩展名称,loadclass时候会判断注解的value是否为空获得

private String cachedDefaultName;

如果单纯的看Soul里面的实现,cachedInstances 和joinInstances这可能会让人产生困惑,为什么要加两个缓存??,这里就要溯源去看dubbo里面的实现了,joinInstances里面是一对一的 value不会重复,cachedInstances里面不同的name,value是可以相同的 目前soul里面应该还没有利用起来????。
ExtensionLoader 类中维护的ConcurrentHashMap类型的 LOADERS 会为每个SPI 扩展都维护一个ExtensionLoader instance使用者通过静态方法getExtensionLoader来lazyload

public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz) {
    if (clazz == null) {
        throw new NullPointerException("extension clazz is null");
    }
    if (!clazz.isInterface()) {
        throw new IllegalArgumentException("extension clazz (" + clazz + ") is not interface!");
    }
    // 必须显示声明
    if (!clazz.isAnnotationPresent(SPI.class)) {
        throw new IllegalArgumentException("extension clazz (" + clazz + ") without @" + SPI.class + " Annotation");
    }
    ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) LOADERS.get(clazz);
    if (extensionLoader != null) {
        return extensionLoader;
    }
    LOADERS.putIfAbsent(clazz, new ExtensionLoader<>(clazz));
    return (ExtensionLoader<T>) LOADERS.get(clazz);
}

如果没有会通过私有的构造方法实例化一个ExtensionLoader。

private ExtensionLoader(final Class<T> clazz) {
    this.clazz = clazz;
    if (clazz != ExtensionFactory.class) {
        ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getExtensionClasses();
    }
}

实例化一个ExtensionLoader的同时也会加载实现该扩展的类,即通过getExtensionCLasses方法
具体的流程是:
loadExtensionClass -> loadDirectory -> 循环调用 loadResources -> 循环调用 loadClass

这里需要注意的一点是在loadDirectory函数中!

Enumeration<URL> urls = classLoader != null ? classLoader.getResources(fileName)
        : ClassLoader.getSystemResources(fileName); 
        // 这里使用了getResources 和getSystemResources 而不是getResource
if (urls != null) {
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        loadResources(classes, url);
    }
}

Soul SPI使用分析

以Divide插件为例子,在选择负载均衡器的时候便是通过SPI的机制拿到了对应算法的LoadBalance。

public static DivideUpstream selector(final List<DivideUpstream> upstreamList, final String algorithm, final String ip) {
    LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm);
    return loadBalance.select(upstreamList, ip);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值