zoukankan      html  css  js  c++  java
  • Dubbo扩展机制(二)Adaptive【URL-动态适配】

    Adaptive是Dubbo的自适应拓展机制。自适应拓展机制是为了解决什么问题?
    解决如何根据运行时参数信息动态选择需要加载的拓展的类
    常用的拓展类:
    interface com.alibaba.dubbo.cache.CacheFactory
    interface com.alibaba.dubbo.common.compiler.Compiler
    interface com.alibaba.dubbo.common.extension.ExtensionFactory
    interface com.alibaba.dubbo.common.serialize.Serialization
    interface com.alibaba.dubbo.common.store.DataStore
    interface com.alibaba.dubbo.common.threadpool.ThreadPool
    interface com.alibaba.dubbo.monitor.MonitorFactory
    interface com.alibaba.dubbo.registry.RegistryFactory
    interface com.alibaba.dubbo.remoting.Codec2
    interface com.alibaba.dubbo.remoting.Dispatcher
    interface com.alibaba.dubbo.remoting.Transporter
    interface com.alibaba.dubbo.remoting.exchange.Exchanger
    interface com.alibaba.dubbo.remoting.telnet.TelnetHandler
    interface com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter
    interface com.alibaba.dubbo.rpc.ExporterListener
    interface com.alibaba.dubbo.rpc.Filter
    interface com.alibaba.dubbo.rpc.InvokerListener
    interface com.alibaba.dubbo.rpc.Protocol
    interface com.alibaba.dubbo.rpc.ProxyFactory
    interface com.alibaba.dubbo.rpc.cluster.Cluster
    interface com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory
    interface com.alibaba.dubbo.rpc.cluster.LoadBalance
    interface com.alibaba.dubbo.rpc.cluster.RouterFactory
    interface com.alibaba.dubbo.validation.Validation
     
    官网定义
    在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。自适应拓展机制的实现逻辑比较复杂,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,整个过程比较复杂。
     
    我自已的理解:Adaptive是对SPI的二次包装Dubbo一般不直接使用SPI这种方式,而是直接使用Adaptive来间接使用SPI这种方式。
     
    一、使用示例
    @SPI("ali")    // 默认的值支付宝支付
    public interface Pay {
        // 接口的方法需要添加这个注解,在测试代码中,参数至少要有一个URL类型的参数
        @Adaptive({"paytype"})    // 付款方式
        void pay(URL url);
    }
    
    public class AliPay implements Pay {
        @Override
        public void pay(URL url) {
            System.out.println("使用支付宝支付");
        }
    }
    
    public class WechatPay implements Pay {
        @Override
        public void pay(URL url) {
            System.out.println("使用微信支付");
        }
    }
    在/dubbo-common/src/main/resources/META-INF/services/com.test.Pay文件下添加内容如下:
    wechat = com.test.WechatPay
    ali = com.test.AliPay

    说明:Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同,相应的处理逻辑也是不同的。

     
    测试
    public static void main(String[] args) {
        ExtensionLoader<Pay> loader = ExtensionLoader.getExtensionLoader(Pay.class);        
        Pay pay = loader.getAdaptiveExtension();
        pay.pay(URL.valueOf("http://localhost:9999/xxx"));                    // 使用支付宝支付
        pay.pay(URL.valueOf("http://localhost:9999/xxx?paytype=wechat"));    // 使用微信支付
    }

    这里也会生成一个代理类 PayService$Adaptive,这种代理类实现AOP功能

    package com.test;
    import com.alibaba.dubbo.common.extension.ExtensionLoader;
    public class PayService$Adaptive implements com.test.PayService {
        public void pay(com.alibaba.dubbo.common.URL arg0) {
            if (arg0 == null) 
                throw new IllegalArgumentException("url == null");
            com.alibaba.dubbo.common.URL url = arg0;
            // 1.从 URL 中获取指定的SPI的扩展名称
            String extName = url.getParameter("paytype", "ali");    // 从URL中获取key为paytype参数的value,如果获取不到,则使用默认@SPI注解上的值
            if(extName == null) 
                throw new IllegalStateException("Fail to get extension(com.test.PayService) name from url(" + url.toString() + ") use keys([0])");
            // 2.通过 SPI 加载具体的实现类
            com.test.PayService extension = (com.test.PayService)ExtensionLoader.getExtensionLoader(com.test.PayService.class)
                                            .getExtension(extName);
            // 3.调用目标方法
            extension.pay(arg0);
        }
    }
     
    二、SPI与Adaptive比较
    如果通SPI方式调用则是,每次都要传参,硬编码方式,不是很方便
    PayService wechatPay = extensionLoader.getExtension("wechatPay");
    wechatPay.pay(20);
    如果是通过Adaptive方式,我们不用关心URL是否动态的,因为生成的代理类在其方法中会自动适配,符合模块接口设计的可插拔原则。
    PayService payService = extensionLoader.getAdaptiveExtension();
    payService.pay(url);
    以ProxyFactory调用为例
    public class ReferenceConfig<T> extends AbstractReferenceConfig {
        // 类在加载阶段这个值为 ProxyFactory$Adaptive类型
        private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
            
        private T createProxy(Map<String, String> map) {    
            // 省略若干代码...        
            return (T) proxyFactory.getProxy(invoker);
        }    
    }
    假设有这样一个 url 参数传入:
    zookeeper://localhost:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=demo-consumer&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
    当执行proxyFactory.getProxy(invoker)方法时,
    即执行
    com.alibaba.dubbo.common.URL url = invoker.getUrl();
    String extName = url.getParameter("proxy", "javassist");        
    ProxyFactory extension = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName);
    1. 通过String extName = url.getParameter("proxy", "javassist"); 从URL参数中获取proxy的值,没有就使用默认的值,即值为javassist;
    2. 通过ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName) 获取到了StubProxyFactoryWrapper这个类
    3. 然后通过StubProxyFactoryWrapper调用目标方法
     
     
    三、源码分析
    直接看生成code部分吧,其它方法的逻辑都比较简单。
    private Class<?> createAdaptiveExtensionClass() {
        // 动态生成适配器类
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = 
            ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }
    ExtensionLoader类中 createAdaptiveExtensionClassCode是一个代码拼装过程,这个方法就占了代理的1/3,代码就不全部贴了。
    codeBuidler.append("package " + type.getPackage().getName() + ";");
    codeBuidler.append("
    import " + ExtensionLoader.class.getName() + ";");
    codeBuidler.append("
    public class " + type.getSimpleName() + "$Adaptive" + " implements " + type.getCanonicalName() + " {");
    //value代表@Adaptive注解中的参数,defaultExtName是@SPI注解参数
    getNameCode = String.format("url.getParameter("%s", "%s")", value[i], defaultExtName); 
    // 例如:
    String getNameCode = String.format("url.getParameter("%s")", "dubbo");    // 结果为url.getParameter("dubbo")
    code.append("
    String extName = ").append(getNameCode).append(";");
    // check extName == null?
    String s = String.format("
    if(extName == null) " +
                             "throw new IllegalStateException("Fail to get extension(%s) name from url(" + url.toString() + ") use keys(%s)");",
                             type.getName(), Arrays.toString(value));
    code.append(s);
    
    s = String.format("
    %s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                      type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
    code.append(s);
  • 相关阅读:
    GDI绘制时钟效果,与系统时间保持同步,基于Winform
    Asp.Net Core API网关Ocelot
    Docker打包 Asp.Net Core应用,在CentOS上运行
    【C#】数据库脚本生成工具(二)
    【C#附源码】数据库文档生成工具支持(Excel+Htm)
    微信小程序初使心得【微信小程序快速入门】
    论:开发者信仰之“天下IT是一家“(Java .NET篇)
    线程池,千万注意,原来很多人都在错用
    .NET跨平台之运行与Linux上的Jexus服务器
    StackExchange.Redis 之 SortedSet 类型示例
  • 原文地址:https://www.cnblogs.com/caoxb/p/13140329.html
Copyright © 2011-2022 走看看