SPI,Service Provider Interface,java中提供的一种使程序可扩展的方式,系统定义好接口规范,供其他服务提供方实现,服务提供方将自己jar包META-INF/services下新建一个以接口全名称定义的文件,里面内容写上自己服务的实现的类名,每一行代表一个实现,服务使用方可以通过ServiceLoader.load加载所有的服务,然后判断可以使用的服务。具体可以参考reference
dubbo也使用了类似Java中的SPI机制,不过服务发现等都是dubbo自己实现的。
dubbo中的ServiceLoader:com.alibaba.dubbo.common.extension.ExtensionLoader
dubbo中的扩展声明配置文件的位置:
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):
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是实现的扩展类的全限定名城,以#开始的都是注释
dubbo为了支持可扩展的SPI机制,有三个相关重要的注解
/**
* 写在可扩展的接口上面,表明该接口是支持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中
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
获取指定扩展接口的adaptive类,可以是动态生成的,也可以使用注解@Adaptivate指定
该方法会生成一个实现了扩展接口的adaptive类,该类实现了接口的所有方法,对于没有Adaptive注解的方法注解抛出UnsupportedOperationException,如果有该注解会从url中找到需要适配到的扩展,然后调用适配到的扩展的对应的方法。比如ProxyFactory接口的getProxy方法,默认会去url中找key为proxy对应的value,如果没有配置proxy的话,默认的value是javassist,然后根据该value加载对应的扩展,默认扩展JavassistProxyFactory,然后调用JavassistProxyFactory#getProxy
下面是动态生成的接口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);
}
}
该方法主要获取两种扩展:
找到的所有扩展是有顺序的,如果用户有配置按照用户配置的先后顺序(默认activate的扩展的排序规则ActivateComparator)
从cachedActivates中查找的主要步骤是:
查找指定name扩展的步骤是:
获取默认的扩展,也是先加载该接口所有的扩展,这个过程中会将SPI注解配置了value——也就是默认的使用的扩展,赋值给cachedDefaultName,然后调用getExtension加载该nam对应的扩展
根据给定的name来获取扩展的class,返回对应的实例对象
根据名称获取扩展
dubbo按照java中的SPI机制来保证扩展性,按照约定将对应的配置文件放在指定的目录下,通过读取配置件的方式来获取接口的扩展实现,可以使用动态编译的方法可以动态获取扩展的适配类。将所有实例化的类缓存起来可以保证单例而且加快速度,同时dubbo有简单的属性装配功能,在实例化扩展的时候会将属性也为扩展接口的字段进行注入。