Dubbo源碼分析系列---擴展點加載


擴展點配置:

約定:

在擴展類的jar包內,放置擴展點配置文件:META-INF/dubbo/接口全限定名,內容為:配置名=擴展實現類全限定名,多個實現類用換行符分隔。(摘自dubbo文檔)

示例:

假如我現在想使用自己定義的協議Myprotocol,在resources目錄下新建META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol目錄文件,文件內容定義:

myprotocol=com.selrain.MyProtocol

實現類內容:

public class MyProtocol implements Protocol {
    @Override
    public int getDefaultPort() {
        return 1111111;
    }
   ...
}

在我們的xml配置protocol屬性時就可以配置myprotocol啦,現在為了測試我們手動獲取下:

@SpringBootApplication
public class Chapter2Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter2Application.class, args);

        Protocol p= ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myprotocol");
        System.out.print(p.getDefaultPort());
    }
}

住:為了少寫點代碼直接上spring boot了

看看自己的控制台輸出吧,是不是調用了我們自定義的類了?

 

擴展點自動包裝:

自動Wrap擴展點的Wrapper類
ExtensionLoader會把加載擴展點時(通過擴展點配置文件中內容),如果該實現有拷貝構造函數,則判定為擴展點Wrapper類。

Wrapper類同樣實現了擴展點接口(摘自dubbo文檔)

什么意思呢?比方說Dubbo里面定義了ProtocolListenerWrapper、ProtocolFilterWrapper2各Wrapper類:

public class ProtocolListenerWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolListenerWrapper(Protocol protocol){
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }
    ...

    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        return new ListenerExporterWrapper<T>(protocol.export(invoker), 
                Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                        .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
    }

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return new ListenerInvokerWrapper<T>(protocol.refer(type, url), 
                Collections.unmodifiableList(
                        ExtensionLoader.getExtensionLoader(InvokerListener.class)
                        .getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
    }
   ...

}

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol){
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }

    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
       ...
        return protocol.export(invoker);
    }

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
       ...
       return protocol.refer(type, url);
    }

    public void destroy() {
        protocol.destroy();
    }

   ...
    
}

2個Wrapper類共同點就是都實現了Protocol接口,構造函數也都時Protocol參數,這樣做的好處是可以通過層層包裝來使各個類的邏輯處理分開;

ProtocolListenerWrapper===》ProtocolFilterWrapper===》DubboProtocol,Protocol的調用過程就是這樣的。

現在來測試一下:

@SpringBootApplication
public class Chapter2Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter2Application.class, args);

        Protocol p= ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myprotocol");
        System.out.print(p.getClass().getSimpleName());
    }
}

控制台輸出了ProtocolListenerWrapper,也就是說當我們調用myprotocol的方法時,會先經過層層包裝,處理,最后才調用myprotocol的方法,當然這個Wrapper我們自己也可以定義,當我們需要額外的做一些邏輯處理的時候,和上面的擴展點配置一樣。

擴展點自動裝配:

加載擴展點時,擴展點實現類的成員如果為其它擴展點類型,ExtensionLoader在會自動注入依賴的擴展點

對應文章底部圖中的injectExtension方法

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        ...
    }

objectFactory 也是基於dubbo的spi擴展機制獲取,它有3個實現類,分別是AdaptiveExtensionFactory、SpiExtensionFactory、SpringExtensionFactory

objectFactory 在我們初始化ExtensionLoader類的時候已經初始化:(因為AdaptiveExtensionFactory打上了Adaptive注解,所以值為AdaptiveExtensionFactory)

private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

AdaptiveExtensionFactory持有所有ExtensionFactory對象的集合,dubbo內部默認實現的對象工廠是SpiExtensionFactory和SpringExtensionFactory,他們經過TreeMap排好序的查找順序是優先先從SpiExtensionFactory獲取,如果返回空在從SpringExtensionFactory獲取。

1) SpiExtensionFactory工廠獲取要被注入的對象,就是要獲取dubbo spi擴展的實現,所以傳入的參數類型必須是接口類型並且接口上打上了@SPI注解,返回的是一個設配類對象。

public class SpiExtensionFactory implements ExtensionFactory {

    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().size() > 0) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

 2)SpringExtensionFactory,Dubbo利用spring的擴展機制跟spring做了很好的融合。在發布或者去引用一個服務的時候,會把spring的容器添加到SpringExtensionFactory工廠集合中去, 當SpiExtensionFactory沒有獲取到對象的時候會遍歷SpringExtensionFactory中的spring容器來獲取要注入的對象

public class SpringExtensionFactory implements ExtensionFactory {
    ...

    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        return null;
    }

擴展點自適應

擴展點的Adaptive實例
ExtensionLoader注入的依賴擴展點是一個Adaptive實例,直到擴展點方法執行時才決定調用是一個擴展點實現。
Dubbo使用URL對象(包含了Key-Value)傳遞配置信息。
擴展點方法調用會有URL參數(或是參數有URL成員)
這樣依賴的擴展點也可以從URL拿到配置信息,所有的擴展點自己定好配置的Key后,配置信息從URL上從最外層傳入。URL在配置傳遞上即是一條總線(摘自Dubbo官方文檔)

比如我們的Protocol接口,由於沒有實現類打上Adaptive注解,所以生成javassist字節碼的方式創建的實現類:

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {


public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
    if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
    if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg1; String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } }

可以看到通過url傳播,在url中拿到最終擴展點名字,最終調用擴展點的export方法

 

內部實現(重要方法):

 

參考:

http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235

 


免責聲明!

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



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