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("\nimport " + ExtensionLoader.class.getName() + ";");
codeBuidler.append("\npublic 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("\nString extName = ").append(getNameCode).append(";");
// check extName == null?
String s = String.format("\nif(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("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                  type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
code.append(s);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM