Dubbo SPI初探

JAVA SPI

java中提供了一种服务发现的机制,叫做spi(Service Provider Interface),其约定大致是:

1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个不带参数的构造方法;

这一块不熟悉的可以搜一下 ServiceLoder 相关的使用,网上有很多例子(jdbc,sl4j等都运用了该机制实现了其特性)。

Dubbo @SPI

在dubbo框架中有一个 @SPI (dubbo提供的一种服务发现机制)标签,这个标签是一个神奇的存在。dubbo提供的自有spi功能并不是基于原生java提供的spi相关的api实现的,而是自起一套;个人觉得比原生的spi更灵活和丰富。

通常用法是:被它标记的接口可以通过一个dubbo提供的ExtensionLoader的相关api动态生成一个自适应类(我们可以暂称之为代理类),这个类主要功能就是根据 URL url 参数里特有的属性去执行真正目标类的对应方法;

当然也可以通过名称直接找到配置文件里的真实类,然后直接使用该真实类。

ExtensionLoader

dubbo的spi特性主要是 ExtensionLoader 为入口,提供了以下api

这些方法中只有

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)

这个方法是静态方法且返回值是ExtensionLoader对象,参数是一个Class对象,表示我们要获得哪一种类型的Loader;所以我们要使用它的api,就需要先使用该方法获得对应的实例(这是使用ExtensionLoader的一个技巧);

这个方法主要功能是返回一个 ExtensionLoader 对象,供后续调用。下面大概讲一下其他api的功能

public T getExtension(String name)

这个方法功能是通过配置文件配置的 K(name) --- V(T) 配置,实例化key对应的类并返回,加载配置文件的方法是这个方法

// synchronized in getExtensionClasses
    private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if (value != null && (value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }

        Object me = this;
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();

//META-INF/dubbo/internal/
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);

//META-INF/dubbo/
        loadFile(extensionClasses, DUBBO_DIRECTORY);

//META-INF/services/
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

加载的顺序分别是:

META-INF/dubbo/internal/
META-INF/dubbo/
META-INF/services/

spi配置文件

我们看看这些配置文件长什么样子(我们以com.alibaba.dubbo.remoting.Transporter这个SPI接口为例子):

1、文件名字是接口名

2、内容格式 name=类全限定名

这样我们就可以通过以下api获得 NettyTransporter 实例了:

    public static void main(String[] args) throws Exception {
        Transporter t = ExtensionLoader.getExtensionLoader(Transporter.class).getExtension("netty");
        System.out.println(t);
    }

打印结果

 

Compiler

上面我们讲了通过名字找到对应的实现类,但是实际框架中通过代理类去执行目标类的使用方式也是非常多的,这个主要通过生成一个叫自适应(Adaptive)类来完成;

在生成消费端配置bean的类

com.alibaba.dubbo.config.ReferenceConfig

就用了

    private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();

    private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

生成自适应类,主要实现是通过动态拼接成一个完整类的代码,然后调用编译模块对代码进行编译生成对应的实例;不得不说这个操作很别处心裁。

我们来跟一下

public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {

//创建自适应类
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }
        return (T) instance;
    }

一直跟到  private Class<?> createAdaptiveExtensionClass() 方法

    private Class<?> createAdaptiveExtensionClass() {

//动态生成代码
        String code = createAdaptiveExtensionClassCode();
        System.out.println("code>>>" + code);

//获得classloader
        ClassLoader classLoader = findClassLoader();

//获得编译器(这里也是通过SPI机制获得的)
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

//自适应类编译完成,返回Class对象
        return compiler.compile(code, classLoader);
    }

自适应类源码动态生成

private String createAdaptiveExtensionClassCode()

能生成动态类的接口必须是被@SPI标记,然后生成的方法个数是根据接口里被 @Adaptive 标记的那些方法;

我们看一下 com.alibaba.dubbo.remoting.Transporter 生成的自适应类的代码(通过debug可以获得)

package com.alibaba.dubbo.demo.provider;

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

public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter {
    public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }

    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.bind(arg0, arg1);
    }
}

可以看出自适应类实现了接口,关键地方就是每个方法最后都是通过

public T getExtension(String name)

这个方法找到真实目标类,来做处理。

编译

调用 com.alibaba.dubbo.common.compiler.Compiler 实例编译生成Class对象。

Compiler 接口最底层实现主要是两个

JdkCompiler 和 JavassistCompiler,而 AbstractCompiler是封装了一些公共方法,AdaptiveCompiler是一个自适应类。

有兴趣的可以看看Javasist和jdkcompiler的实现,这种思路很特别,也许以后我们开发过程中也能使用到;

有一点需要注意,dubbo生成的自适应类有一点就是方法执行的时候需要依赖URL类,在生成自适应类源码的时候会循环遍历的去找该类型,这个是强制依赖的;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值