@TOC
@Adaptive是什么?
- 从Dubbo官方文档可知,每个接口可以作为一个扩展点,相应的扩展类一共包含四种特性:自动包装(Wrapper)、自动加载(SPI)、自适应(Adaptive)和自动激活(Activate)。
- @Adaptive注解可以实现扩展类的自适应属性。可以标记在类、接口、枚举类和方法上。
- @Adaptive注解标注在接口时,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过javassist 或 jdk 编译这段代码,得到代理 Class 类。最后再通过反射创建代理实例。
- @Adaptive注解标注在类上不会生成代理类。被标注的扩展类将直接作为默认实现类来调用方法。在Dubbo框架中类级别的仅有
AdaptiveExtensionFactory和AdaptiveCompile两个类。 - 大部分情况下,@Adaptive 是注解在接口方法上的,真实的实现方法会在动态生成的代理类中进行抉择,接下来主要讲解方法级别的注解。
@Adaptive源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}
@Adaptive有什么用?
举例:一个接口有三个方法,分别是methodA,methodB,methodC。此接口有三个实现类impl1,impl2,impl3。接口通过@SPI注解指定默认实现为impl1,通过@Adaptive注解及URL参数生成一个动态类,可以完成以下动作。
- 接口能将每个方法的实现都对应不同实现类。例如接口可以的
methodA由impl1执行,methodB由impl2执行,methodC由impl3执行。 - 接口能让方法按一定优先级选择实现类来执行。例如
methodA方法上有注解@Adaptive({"key1","key2","key3"})先尝试查找参数URL中key1对应的实现类,未指定则取key2,还未指定则key3,再没指定则使用SPI注解规定的默认实现类去执行方法。
@Adaptive的使用示例
接下来通过示例能更直观地看到@Adaptive的作用
示例项目结构
示例项目代码
com.example.demp.service.SimpleExt
impl1=com.example.demo.impl.SimpleExtImpl1
impl2=com.example.demo.impl.SimpleExtImpl2
impl3=com.example.demo.impl.SimpleExtImpl3
SimpleExt.java
package com.example.demo.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI("impl1")
public interface SimpleExt {
String echo(URL url,String s);
@Adaptive({"key4"})
void printA(URL url);
@Adaptive
void printB(URL url);
@Adaptive({"key3","key2","key1"})
void printC(URL url);
}
SimpleExtImpl1.java
package com.example.demo.impl;
import com.example.demo.service.SimpleExt;
import org.apache.dubbo.common.URL;
public class SimpleExtImpl1 implements SimpleExt {
@Override
public String echo(URL url, String s) {
return null;
}
@Override
public void printA(URL url) {
System.out.println("print-A: I'm SimpleExtImpl1");
}
@Override
public void printB(URL url) {
System.out.println("print-B: I'm SimpleExtImpl1");
}
@Override
public void printC(URL url) {
System.out.println("print-C: I'm SimpleExtImpl1");
}
}
SimpleExtImpl2.java
package com.example.demo.impl;
import com.example.demo.service.SimpleExt;
import org.apache.dubbo.common.URL;
public class SimpleExtImpl2 implements SimpleExt {
@Override
public String echo(URL url, String s) {
return null;
}
@Override
public void printA(URL url) {
System.out.println("print-A: I'm SimpleExtImpl2");
}
@Override
public void printB(URL url) {
System.out.println("print-B: I'm SimpleExtImpl2");
}
@Override
public void printC(URL url) {
System.out.println("print-C: I'm SimpleExtImpl2");
}
}
SimpleExtImpl3.java
package com.example.demo.impl;
import com.example.demo.service.SimpleExt;
import org.apache.dubbo.common.URL;
public class SimpleExtImpl3 implements SimpleExt {
@Override
public String echo(URL url, String s) {
return null;
}
@Override
public void printA(URL url) {
System.out.println("print-A: I'm SimpleExtImpl3");
}
@Override
public void printB(URL url) {
System.out.println("print-B: I'm SimpleExtImpl3");
}
@Override
public void printC(URL url) {
System.out.println("print-C: I'm SimpleExtImpl3");
}
}
测试结果
先说结论,Adaptive注解的自适应匹配遵循一定顺序。
- 若Adaptive的value字段不为空,则实现类查找顺序为Adaptive注解的value数组 => spi的value
- 若Adaptive的value字段为空,则实现类查找顺序为simple.ext => spi的value
- 若查找不到所需的实现类则会抛出异常
其中simple.ext是接口(扩展点)SimpleExt的转换,将接口名驼峰处分开并转换成小写,然后以"."连接起来
测试代码:
URL url=URL.valueOf("dubbo://0.0.0.0:6666/test?key1=impl1&key3=impl3&simple.ext=impl2");
SimpleExt simpleExt= ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
simpleExt.printA(url);
simpleExt.printB(url);
simpleExt.printC(url);
url中的参数指定了以下内容
- key1 对应impl1指代的实现类
- key3 对应impl3指代的实现类
- simple.ext 对应impl2指代的实现类
运行输出:
printA方法先尝试从url查找key4的value,查找不到,因此使用SPI注解中指定的impl1作为实现类printB方法的Adaptive注解未指定value,因此先查找其驼峰变形名simple.ext,url中存在并对应了impl2的实现类printC方法先尝试查找key3的value,发现url中存在,因此将impl3作为实现此方法的实现类- 注意:由于扩展点获取的是自适应实现,因此未使用@Adaptive标识的方法不应调用,调用则会抛出异常(详情可见下方动态类源码)
@Adaptive实现原理
添加了@Adaptive的接口在运行时会动态生成一个类。该类实现了一些通用逻辑,并且包含了如何选择实现类的逻辑,此类以XXX&Adaptive命名,如SimpleExt&Adaptive。
- 对于未被
@Adaptive标注的方法,代理方法只会生成一句抛出异常的语句。 - 对于有
@Adaptive标注的方法,代理方法会从URL参数中提取扩展类名称,然后对扩展类顺序匹配来决定真实调用的实现类。
如下为上述接口SimpleExt.java的自动生成的动态类代码。
url.getParameter(key,default_value)用来查找URL中参数key对应的value,若无则返回default_value
package com.example.demo.service;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class SimpleExt$Adaptive implements com.example.demp.service.SimpleExt {
public java.lang.String echo(org.apache.dubbo.common.URL arg0, String arg1) {
throw new UnsupportedOperationException("The method public abstract void com.example.demo.service.SimpleExt.bang(org.apache.dubbo.common.URL,java.lang.String) of interface com.example.demo.service.SimpleExt is not adaptive method!");
}
public java.lang.String printA(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("key4", "impl1");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key4])");
org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
return extension.printA(arg0);
}
public java.lang.String printB(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("simple.ext", "impl1");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])");
org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
return extension.printB(arg0);
}
public java.lang.String printC(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("key3", url.getParameter("key2",url.getParameter("key1","impl1")));
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key3,key2,key1])");
org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
return extension.printC(arg0);
}
}
整个示例程序运行情况如下图:
关于源码部分在下一篇dubbo SPI @Adaptive源码解读
如果有疑问,欢迎评论~ 如果成功解决了你的问题,点个赞再走~