dubbo源码—SPI

Java中的SPI

SPI,Service Provider Interface,java中提供的一种使程序可扩展的方式,系统定义好接口规范,供其余服务提供方实现,服务提供方将本身jar包META-INF/services下新建一个以接口全名称定义的文件,里面内容写上本身服务的实现的类名,每一行表明一个实现,服务使用方能够经过ServiceLoader.load加载全部的服务,而后判断能够使用的服务。具体能够参考referencejava

dubbo中的SPI

dubbo也使用了相似Java中的SPI机制,不过服务发现等都是dubbo本身实现的。缓存

dubbo中的ServiceLoader:com.alibaba.dubbo.common.extension.ExtensionLoaderapp

dubbo中的扩展声明配置文件的位置:ide

private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

dubbo中的扩展声明配置文件的内容(好比com.alibaba.dubbo.rpc.Protocol):url

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol

文件里面都是name=value的格式,name就是该扩展的名称,value是实现的扩展类的全限定名城,以#开始的都是注释.net

注解

dubbo为了支持可扩展的SPI机制,有三个相关重要的注解code

/** 
 * 写在可扩展的接口上面,代表该接口是支持SPI扩展的,有一个属性value表示默认的扩展类名称(就是配置文件中的name)
 * 好比:Protocol上的@SPI("dubbo"),说明默认的Protocol的扩展是name为dubbo的扩展,
 * 查找配置文件META_INFO/dubbo.internal/com.alibaba.dubbo.rpc.Protocol
 * 对应的扩展类是com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
 */
com.alibaba.dubbo.common.extension.SPI

/**
 * 写在类上面表示该类是默认的adapter,写在方法上表示该方法须要被adapt,
 * 属性value表示须要被adapt成的扩展的key(在url中配   置的key)
 * 好比com.alibaba.dubbo.common.threadpool.ThreadPool#getExecutor方法上面的注解:@Adaptive({Constants.THREADPOOL_KEY})
 * 表示该方法返回的Executor是url中key是threadpool指定的扩展,若是url中没有配置则使用默认的扩展
 */
com.alibaba.dubbo.common.extension.Adaptive

/**
 * 指定默认激活的扩展,属性group和value用来过滤(判断何时激活)
 * 好比:com.alibaba.dubbo.rpc.filter.ExceptionFilter上的@Activate(group = Constants.PROVIDER)
 * 该filter只有在provider一侧的时候才会被激活,也就是服务提供方的时候才会使用该filter
 */
com.alibaba.dubbo.common.extension.Activate

获取扩展

dubbo获取扩展的方式都是先获取extensionLoader,而后经过loader去加载对应的扩展对象

获取对应的extensionLoader的方法,每一个扩展接口都有本身的extensionLoader实例,获取以后缓存在EXTENSION_LOADERS中blog

com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionLoader排序

获取扩展的方法主要有:

// 获取适配后的扩展
com.alibaba.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension
// 获取active的扩展
com.alibaba.dubbo.common.extension.ExtensionLoader#getActivateExtension
// 获取默认配置的扩展
com.alibaba.dubbo.common.extension.ExtensionLoader#getDefaultExtension
// 根据name(配置文件中的key)获取扩展
com.alibaba.dubbo.common.extension.ExtensionLoader#getExtension

加载扩展的调用路径

com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionClasses
com.alibaba.dubbo.common.extension.ExtensionLoader#loadExtensionClasses
com.alibaba.dubbo.common.extension.ExtensionLoader#loadFile

getAdaptiveExtension

获取指定扩展接口的adaptive类,能够是动态生成的,也能够使用注解@Adaptivate指定

主要步骤
  1. 先尝试从缓存中get,没有再去load
  2. load的时候,先判断是否有SPI注解,若是有注解判断value值配置是否正确(由于value指定的是默认扩展,不容许指定多个默认值),若是正确的话设置字段cachedDefaultName为配置的默认扩展
  3. 而后从指定的三个位置(就是上面配置文件的三个位置)查找并加载全部的扩展
  4. 主要的load工做由loadFile完成
  5. 找到class以后,调用newInstance实例化
  6. 判断该类是否有类字段是其余扩展,若是有则从全部的扩展中查找到对应的扩展并注入到该实例中
  7. 若是第4步没有找到配置了的adaptiveClass,须要动态生成一个adaptiveClass,调用的是createAdaptiveExtensionClassCode方法生成动态代码
  8. 获取classLoader,默认是加载ExtensionLoader的类加载器,用来加载动态编译后的class
  9. 获取com.alibaba.dubbo.common.compiler.Compiler.class的AdaptiveExtension,用来编译动态生成的代码
  10. 返回进过类加载器加载的动态编译后的Class
loadFile的主要过程
  1. 查找classLoader,默认使用ExtensionLoader对应的类加载器
  2. 使用classLoader查找指定目录下全部以扩展接口全限定名命名的配置文件,若是找到了继续,若是没有找到直接返回
  3. 循环加载每一个查找到的配置文件
  4. 针对每一个配置文件,读入每一行配置,忽略空行和注释("#")
  5. 若是类上面有Adaptive注解,设置为cachedAdaptiveClass
  6. 判断该扩展类是否有包含扩展接口类型的构造方法,若是有,将该class加入到cachedWrapperClasses,未来会使用wrapper包裹其余
  7. 若是没有上面这样的构造方法,判断该类是否有Activate注解,若是有则加入cachedActivates
  8. 并将配置的该class添加到extensionClasses
createAdaptiveExtensionClassCode生成动态代码

