老板,救救孩子?关注一下吧!
作者 肥又君
前情回顾
在前面介绍了Dubbo SPI 中 ExtensionLoader 的基础使用和原理,本篇重点介绍 Dubbo SPI 对 @Adaptive 注解的处理。
@Adaptive注解简介
@Adaptive 注解是什么东西?
通过名字就能看出,它是用来标注适配器的。Dubbo SPI能根据配置选择实现类了,那还要适配器干啥呢?
简单点说,就是动态选择实现,META-INF 目录下面的配置毕竟还是静态的,要是能根据参数选择实现,那就爽歪歪了。
@Adaptive 注解可以标注在类上,也可以标注在方法上。这里直接先摆出 @Adaptive 注解的功能:
当 @Adaptive 注解直接标记适配器类上的时候,优先级最高,Dubbo直接使用该类作为接口实现。
当 @Adaptive 注解标记在方法上时,Dubbo 会解析该方法上的URL类型参数,确定该方法使用的具体实现类。
对于2,有几点说明,一个是这里的URL是org.apache.dubbo.common.URL,先把它理解成一个KV就行,【整个Dubbo系列中,只要没有特殊说明,URL都是指的 org.apache.dubbo.common.URL ,千万别和java.net.URL混了,混了的都是傻】。
另一个要想动态选择实现,就必须要有代理,代理类是跑不掉的,既然我们啥都没干,那肯定是 Dubbo 帮我们做了,后面的代码会详细说。
@Adaptive 注解示例
这个示例重点展示上述第2点的效果,涉及一个接口—— Car,两个实现类—— BMW 和 Audi,还有一个 SPI 配置文件,当然还有一个 Main 方法。
首先,我们先定义 Car 接口:
@SPI("BMW") // @SPI注解表示该接口是个扩展点
public interface Car {
// 看到第一个URL参数了吗?@Adaptive会告诉Dubbo SPI
// 生成的代理类,解析这个URL参数,根据解析到的type参数
// 确定start()方法使用哪个实现类。如果没有type参数,
// 则尝试通过model参数确定实现类
@Adaptive({"type", "model"})
void start(URL url, String who);
@Adaptive({"type", "model"})
void stop(URL url, String who);
}
接下来是两个平淡无奇,但是想坐在里面哭的实现类—— BMW 和 Audi,醒醒,看代码吧:
public class BMW implements Car{
@Override
public void start(URL url, String who) {
System.out.println(who+" is starting the BMW ...");
}
@Override
public void stop(URL url, String who) {
System.out.println(who+" is stoping the BMW ...");
}
}
public class Audi implements Car{
@Override
public void start(URL url, String who) {
System.out.println(who+" is starting the Audi ...");
}
@Override
public void stop(URL url, String who) {
System.out.println(who+" is stoping the Audi ...");
}
}
接下来是 org.apache.dubbo.common.extension.test.Car 配置文件,注意哦,这个文件是放到 resources/META-INF/dubbo/internal 目录下的,为什么放到这里?翻翻关注公众号翻翻上一篇对ExtensionLoader的分析吧。
BMW=org.apache.dubbo.common.extension.test.BMW
Audi=org.apache.dubbo.common.extension.test.Audi
最后来看Main 方法:
public static void main(String[] args) throws Exception {
// 这里调用的是getAdaptiveExtension()方法,而不是
// 上一篇介绍的getExtension()方法哦!
Car car =
ExtensionLoader
.getExtensionLoader(Car.class)
.getAdaptiveExtension();
// 注意这个URL,没有type参数也没有model参数,Dubbo会根据Car接口@SPI注解中
// 指定的默认值选择实现类
URL url = URL.valueOf("test://mycar");
car.start(url, "黄宏斯坦森");
// 输出:黄宏斯坦森 is starting the BMW ...
// 优先根据URL中的type参数选择实现
URL startUrl = URL.valueOf("test://mycar?type=BMW&model=Audi");
car.start(startUrl, "肥又君");
// 输出:肥又君 is starting the BMW ...
// 没有type参数根据URL中的model选择实现
URL stopUrl = URL.valueOf("test://mycar?model=Audi");
car.stop(stopUrl, "百里");
// 百里 is stoping the Audi ...
}
@Adaptive 注解实现原理
前面说了,一个Car对象会根据URL参数切换具体的实现主要是因为Dubbo SPI会生成一个Adaptive的代理类。下面就来分析 ExtensionLoader.getAdaptiveExtension() 方法的如何处理@Adaptive注解以及生成Adaptive代理的。
先来看 ExtensionLoader.getAdaptiveExtension() 方法的实现:
// cachedAdaptiveInstance又是一个Holder,用来缓存Adaptive代理类
private final Holder
下面来看 createAdaptiveExtension() 方法的核心流程:
private volatile Class> cachedAdaptiveClass = null;
private void loadClass(Map> extensionClasses,
java.net.URL resourceURL,
Class> clazz, String name) throws NoSuchMethodException {
... ...
// 如果发现一个实现类被@Adaptive注解标记了,则记录到cachedAdaptiveClass字段,
// 这个字段后面有用
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} else
... ...
}
private Class> createAdaptiveExtensionClass() {
// 生成Adaptive代理类,返回的code就是生成的Java代码
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
// 获取ClassLoader
ClassLoader classLoader = findClassLoader();
// 通过SPI方式获取Compiler实现类
org.apache.dubbo.common.compiler.Compiler compiler =
ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class)
.getAdaptiveExtension();
// 编译并加载上面生成的Java代码
return compiler.compile(code, classLoader);
}
简单看看上面示例生成的 Adaptive 代理 —— Car$Adaptive:
public class Car$Adaptive implements org.apache.dubbo.common.extension.test.Car {
public void start(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
// 尝试先尝试获取type,然后尝试获取model,最后再用"BMW"默认值
String extName = url.getParameter("type", url.getParameter("model", "BMW"));
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.test.Car) name from url (" + url.toString() + ") use keys([type, model])");
// 最后还是调用上一篇介绍的getExtension()方法
org.apache.dubbo.common.extension.test.Car extension =
(org.apache.dubbo.common.extension.test.Car) ExtensionLoader
.getExtensionLoader(org.apache.dubbo.common.extension.test.Car.class)
.getExtension(extName);
extension.start(arg0, arg1);
}
public void stop(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
... // 生成的stop()方法与start()方法类似,不再粘贴
}
}
最后,看一下 AdaptiveClassCodeGenerator.generate() 方法是如何生成 Adaptive代理类的:
public String generate() {
StringBuilder code = new StringBuilder();
code.append(generatePackageInfo()); // 生成package部分
code.append(generateImports()); // 生成import部分
code.append(generateClassDeclaration()); // 生成类签名
Method[] methods = type.getMethods(); // 根据SPI接口生成相应的方法代码
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
return code.toString();
}
@Activate注解的事情,下次再说?