Dubbo源码分析三、SPI之自适应扩展

本文深入分析了Dubbo的自适应扩展机制,解释了如何根据运行时参数动态选择拓展实现。Dubbo通过生成具有代理功能的自适应扩展类,利用URL中的信息在调用时确定具体的实现,避免了提前加载所有拓展。文中详细探讨了自适应扩展类的生成过程,包括代码的编译和加载,并以Transporter接口为例展示了生成逻辑。
摘要由CSDN通过智能技术生成

关于自适应扩展,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得到对应真实的扩展对象。

这样做有什么好处呢?

  1. 我们不需要在Cluster中维护LoadBalance扩展的对应关系。实际上Cluster本身也是一个SPI,如果要维护这个,就要考虑Cluster的初始化时要如何注入LoadBalance的映射关系。

  2. 各层之间完全解耦了,各层之间依赖只是通过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() {
    // 从缓存中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值