SPIJava SPIDubbo SPI案例原理

Java SPI

SPI 全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现,然后按照SPI 标准存放到资源路径META-INF/services目录下,文件的命名为该服务接口的全限定名。

案例

有这么一个服务

package com.zto.sxy.helloworld;

public interface ISPI {
    void say();
}

实现类SPITestA

package com.zto.sxy.helloworld;

public class SPITestA implements ISPI{
    @Override
    public void say() {
        System.out.println("SPI with implement testA");
    }
}

实现类SPITestB

package com.zto.sxy.helloworld;

public class SPITestB implements ISPI{
    @Override
    public void say() {
        System.out.println("SPI with implement testB");
    }
}

然后在resources目录下添加META-INF/services文件夹,同时新建一个名为com.zto.sxy.helloworld.ISPI的文件,内容如下:

com.zto.sxy.helloworld.SPITestA

com.zto.sxy.helloworld.SPITestB

以下是测试类

public class TestSPI {
    @Test
    public void testWithNoSPI(){
        ISPI testA = new SPITestA();
        testA.say();

        ISPI testB = new SPITestB();
        testB.say();
    }
    
    @Test
    public void testWithSPI(){
        ServiceLoader services = ServiceLoader.load(ISPI.class);
        for (Iterator<ISPI> iterator = services.iterator(); iterator.hasNext(); ) {
            ISPI spi = iterator.next();
            spi.say();
        }
    }
}

执行测试函数,发现两个输出结果一样,说明SPI生效了,输出结果如下:

SPI with implement testA
SPI with implement testB

实现原理

先看load方法

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取当前线程上下文类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){
    return new ServiceLoader<>(service, loader);
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
    // 确保Class参数不为空
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;

    // 清空缓存,确保会重新加载
    reload();
}

在调用的的时候,先通过调用iterator方法返回一个Iterator对象,然后遍历该Iterator对象

public Iterator<S> iterator() {
    return new Iterator<S>() {
        //     private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

调用hasNext方法的时候,会调用lookupIterator.hasNext()

private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }

        // 获取所有类的全类名
        pending = parse(service, configs.nextElement());
    }
  
    nextName = pending.next();
    return true;
}

当调用next方法的时候

private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        // 生成Class对象
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
                "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
        fail(service,
                "Provider " + cn  + " not a subtype");
    }
    try {
        // 实例化
        S p = service.cast(c.newInstance());
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
                "Provider " + cn + " could not be instantiated",
                x);
    }
    throw new Error();          // This cannot happen
}

ServiceLoader不是一实例化以后立马就去读配置文件中的服务实现者并实例化,而是等其Iterator实现获取对应的服务提供者时才会加载对应的配置文件进行解析,在调用Iterator的hasNext方法时会去加载配置文件进行解析,在调用next方法时会将对应的服务提供者进行实例化并进行缓存。

Dubbo SPI

某博客

Dubbo基于Java SPI机制,在其基础上做了改进和扩展,其实现类是ExtensionLoader。

案例

@Test
public void test(){
    Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");

    protocol.destroy();
}

原理

先看看getExtensionLoader静态方法

// 缓存的是扩展和ExtensionLoader实例的关系,每一种扩展对应一个ExtensionLoader实例
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

// 缓存的是扩展实现类
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null)
        throw new IllegalArgumentException("Extension type == null");
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }

    // 检查是否有@SPI注解
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }

    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏淡定的博客

python入门基础语法总结

1023
来自专栏海说

Dubbo源码学习--环境搭建及基础准备(ServiceLoader、ExtensionLoader)

环境搭建 Github上下载Dubbo最新发布版本,楼主下载版本为2.5.7。 cd到源码解压目录,maven编译,命令为: mvn clean install...

4370
来自专栏C语言及其他语言

【每日一题】问题 1195: 去掉双斜杠注释

题目描述 将C程序代码中的双斜杠注释去掉。 输入 输入数据中含有一些符合C++语法的代码行(每行代码不超过200个字符)。需要说明的是,...

3069
来自专栏C语言及其他语言

【每日一题】问题 1195: 去掉双斜杠注释

输入数据中含有一些符合C++语法的代码行(每行代码不超过200个字符)。需要说明的是,为了方便编程,规定双斜杠注释内容不含有双引号,源程序中没空行。

872
来自专栏C语言及其他语言

【编程经验】变量的存储类型

在 C 语言中,变量是对程序中数据所占内存空间的一种抽象定义,定义变量时,用户定义变量的名、 变量的类型,这些都是变量的操作属性。不仅可以通过变量...

1051
来自专栏章鱼的慢慢技术路

指针的使用

1323
来自专栏大前端_Web

js浮点数加减乘除

版权声明:本文为吴孔云博客原创文章,转载请注明出处并带上链接,谢谢。 https://blog.csdn.net/wkyseo/articl...

1343
来自专栏和蔼的张星的图像处理专栏

684. 缺少的字符串分解到vector中借助find函数

先把两个字符串都分解到vector中,以空格为标志,然后在借助find函数来找出两个vector中不同的单词。

1333
来自专栏黑泽君的专栏

const类型变量的详细解读

const类型变量 -------------------------------------- int i; const int *p; ---------...

2891
来自专栏星回的实验室

js重修课[三]:对象

这一章相对来说是部重头戏,看完之后才发现用了这么久的js,却很少有用真正OO的思想在写js代码……

1264

扫码关注云+社区

领取腾讯云代金券