上一篇介绍了 Dubbo SPI 的基本实现,这篇就介绍下 Dubbo SPI 的自适应扩展机制,对应注解 @Adaptive。
@Adaptive 定义如下:
public @interface Adaptive {
/**
* parameter names in URL
*/
String[] value() default {};
}
value 是个字符数组,通过该属性从 URL 中获取扩展名,来决定使用哪个扩展。分为几种情况:
1.设置了 value,且从 URL 中找到了对应的扩展名,则使用该扩展;
2.设置了 value,但从 URL 中找不到扩展名,则使用默认的扩展,即 @SPI 中配置的 value,还是找不到则抛出异常;
3.未设置 value,则根据接口名生成 value,比如接口 YyyInvokerWrapper 生成 value = “yyy.invoker.wrapper”。
定义了 @Adaptive 注解的方法,参数列表中一定要有类型为 URL 的参数。
按照 有无 @Adaptive 注解、注解设置不同 value,建立测试接口:
@SPI(value = "spi")
public interface SpiTest {
String getName();
@Adaptive
int getAge(URL url);
@Adaptive(value = "country")
String getCountry(URL url);
@Adaptive({"province", "city"})
String getAddress(URL url);
}
使用方式:
@Test
public void adaptiveTest() {
SpiTest spiTest = ExtensionLoader.getExtensionLoader(SpiTest.class).getAdaptiveExtension();
System.out.println(spiTest);
}
首先根据接口获取 ExtensionLoader 对象,Dubbo 会缓存 ExtensionLoader 和接口的 Class 对象的映射关系。
接着调用 getAdaptiveExtension() 获取自适应扩展。同样,Dubbo 会缓存自适应扩展对象,但第一次会调用 createAdaptiveExtension() 创建自适应扩展对象。主要逻辑如下:
创建自适应扩展的步骤分为三步:
1.调用 getAdaptiveExtensionClass() 获取自适应扩展类的 Class 对象;
2.调用 Class.newInstance() 反射创建对象;
3.调用 injectExtension 为扩展对象注入依赖。
其中第 3 步是 Dubbo 实现的 IOC 机制,会为对象自动注入依赖,可参看另一篇讲述 Dubbo SPI 机制的文章。
本篇重点关注第 1 步。
首先调用 getExtensionClass() 加载所有扩展类,即 Dubbo SPI 的基础功能。需要注意的是,在加载具体扩展实现类的时候,如果类上有 @Adaptive 注解,会标记为 cachedAdaptiveClass:
如果有 cachedAdaptiveClass,则直接返回;否则自动创建自适应扩展类。
这里分为两步:
1.根据接口信息和 SPI 信息动态生成代理类 Java 代码;
2.调用 Compiler 接口将编译 Java 代码。
Dubbo 通过调用 AdaptiveClassCodeGenerator#generate() 生成 Java 代码:
生成的 Java 代码主要有:package、import、class 声明、遍历生成方法信息等。
这里要注意的是,通过自适应扩展机制生成的类名,都以 “$Adaptive” 结尾:
String CODE_CLASS_DECLARATION = "public class %s$Adaptive implements %s {\n";
具体的逻辑就不展开,读者可自行了解。SpiTest 类生成的 Java 代码示例如下:
动态生成的代理类,获取扩展实现类时,根据设置 @Adaptive 的不同分为以下几种情况:
1.未设置 @Adaptive 注解,不支持调用,会抛出 UnsupportedOperationException 异常;
2.只设置 @Adaptive,未设置 value,根据接口名生成扩展名,即 “spi.test”,默认值为 @SPI 定义的 value;
3.设置 @Adaptive(value=“country”), 根据 “country” 从 URL 中获取扩展名,默认值为 @SPI 定义的 value;
4.设置 @Adaptive({“province”, “city”}),先根据 “city” 从 URL 获取配置 value1,默认值为 @SPI 定义的 value;再根据 “province” 从 URL 获取配置 value2,默认值为 value1。
可以看到,@Adaptive 的 value 值如果设置为数组,则按照倒序从 URL 获取配置,每一个配置都是下一个配置的默认值。
编译代码使用了 Compiler 接口,也是通过自适应扩展机制获取。Dubbo 提供了 Javasist 和 JDK 两种编译器实现。
先根据 ClassName 从类加载器获取类信息,如果获取不到,再将前面生成的 Java 代码编译成字节码。
编译 Java 代码的实现不是本篇重点,读者可自行了解。
Dubbo 通过 @Adaptive 注解定义自适应扩展信息,可定义在类和方法上:如果定义在类上,则直接使用该类获取扩展实现类;如果定义在方法上,则动态生成 Java 代码,并编译加载到类加载器,内部根据 value 的不同,从 URL 获取配置信息,决定使用哪个扩展实现类。