Dubbo SPI (service provider interface)

最近拜读dubbo 的源码,其构架设计是很精彩,基于插件式的构架方式,灵活性可见一般。此外dubbo 框架难得基 服务降级,服务路由,服务发现与注册,服务loadbalance 和 支持多种协议于一身的优秀开源框架,很是值得学习和研究细细读来的。此外整个框架的class 加载是根据统一的URL参数。各个层次结构分明,清晰,可扩展性特别好。读次源码感叹一下!

曾经我再封装一些框架的时候,总是加入一些不必要的依赖,比如封装ribbon,支持多个配置中心的时候,就会发现,会把多个配置中心的依赖都加入进去。开发支持多个校验引擎的时候,心理也是在琢磨着如何更好的切换到不同的校验引擎。Dubbo SPI 提供一种很好的思路。根据统一的URL 配置信息,通过代理类按需加载!

前提:

(1). 理解 jdk SPI 

(2). 理解AVAssist 的产生class 字节码,Dubbo 中默认使用AVAssist,主要用来生成代理类

(3). 反射和动态代理(dubbo 的自适应扩张,就是依靠动态代理)

好了,我们来看看dubbo SPI具体的特性。

(1): dubbo SPI 根据 protocol=com.XX.XXX.dubboProtocol, 的方式加载

(2): dubbo SPI 的IOC 特性,dubboProtocol类的setXXX 方法,会通过反射的方式,将相应的class instance, inject 到 dubboProtocol, 通过setXXX的方法。 后面将会有源码。

(3):dubbo SPI 的AOP 特性, ProtocolFilterWrapper 和 ProtocolListenerWrapper这两个类含有protocol 单构造器,放置在loader的私有属性cachedWrapperClasses。ProtocolFilterWrapper在服务的暴露与引用的过程中,根据key 是provider还是consumer来构建服务提供者和消费者调用过滤链。因此具有AOP 的性质。ProtocolListenerWrapper也是在服务暴露与引用的过程中调用listener链。

首先我们需要理解ExtensionLoader, ExtensionFactory这个类。ExtensionFactory 的实现类有 SpiExtensionFactory, SpringExtensionFactory. 默认会加载AdaptiveExtensionFactory (@Adaptive 的注解)实现类。

(1): SpiExtensionFactory:加载有@SPI 的注解的接口实现类。

(2):SpringExtensionFactory: 加载spring context bean 的beans。

默认的实现类AdaptiveExtensionFactory,依赖 List factories. 当调用 ExtensionFactory 时候,会循环SpiExtensionFactory和SpringExtensionFactory,获得 Class type 的 extension。

@SPI

public interface ExtensionFactory {

    T getExtension(Class type, String name);

}


接下来就是进入到 ExtensionLoader,SpiExtensionFactory 的 getExtension 服务,会调用ExtensionLoader的 getExtensionLoader方法。然后通过调用 getAdaptiveExtension,加载自适应点。好了,我们进入ExtensionLoader, getExtensionLoader 去看看会发生什么。

但是再这之前,我们看看加载类的配置。配置文件的路径如下:

private static final StringSERVICES_DIRECTORY ="META-INF/services/";

private static final StringDUBBO_DIRECTORY ="META-INF/dubbo/";

private static final StringDUBBO_INTERNAL_DIRECTORY =DUBBO_DIRECTORY +"internal/";

ExtensionLoader    会加载所有classpath 下,该目录的实现类。其implement 的接口都必须有@SPI的注解。


里面的每个类的结构都是 “name” = “com.XXX.XXXX.registryProtocol”的方式来实现的。首先我们来看下ExtensionLoader主要逻辑:

@SuppressWarnings("unchecked")

public static ExtensionLoadergetExtensionLoader(Class type) {

if (type ==null)

throw new IllegalArgumentException("Extension type == null");

    if (!type.isInterface()) {

throw new IllegalArgumentException("Extension type(" + type +") is not interface!");

    }

if (!withExtensionAnnotation(type)) {

throw new IllegalArgumentException("Extension type(" + type +

") is not extension, because WITHOUT @" +SPI.class.getSimpleName() +" Annotation!");

    }

ExtensionLoader loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);

    if (loader ==null) {

EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));

        loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);

    }

return loader;

}

先从EXTENSION_LOADERS cache 中取出 type 类型的 ExtensionLoader, 比如protocol。 如果没有就会new 一个ExtensionLoader. 我们来看一下new ExtensionLoader的逻辑。 

private ExtensionLoader(Class type) {

this.type = type;   

 objectFactory = (type == ExtensionFactory.class ?null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

}

如果不是ExtensionFactory类型的,就获取一个自适应扩张类。getAdaptiveExtension()。

那么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;

}

首先也是从cachedAdaptiveInstance 获取, 如果获取不到,通过调用createAdaptiveExtension()。 我们看看createAdaptiveExtension的逻辑。

