dubbo spi
JDK SPI
spi全称英文是service provider Interface,翻译成中文也就是服务提供接口,在jdk 1.6开始,就已经提供了SPI.它的使用比较简单。即在项目的类路径下提供一个META/services/xx文件,配置一个文件,文件名为接口的全路径的名称,内容为具体的实现类全路径名。jdk将会使用ServiceLoader.load()方法去解析和加载接口和其中的实现类,按需执行不同的方法。
缺点:
1.无法按需加载。虽然 ServiceLoader 做了延迟载入,使用了LazyIterator,但是基本只能通过遍历全部获取,接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,假如我只需要其中一个,其它的并不需要这就形成了一定的资源消耗浪费
2.不具有IOC的功能,假如我有一个实现类,如何将它注入到我的容器中呢,类之间依赖关系如何完成呢?
3.serviceLoader不是线程安全的,会出现线程安全的问题
dubbo spi
dubbo在原有的spi基础上主要有以下的改变:
-
配置文件采用键值对配置的方式,使用起来更加灵活和简单
-
增强了原本SPI的功能,使得SPI具备ioc和aop的功能
dubbo spi 相关注解
-
@SPI 标记一个spi接口 ,@SPI注解中的value是默认值
-
@Adaptive 注解可以实现扩展类的自适应属性。可以标记在类、接口、枚举类和方法上。
@Adaptive注解标注在接口时,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过javassist 或 jdk 编译这段代码,得到代理 Class 类。最后再通过反射创建代理实例。注意: 当标接口方法上时接口参数列表中需要有URL 参数。
@Adaptive注解标注在类上不会生成代理类。被标注的扩展类将直接作为默认实现类来调用方法。在Dubbo框架中类级别的仅有
AdaptiveExtensionFactory
和AdaptiveCompile
两个类。
spi 优先级:
第一优先级@Adaptive 标注再实现类上,改实现类直接为SPI 的扩展配置。
第二优先级 @Adaptive 标注在接口方法中,且注解中配置了key键@Adaptive({key1}) ,URL路由规则中存在key1 键值对 如:URL.valueOf(“dubbo://0.0.0.0:6666/test?key1=B&simple.echo=B”)
第三优先级 @Adaptive 标注在接口方法中,注解中无key键配置,URL 路由规则中包含接口名称 (驼峰命名转为 . 分隔)键值对URL.valueOf(“dubbo://0.0.0.0:6666/test?simple.echo=B”)
第四优先级 @SPI 中的默认配置 如 @SPI(“A”)
简单实用示例
在META-INF 目录下新建 dubbo.internal 目录。在目录中创建以接口全路径为文件名的文件,文件内容为 key=实现类的全路径名
如下图所示:
代码如下所示:
@SPI
public interface SimpleEcho {
void printA(String s);
//@Adaptive({"key1","key2"})
@Adaptive
void printB(URL url,String s);
}
//@Adaptive
public class SimpleEchoImplA implements SimpleEcho{
@Override
public void printA(String s) {
System.out.println("SimpleEchoImplA.printA "+s);
}
@Override
public void printB(URL url,String s) {
System.out.println("SimpleEchoImplA.printB "+s);
}
}
public class SimpleEchoImplB implements SimpleEcho{
@Override
public void printA(String s) {
System.out.println("SimpleEchoImplB.printA "+s);
}
@Override
public void printB(URL url, String s) {
System.out.println("SimpleEchoImplB.printB "+s);
}
}
public class SimpleEchoImplC implements SimpleEcho{
@Override
public void printA(String s) {
System.out.println("SimpleEchoImplC.printA "+s);
}
@Override
public void printB(URL url, String s) {
System.out.println("SimpleEchoImplC.printA "+s);
}
}
public class TestMain {
public static void main(String[] args) {
SimpleEcho simpleEcho = ExtensionLoader.getExtensionLoader(SimpleEcho.class).getAdaptiveExtension();
//simpleEcho.printB(URL.valueOf("dubbo://0.0.0.0:6666/test?key1=B&simple.echo=A"),"TEST");
simpleEcho.printB(URL.valueOf("dubbo://0.0.0.0:6666/test?simple.echo=B"),"TEST");
simpleEcho.printA("AAAA");
//simpleEcho.printB(URL.valueOf("dubbo://0.0.0.0:6666/test"),"TEST");
}
}
@Adaptive 标注在接口方法上生成的代理类:
生成代理类的源码位于ExtensionLoader.createAdaptiveExtensionClassCode 方法中 。
生成代码后,会默认使用 javassist 技术将代码编译、加载到jvm 中,生成对象实例
在动态生成代理的逻辑中,我们可以看到其中是使用到了 dubbo spi
动态生成的代理类
//代理类
package com.jufeng.learn.other.api.dubbo.spi.demo03;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class SimpleEcho$Adaptive implements com.jufeng.learn.other.api.dubbo.spi.demo03.SimpleEcho {
public void printA(java.lang.String arg0) {
throw new UnsupportedOperationException("method public abstract void com.jufeng.learn.other.api.dubbo.spi.demo03.SimpleEcho.printA(java.lang.String) of interface com.jufeng.learn.other.api.dubbo.spi.demo03.SimpleEcho is not adaptive method!");
}
public void printB(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
String extName = url.getParameter("simple.echo");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.jufeng.learn.other.api.dubbo.spi.demo03.SimpleEcho) name from url(" + url.toString() + ") use keys([simple.echo])");
com.jufeng.learn.other.api.dubbo.spi.demo03.SimpleEcho extension = (com.jufeng.learn.other.api.dubbo.spi.demo03.SimpleEcho)ExtensionLoader.getExtensionLoader(com.jufeng.learn.other.api.dubbo.spi.demo03.SimpleEcho.class).getExtension(extName);extension.printB(arg0, arg1);
}
}