关于自适应扩展,dubbo官网中有这么一段话:
在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。自适应拓展机制的实现逻辑比较复杂,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。
这段怎么理解呢?我们考虑这样一种场景:接口A的LoadBalance使用RandomLoadBalance(加权随机负载),接口B的LoadBalance使用RoundRobinLoadBalance(轮询负载)。那么运行时中在上层Cluster使用LoadBalance时,就需要根据当前所需要调用的接口资源选择对应的LoadBalance,而所有的LoadBalance实现都需要在启动时初始化以备使用。也就是Cluster中需要维护一个name->LoadBalance的缓存以备运行时拿到对应的实现。Dubbo是怎么干的呢? 首先URL作为资源描述会被贯穿在整个调用链始终,而URL中包含了运行时每次调用的相关信息。因此,Dubbo通过javassist 或jdk 编译了一组代码中生成的类, 这些类也是原SPI接口的实现,而它的实现方法内就是通过URL找到对应的SPI name值,然后根据name再得到对应的实现。也就是说这些生成的类是对SPI的所有原实现类的代理,根据传入的url参数进行选择相应的实现。
下面是生成的Protocol自适应扩展类的代码:
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
@Override
public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
@Override
public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
@Override
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
}
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
@Override
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) {
throw new IllegalArgumentException("url == null");
}
org.apache.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
@Override
public java.util.List getServers() {
throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
}
回到刚才讨论的那个场景,Cluster中实际上持有的是这个生成的Protocol$Adaptive类,调用对应的export或refer方法时,里面根据url得到对应的extName,然后再根据这个extName得到对应真实的扩展对象。
这样做有什么好处呢?
-
我们不需要在Cluster中维护LoadBalance扩展的对应关系。实际上Cluster本身也是一个SPI,如果要维护这个,就要考虑Cluster的初始化时要如何注入LoadBalance的映射关系。
-
各层之间完全解耦了,各层之间依赖只是通过setter直接注入,而注入的内容通过自适应扩展来创建。
回头想想setter注入那部分,使用的是ExtensionFactory,我们看一下对应的SpiExtensionFactory实现
public class SpiExtensionFactory implements ExtensionFactory { @Override public <T> T getExtension(Class<T> type, String name) { if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type); if (!loader.getSupportedExtensions().isEmpty()) { return loader.getAdaptiveExtension(); } } return null; } }
return loader.getAdaptiveExtension();就是拿的自适应扩展,也就是上面生成的类的实例。
好了,前面大概分析了自适应扩展干嘛用的,下面具体分析代码。
源码分析
首先先了解一个注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}
Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。
注解在类上就说明这个SPI的自适应扩展不是通过dubbo生成的代码,而是已经编写完的代码,getAdaptiveExtension时直接返回这个类的实例即可。
注解在方法上说明这个SPI需要生成自适应扩展类的代码,如何生成的就是下面要详细说明的了
如何获取自适应扩展类
入口方法是ExtensionLoader的getAdaptiveExtension方法
public T getAdaptiveExtension() {
// 从缓存中