Dubbo 的可扩展性是基于 SPI 去实现的,而且Dubbo所有的组件都是通过 SPI 机制加载。
SPI 全称为 (Service Provider Interface) ,是一种服务提供发现机制。可以将服务接口与服务实现分离以达到解耦可拔插、大大提升了程序可扩展性。
一个接口有多个实现类,具体使用哪个实现类,通过SPI机制让用户来决定。
@SPI
:此注解表示这是一个SPI接口,标注在Interface上。
配置文件
在指定目录下创建配置文件,文件名格式为接口的全限定名(此处为com.javaedit.spi.Robot)
指定目录有3个,分别为:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
@SPI注解有value参数,可以配置默认实现类的key
// 接口的注解添加默认值
@SPI("norRobot")
// 获取实现类时将getExtension替换一下
// Robot robot = extensionLoader.getExtension("norRobot");
Robot robot = extensionLoader.getDefaultExtension();
@SPI // 必须添加SPI注解
public interface TestInterface {
public void say();
}
public class TestImpla implements TestInterface {
@Override
public void say() {
System.out.println("aaaaaaa");
}
}
public class TestImplb implements TestInterface {
@Override
public void say() {
System.out.println("bbbbbbbbb");
}
}
配置文件
testaa=example.impl.TestImpla
testbb=example.impl.TestImplb
testcc=example.impl.TestImplc
ExtensionLoader.getExtensionLoader(TestInterface.class).getExtension("testaa").say();
包装类
Dubbo SPI提供了类似装饰器模式的实现
原理就是RobotWrapper
只要有构造方法是有且只有一个参数,且这个参数是Robot
类型,就认为其实包装类,会自动将Robot通过构造方法注入。
所以getExtension("norRobot")
实际返回的是RobotWrapper
如果多个包装类,就按照配置文件里的顺序倒叙执行。
@SPI // 必须添加SPI注解
public interface TestInterface {
public void say();
}
public class TestImpla implements TestInterface {
@Override
public void say() {
System.out.println("aaaaaaa");
}
}
public class TestImplWrappera implements TestInterface {
private TestInterface testInterface;
public TestImplWrappera(TestInterface testInterface) {
this.testInterface = testInterface;
}
@Override
public void say() {
System.out.println("wrappera1");
testInterface.say();
System.out.println("wrappera2");
}
}
public class TestImplWrapperb implements TestInterface {
private TestInterface testInterface;
public TestImplWrapperb(TestInterface testInterface) {
this.testInterface = testInterface;
}
@Override
public void say() {
System.out.println("wrapperb1");
testInterface.say();
System.out.println("wrapperb2");
}
}
配置文件
testaa=example.impl.TestImpla
wrappera=example.impl.TestImplWrappera
wrapperb=example.impl.TestImplWrapperb
ExtensionLoader.getExtensionLoader(TestInterface.class).getExtension("testaa").say();
执行顺序:b1 a1 aaaa a2 b2
自适应扩展
有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这就是Dubbo SPI自适应扩展的作用。
@Adaptive
:此注解用于自适应扩展,可标注在类或者方法上。
1、类的自适应扩展
当@Adaptive
标注在类上时,无需通过key指定需要获取的实现类,通过getAdaptiveExtension方法即可获取自适应扩展类。同一个接口,有且只能有一个实现类允许使用@Adaptive
标注。
@Adaptive
public class TestImplAdaptiveClass1 implements TestInterface {
@Override
public void say() {
System.out.println("adaptive1");
}
}
配置文件
adative1=example.impl.TestImplAdaptiveClass1
ExtensionLoader.getExtensionLoader(TestInterface.class).getAdaptiveExtension().say();
// 不用再指定key
如果配置文件里两个自适应扩展类,只有上面一个会生效
adative1=example.impl.TestImplAdaptiveClass1
adative2=example.impl.TestImplAdaptiveClass2
不会触发包装类-自测是的,需要权威验证!!!
目前 Dubbo 项目里,只有 ExtensionFactory 拓展的实现类 AdaptiveExtensionFactory、AdaptiveCompiler 有这么用.
2、方法的自适应扩展
在接口的方法上加注解,不是实现类。@Adaptive
标注在类上和标注在方法上是冲突的。有类自适应注解,会屏蔽方法自适应。
会触发包装类-自测是的
@SPI
public interface TestInterface {
@Adaptive({"aaa"})
public void say(URL url);
}
public class TestImplAdaptiveMethod1 implements TestInterface {
@Override
public void say(URL url) {
System.out.println("adaptivem1");
}
}
public class TestImplAdaptiveMethod2 implements TestInterface {
@Override
public void say(URL url) {
System.out.println("adaptivem2");
}
}
配置文件
wrappera=example.impl.TestImplWrappera
wrapperb=example.impl.TestImplWrapperb
adativem1=example.impl.TestImplAdaptiveMethod1
adativem2=example.impl.TestImplAdaptiveMethod2
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("aaa", "adativem1");
URL url = new URL("", "", 1, map);
ExtensionLoader.getExtensionLoader(TestInterface.class).getAdaptiveExtension().say(url);
}
输出:
wrappera1
wrapperb1
adaptivem1
wrapperb2
wrappera2
当@Adaptive
标注在方法上时,getAdaptiveExtension获取的是动态生成的自适应扩展类,固定类名是 接口名$Adaptive,
public class Robot$Adaptive implements com.javaedit.spi.Robot {
public void sayHello(com.alibaba.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg0;
// 从url中获取robotAda参数,也就是extName = "norRobot"
String extName = url.getParameter("robotAda");
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.javaedit.spi.Robot) name from url(" + url.toString() + ") use keys([robot])");
// 获取norRobot,也就是RobotImpl类
com.javaedit.spi.Robot extension = (com.javaedit.spi.Robot) ExtensionLoader.getExtensionLoader(com.javaedit.spi.Robot.class).getExtension(extName);
// 调用norRobot的sayHello方法
extension.sayHello(arg0);
}
}
Robot$Adaptive的sayHello会动态从URL参数中获取实际要调用的Robot实现类,这样就实现了根据运行时参数进行加载的功能。
动态选择实现类,是需要通过URL来传递参数的。也就是方法参数中需要包含URL对象或者方法参数中有getUrl()方法来提供URL对象。
可以设置多个键名( Key ),顺序获取直到有值.若最终获取不到,使用默认拓展名。