该方法会生成一个实现了扩展接口的adaptive类,该类实现了接口的全部方法,对于没有Adaptive注解的方法注解抛出UnsupportedOperationException,若是有该注解会从url中找到须要适配到的扩展,而后调用适配到的扩展的对应的方法。好比ProxyFactory接口的getProxy方法,默认会去url中找key为proxy对应的value,若是没有配置proxy的话,默认的value是javassist,而后根据该value加载对应的扩展,默认扩展JavassistProxyFactory,而后调用JavassistProxyFactory#getProxy

  1. 判断给定扩展接口是否有被Adaptive注解的方法,若是没有就报错,若是有则继续
  2. 动态拼接包名、import等代码
  3. 循环每个方法进行进行适配adapt
  4. 若是没有Adaptive注解,则在适配方法内部抛出UnsupportedOperationException
  5. 若是有Adaptive注解,主要找到须要使用哪个扩展来适配,下面的代码主要在找这个扩展的key
  6. 先判断该方法的入参有没有URL类型的参数,若是有则使该url,
  7. 若是没有URL类型的参数,判断该方法的参数有没有get方法的返回值是URL,若是没有则报错
  8. 若是有返回值是URL类型入参,作一些NPE的校验,使用get方法获取url
  9. 上面已经获取到url,接下来是从url中获取配置该类扩展的名称(配置文件中name=value中的name),须要先知道url中对应的key
  10. 若是adaptive注解上配置了value属性则直接使用做为key
  11. 若是没有配置,则使用扩展点接口名的点分隔做为key
  12. 找到key以后就是在url中找到key对应的value,分如下几种状况:
    • key是protocol,若是有默认扩展名称配置cachedDefaultName而且url.getProtocol()为空则使用cachedDefaultName,不然直接经过url.getProtocol()获取对应的扩展的name
    • key不是protocol,若是参数中没有Invocation则直接冲url中获取;若是有Invocation须要获取方法级的配置(由于好比loadbalance,若是方法级有单独的配置,须要按照方法级的配置获取扩展)
    • 注意:只有一个配置的时候才会考虑默认配置cachedDefaultName,若是有多个配置,以最后一个获取到的配置为准
  13. 取到扩展的name以后,调用ExtensionLoader#getExtension获取扩展
  14. 调用扩展的对应方法

下面是动态生成的接口com.alibaba.dubbo.rpc.ProxyFactory的适配类

import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory {
    public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common
        .URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null) throw new IllegalStateException(
                "Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() +
                ") use keys([proxy])");
        com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(
            com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getInvoker(arg0, arg1, arg2);
    }
    public java.lang.Object getProxy(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.getParameter("proxy", "javassist");
        if (extName == null) throw new IllegalStateException(
                "Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() +
                ") use keys([proxy])");
        com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(
            com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0);
    }
}

getActivateExtension

该方法主要获取两种扩展:

  1. 符合条件的配置了@Activate注解的扩展,从cachedActivates中查找
  2. 指定name的扩展

找到的全部扩展是有顺序的,若是用户有配置按照用户配置的前后顺序(默认activate的扩展的排序规则ActivateComparator)

从cachedActivates中查找的主要步骤是:

  1. getExtensionClasses加载全部的扩展,其中会将类上面有Activate的class加入cachedActivates
  2. 若是配置了"-default",表示不使用默认有Activate注解扩展,若是没有该配置,冲cachedActivates中查找合适的扩展眼,根据如下条件过滤默认activate的扩展,下面条件都符合才会添加到最终的返回值中
    • 根据注解上配置的group是否和指定的一致
    • url中配置不包含该扩展,url中也不包含去除该扩展name的配置,Activate注解配置了value而且url中的配置包含该value
  3. 将从cachedActivates中找到的扩展排序

查找指定name扩展的步骤是:

  1. 配置的name不是用来排除扩展的,而且没有没有排除该扩展的配置
  2. 若是指定的配置中包含默认配置的"default",将default前面的配置扩展放在list的前面,default后面的配置正常放在list的后面,保证按照用户配置的顺序获取对应的扩展

getDefaultExtension

获取默认的扩展,也是先加载该接口全部的扩展,这个过程当中会将SPI注解配置了value——也就是默认的使用的扩展,赋值给cachedDefaultName,而后调用getExtension加载该nam对应的扩展

getExtension

根据给定的name来获取扩展的class,返回对应的实例对象

  1. 对name判空
  2. 若是name是true则getDefaultExtension获取默认扩展
  3. 尝试从缓存中获取holder,若是没有则先添加holder
  4. double check holder中是否有对应的实例对象,没有的话就调用createExtension建立,建立成功以后set到holder中

createExtension

根据名称获取扩展

  1. 加载该接口对应的全部扩展,查找该name对应扩展的class,若是没有该name的扩展则报错
  2. 从缓存中获取class,若是没有则newInstance,并加入缓存
  3. 属性注入:若是该实例对象的字段是扩展接口类型,查找全部扩展中该扩展的并将注入到实例对象中
  4. 使用wrapper包装:在loadFile加载扩展的时候会把符合条件(条件就是扩展类包含扩展接口做为入参的构造方法)的wrapper加入cachedWrapperClasses,将全部的wrapper应用到instance,并调用injectExtension注入wrapper中属性

总结

dubbo按照java中的SPI机制来保证扩展性,按照约定将对应的配置文件放在指定的目录下,经过读取配置件的方式来获取接口的扩展实现,能够使用动态编译的方法能够动态获取扩展的适配类。将全部实例化的类缓存起来能够保证单例并且加快速度,同时dubbo有简单的属性装配功能,在实例化扩展的时候会将属性也为扩展接口的字段进行注入。

相关文章
相关标签/搜索