Dubbo源码 SPI实现ExtensionLoader

更多请移步: 我的博客

Dubbo源码 SPI实现ExtensionLoader

SPI 全称为 (Service Provider Interface) ,JDK也默认提供了SPI的一种实现,不过对比Dubbo的实现,JDK的实现就非常简单。

简单说下JDK默认的SPI用法。
1. 定义Service接口
2. 增加Service实现类
3. META-INF/services目录下建立以接口包全名命名的文件,文件中写入实现类的包名+类名
4. 用Java提供的ServiceLoader来加载实现

目录结构如下图:

工程结构示例

看下如何使用ServiceLoader进行加载

/**
* com.cxd.spi.SayHello文件内容如下:
* com.cxd.spi.impl.TomSayHello
* com.cxd.spi.impl.JerrySayHello
**/
public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<SayHello> sayHellos = ServiceLoader.load(SayHello.class);
        Iterator<SayHello> sayHelloIterator = sayHellos.iterator();
        while (sayHelloIterator.hasNext()) {
            SayHello sayHello = sayHelloIterator.next();
            sayHello.say();
        }
    }
}

ServiceLoader的实现也是比较简单,说下过程,不再列出源码。ServiceLoader根据传入的class去约定的目录下找到相应的文件逐行读取,然后用当前线程的ClassLoader加载文件中定义的实现类,通过class.newInstance()创建实例对象。ServiceLoader通过返回迭代器的方式让我们遍历所有的借口实现。

Dubbo整体思路也是如此,但是Dubbo SPI实现的功能就比较强大了。
SPI通过定义文件实现,但是文件格式并没有局限,Dubbo中使用key=value的形式来进行定义,这也是dubbo能够灵活支持多种协议以及实现良好扩展性的关键所在。

在Dubbo里面使用ExtensionLoader来加载扩展点(即:接口的具体实现),每类接口Dubbo都有默认的实现,当然我们也可以根据自己的业务需要来定义自己的扩展,而扩展的方式也非常简单,SPI的方式给Dubbo提供了框架极好的扩展性。

下面我们主要来看ExtensionLoader,它是Dubbo对SPI实现的核心,也是Dubbo的核心。

  1. 每个定义的SPI的接口都会构建一个ExtensionLoader实例,ExtensionLoader采用工厂模式,以静态方法向外提供ExtensionLoader的实例。实例存储在ExtensionLoader内部静态不可变Map中。

  2. 外部使用时调用:
    ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();getExtensionLoader方法创建ExtensionLoader实例,getAdaptiveExtension方法会加载扩展点中的实现类,并创建或者选择适配器。

    • 读取SPI注解的value值,如果value不为空则作为缺省的扩展点名
    • 依次读取指定路径下的扩展点
      META-INF/dubbo/internal/
      META-INF/dubbo/
      META-INF/dubbo/services/
  3. getAdaptiveExtension方法最终调用loadFile方法逐行读取SPI文件内容并进行解析

    • 实现类上是否含有@Adaptive注解,如果有,则将其作为适配器缓存到cachedAdaptiveClass,并进入下一行配置的解析,一个SPI只能有一个适配器,否则会报错;
    • 如果实现类上没有@Adaptive注解,那么看其是否存在以当前获取接口类型为入参的构造器,如果有,则将其作为包装器(wrapper)存入cachedWrapperClasses变量;
    • 如果实现类既没有@Adaptive注解,也不是包装器,那它就是扩展点的具体实现
    • 判断扩展实现上是否有@Activate注解,如果有,将其缓存到cachedActivates(一个类型为Map<String, Activate>的变量)中,然后将其key作为扩展点的名字,放入cachedClasses(一个类型为Holder<Map<String, Class<?>>>的变量)中,dubbo支持在配置文件中n:1的配置方式,即:不同名的协议使用同一个SPI实现,只要配置名字按照正则\s*[,]+\s*命名即可。
  4. 完成对文件的解析后,getAdaptiveExtension方法开始创建适配器实例。如果cachedAdaptiveClass已经在解析文件中确定,则实例化该对象;如果没有,则创建设配类字节码。

为什么要用适配器?一个接口有多种实现,SPI机制也是如此,这是策略模式。Dubbo采用统一数据模式com.alibaba.dubbo.common.URL(dubbo自定义数据结构),它贯穿系统的整个执行过程,URL中定义的协议类型字段protocol,会根据具体业务设置不同的协议。url.getProtocol()值可以是dubbo、webservice、zookeeper或者redis,当然也可以是其他类型。适配器的作用是根据url.getProtocol()的extName,去选取解析该协议的策略。

Dubbo能够为没有适配器的SPI生成适配器字节码的必要条件:

  • 接口方法中必须至少有一个方法打上了@Adaptive注解
  • 打上了@Adaptive注解的方法参数必须有URL类型参数或者有参数中存在getURL()方法

Dubbo生成的适配器代码如下:

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
        if (arg0 == null) 
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) 
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

Dubbo生成代码后需要对代码进行编译,大家注意,Dubbo中服务皆是SPI,编译器的获取依然需要ExtensionLoader来加载,Dubbo缺省编译器为javassist。Dubbo在加载Compiler时,Compiler的实现类之一AdaptiveCompiler中有@Adaptive的注解,即有已实现的适配器,Dubbo不必为Compiler生成字节码,不然此时就死循环了。

拿到适配器Class后,Dubbo对适配器进行实例化,并且实现了一个类似IOC功能的变量注入。IOC代码非常简单,拿出类中public的set方法,从objectFactory中获得对应的属性值进行设置。

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                 //找出适配器中公开的set方法
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            //从工厂中获取变量的值
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                            //反射调用set方法设置变量值
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

那么objectFactory是个什么东东?ExtensionLoader在初始化的时候都会先通过ExtensionLoader获取一个ExtensionFactory的服务,ExtensionFactory本身除外,不然又是一个死循环。

//它跟Compiler接口一样设配类注解@Adaptive是打在类上,不通过javassist编译生成
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    /**
     * ExtensionFactory默认扩展点
     * spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory
     * adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
     * spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
     * 其中adaptive被标记为@Adaptive,不会出现在factories中
     */
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        //loader.getSupportedExtensions()返回自然排序的name集合,所以顺序是spi->spring
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    public <T> T getExtension(Class<T> type, String name) {
        //因为factories已按照自然顺序排好序,spi在前,所以先从SpiExtensionFactory中查找,如果未找到再去SpringExtensionFactory中找
        for (ExtensionFactory factory : factories) {
            //通过需要注入属性的类型和名字查找
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值