Dubbo-Dubbo SPI 擴展機制

Dubbo SPI 擴展機制

一、理解

我的理解就是SPI擴展機制給予Dubbo帶來了更多的靈活的擴展性,可以自動根據URL中的參數,進行選擇合適的擴展類進行處理,除了這個簡單的Adapter 幫助適配之外,還引入了IOC機制,set and 構造函數;使用URL參數進行選擇合適的擴展爲默認的Adapter,還可以通過自己手動的Adaptive 這個註解進行自定義呢。功能十分的豐富,代碼也不是非常的多,按需加載特定的SPI的擴展,性能不是問題。

spi :可以理解爲,一個接口有多個實現類,我們需要根據某種策略進行選擇具體的實現,Dubbo中主要根據URL 參數進行傳遞,比如DynamicConfigurationFactory 動態配置中心,有Etcd、Nacos、Zookeeper的實現,這裏是根據協議的類型去選擇一個具體的實現

直接根據某個key 去或者擴展

1、根據key 獲取擴展

 DynamicConfigurationFactory factories = ExtensionLoader
                .getExtensionLoader(DynamicConfigurationFactory.class)
                .getExtension(url.getProtocol());

2、根據一個適配類,適配類根據參數進行動態獲取擴展

如下獲取一個適配類
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

然後根據URL的參數,類似根據key獲取擴展。

public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) 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.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0, arg1);
}

org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocolTest#testThriftProtocol

  @Test
    public void testThriftProtocol() throws TException{
        DemoServiceImpl server = new DemoServiceImpl();
        ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
        URL url = URL.valueOf(org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol.NAME + "://127.0.0.1:5341/" + DemoService.Iface.class.getName() + "?version=1.0.0&nativethrift.overload.method=true");
        Exporter<DemoService.Iface> exporter = protocol.export(proxyFactory.getInvoker(server, DemoService.Iface.class, url));
        Invoker<DemoService.Iface> invoker = protocol.refer(DemoService.Iface.class, url);
        DemoService.Iface client = proxyFactory.getProxy(invoker);
        String result = client.sayHello("haha");
        Assertions.assertTrue(server.isCalled());
        Assertions.assertEquals("Hello, haha", result);
        invoker.destroy();
        exporter.unexport();
    }

二、如何學習 Dubbo SPI

1、dubbo sample & 源碼

git clone   https://github.com/apache/dubbo-samples.git

2、斷點跟蹤 & 源碼測試用例

建議:源碼跟蹤畢竟代碼量比較大,充足的時間、充足的經歷、然後加上註釋,不要害怕。

三、Dubbo SPI 關鍵解析

1、@SPI

註解SPI 意思就是說,這個類要被SPI 處理。

2、@Active

Active 可以理解爲激活的意思,getExtension(name)是根據配置文件中定義的key 獲取到唯一的Class,而Active可以理解爲篩選的意思,這麼多的擴展,可以根據分組可values去篩選出所有的擴展,這個是賽選多個的意思,然後在根據getExtension(name)或者所有的擴展的實例。image.png
List getActivateExtension(URL url, String[] values, String group)
image.png

3、@Adaptive

Dubbo SPI之Adaptive詳解

3.1 默認適配類

我的理解是根據參數動態的選擇某種擴展,默認情況下定義的SPI 會生成一個默認的擴展,這個擴展是根據URL中的參數進行選擇的,也可以自己聲明一個擴展哦。@Adaptive 註解只是針對適配類有效,標註到類上標明這個類就是一個適配不使用默認的,其他的就是使用默認的,使用默認的最少要有一個@Adaptive({PROXY_KEY}),註解在方法上,根據參數動態的獲取到適配的SPI的實現類。

 Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();


    /**
     * create proxy.
     *
     * @param invoker
     * @return proxy
     */
    @Adaptive({PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker) throws RpcException;

斷點設置到這裏會看的自動生成的代碼;
org/apache/dubbo/common/extension/ExtensionLoader.java:996

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
public java.lang.Object getProxy(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.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) 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.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0, arg1);
}
public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
if (arg2 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
}

3.2 自定義適配類

自定義的@Adaptive,在Dubbo中就有一個AdaptiveExtensionFactory,他的策略是根據先後順序,找到了就返回,就兩種情況。

**
 * AdaptiveExtensionFactory  依賴注入工廠
 */
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        // 依賴注入 根據第一匹配原則去查找,如果SPI中有 使用 SpiExtensionFactory,如果Spring 容器中有 使用Spring 容器中的!
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

3.3 不適用自定義適配類

還有不使用@Adaptive DynamicConfigurationFactory動態配置中心,接口上沒有定義動態適配,只能根據名稱進行一個個的獲取,不能調用getAdaptiveExtension這個方法,這裏會觸發自動生成適配默認的適配類的,接口上並沒有定義,因此會報錯
org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generate

image.png

 DynamicConfigurationFactory factories = ExtensionLoader
                .getExtensionLoader(DynamicConfigurationFactory.class)
                .getExtension(url.getProtocol());
/**
 * 默認是什麼都沒有實現的
 */
@SPI("nop")
public interface DynamicConfigurationFactory {

    /**
     * 根據SPI動態獲取配置中心
     * @param url
     * @return
     */
    DynamicConfiguration getDynamicConfiguration(URL url);

}

4、@DisableInject

不允許進行依賴注入,依賴注入可以分爲IOC的set 、構造依賴注入,標註這個表明不進行依賴注入處理。
假設協議是Zookeeper,那麼這個ZookeeperTransporter是怎麼注入依賴的?

DynamicConfigurationFactory factories = ExtensionLoader
                .getExtensionLoader(DynamicConfigurationFactory.class)
                .getExtension(url.getProtocol());

org.apache.dubbo.common.extension.ExtensionLoader#createExtension

image.png

4.1 這裏是Adaptive中的注入工廠處理。

org.apache.dubbo.common.extension.ExtensionLoader#injectExtension
image.png

4.2 根據Class 類名稱獲取AdaptiveExtension

/**
 * 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);
            
            //依賴注入必須實現 Adaptive 
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}
public class ZookeeperDynamicConfigurationFactory extends AbstractDynamicConfigurationFactory {

    /**
     * Zookeeper 也是動態生成的擴展
     */
    private ZookeeperTransporter zookeeperTransporter;

    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
        this.zookeeperTransporter = zookeeperTransporter;
    }


    @Override
    protected DynamicConfiguration createDynamicConfiguration(URL url) {
        return new ZookeeperDynamicConfiguration(url, zookeeperTransporter);
    }
}

@SPI("curator")
public interface ZookeeperTransporter {

    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    ZookeeperClient connect(URL url);

}

四、源碼流程解析

一個SPI擴展對應一個ExtensionLoader,所有的ExtensionLoader都有ExtensionFactory依賴注入處理工廠,一個SPI對應一個Adaptive(可能是默認的、或者自定義的)

1、無需Adaptive,直接根據key進行獲取。

DynamicConfigurationFactory factories = ExtensionLoader
                .getExtensionLoader(DynamicConfigurationFactory.class)
                .getExtension(url.getProtocol());

在這裏插入圖片描述

2、獲取Adaptive

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

在這裏插入圖片描述

3、獲取@Active getActivateExtension

比如:Filter過濾的時候需要獲取某種分組情況下的Filter ,這個服務提供者,還是服務消費者提供的呢?因此就有了分組的概念。

List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章