private T createAdaptiveExtension() {

try {

return injectExtension((T) getAdaptiveExtensionClass().newInstance());

    }catch (Exception e) {

throw new IllegalStateException("Can not create adaptive extension " +type +", cause: " + e.getMessage(), e);

    }

}

首先是通过getAdaptiveExtensionClass,然后实列化,通过injectExtension 来注入,injectExtension就是上面提到的 IOC 特性,会通过setXXX() 方法,注入对象实列。我们先看

private Map>getExtensionClasses() {

Map> classes =cachedClasses.get();

    if (classes ==null) {

synchronized (cachedClasses) {

classes =cachedClasses.get();

            if (classes ==null) {

classes = loadExtensionClasses();

                cachedClasses.set(classes);

            }

}

}

return classes;

}

首先从cachedClasses 缓存中获取class,如果获取不到,就调用loadExtensionClasses();,加载extension classes. 

private Map>loadExtensionClasses() {

final SPI defaultAnnotation =type.getAnnotation(SPI.class);

    if (defaultAnnotation !=null) {

String value = defaultAnnotation.value();

        if ((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];

        }

}

Map> extensionClasses =new HashMap>();

    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);

    loadDirectory(extensionClasses, DUBBO_DIRECTORY);

    loadDirectory(extensionClasses, SERVICES_DIRECTORY);

    return extensionClasses;

}

首先是设置cachedDefaultName 的默认名字, 然后去load 各个目录下的class, key = value 的形式。

Map> extensionClasses =new HashMap>();

loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);

loadDirectory(extensionClasses, DUBBO_DIRECTORY);

loadDirectory(extensionClasses, SERVICES_DIRECTORY);

然后进入loadResource 的方法,我们就明白key value 的形式是如何解析的。最后我们调用loadClass 方法,将加载好的class 放入到对应的缓存中。带有adaptive 注解的放入cachedAdaptiveClass, wrapper的class,放入到 cachedWrapperClasses中去。标记有@Active 的类注入到cachedActivates 缓存中去。

将所有的key  = value 形式加载完毕之后,我们要看看IOC 是如何作用的。

privateTinjectExtension(T instance){

    try {

        if (objectFactory != null) {

            // 遍历目标类的所有方法            for (Method method : instance.getClass().getMethods()) {

                // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public                if (method.getName().startsWith("set")

                    && method.getParameterTypes().length == 1                    && Modifier.isPublic(method.getModifiers())) {

                    // 获取 setter 方法参数类型                    Class pt = method.getParameterTypes()[0];

                    try {

                        // 获取属性名,比如 setName 方法对应属性名 name                        String property = method.getName().length() > 3 ?

                            method.getName().substring(3, 4).toLowerCase() +

                            method.getName().substring(4) : "";

                        // 从 ObjectFactory 中获取依赖对象                        Object object = objectFactory.getExtension(pt, property);

                        if (object != null) {

                            // 通过反射调用 setter 方法设置依赖                            method.invoke(instance, object);

                        }

                    } catch (Exception e) {

                        logger.error("fail to inject via method...");

                    }

                }

            }

        }

    } catch (Exception e) {

        logger.error(e.getMessage(), e);

    }

    return instance;

}

通过 Object object = objectFactory.getExtension(pt, property) 和 method.invoke(instance, object) 将adaptive 的类注入的 带有setXXX方法类中。其中objectFactory就是spiExtensionFactory。 

那么有没有想过到底是如何使用 自适应扩展点的? 你看,上面通过IOC 和 spiExtensionFactory 将所有的key = value 形式的类都加载好了,存储的形式是map class>. 那我们什么时候用哪个接口的实现类呢? 比如说:

package com.alibaba.dubbo.rpc; 下的 protocol。 实现类有


那应该使用哪个实现类呢? 这就是自适应扩展点。首先会产生自适应扩展点的代理,然后 在通过URL 的配置信息,传入name, 获取相应的加载类,这就是key value 的原因。 

代理类是什么样呢?代理类是通过javassit 产生的, 请看下面:

package com.alibaba.dubbo.rpc;

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

public class Protocol$Adaptive 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 com.alibaba.dubbo.rpc.RpcException {

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.RpcException {

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);

}}

String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); 就是从 URL 获取 name, 如果获取不到,就默认使用dubbo,通过 getExtension(extName),就可以获取到 加载类: DubboProtocol

那URL 是啥样子呢:dubbo://172.27.238.135:20880/com.mastercard.api.service.DemoService?anyhost=true&application=dubbo-provider&bind.ip=172.27.238.135&bind.port=20880&default.timeout=5000&dubbo=2.6.2&generic=false&interface=com.mastercard.api.service.DemoService&methods=sayHello&pid=71712&revision=1.0.0&side=provider×tamp=1549356336726&version=1.0.0

其实里面的很多内容很精彩,需要自己慢慢品味。后面具体分析下自适应扩展点。

你可能感兴趣的:(Dubbo SPI (service provider interface))