Dubbo進階(八)- @Reference或ReferenceConfig.get代理對象如何產生(一):SPI模式中 Wrapper和 SPI 類組裝邏輯

當使用Dubbo時候,使用@Reference 或者 ReferenceConfig.get 時候獲取的一個目標接口,那麼進行調用時候,接口並不是直接調用到了接口的實現(Impl)類。

    public static void main(String[] args) {
        ReferenceConfig<HelloService> reference = new ReferenceConfig<>();
        reference.setApplication(new ApplicationConfig("dubbo-consumer"));
        reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
        reference.setInterface(HelloService.class);
        HelloService service = reference.get();
        String message = service.hello("dubbo I am anla7856");
        System.out.println(message);
    }

當執行完reference.get() 後,返回的 HelloService 是什麼樣子呢?肯定不是Consumer 端產生的 HelloServiceImpl,如果是這樣,那過濾器豈不是沒用了?
下面一步一步看博主分析。

ReferenceConfig 的 createProxy

主要就是在 ReferenceConfigcreateProxy 方法,會產生一個代理對象。
在方法內部檢查完參數之後,則會初始化invoker,如果有指定多個url,則會加入集羣容錯機制,這裏先分析通過Protocol 而獲取 的refer方法而獲取具體invoker對象。

            if (urls.size() == 1) {
            // 當 url有多個
                invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
            } else {
                List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
                URL registryURL = null;
                for (URL url : urls) {
                    invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
                    if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        registryURL = url; // use last registry url
                    }
                }
                if (registryURL != null) { // registry url is available
                    // use RegistryAwareCluster only when register's CLUSTER is available
                    URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
                    // The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
                    invoker = CLUSTER.join(new StaticDirectory(u, invokers));
                } else { // not a registry url, must be direct invoke.
                    invoker = CLUSTER.join(new StaticDirectory(invokers));
                }
            }

而此時,url爲 registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-consumer&dubbo=2.0.2&pid=6501&refer=application%3Ddubbo-consumer%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.anla.rpc.pureapi.service.HelloService%26lazy%3Dfalse%26methods%3Dhello%26pid%3D6501%26register.ip%3D192.168.1.107%26release%3D2.7.2%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1566912707974&registry=zookeeper&release=2.7.2&timestamp=1566912927481
此時 REF_PROTOCOLProtocol$Adaptive ,上一篇文章有講默認的 Adaptive類原理,可以參考:Dubbo進階(七)- Dubbo 中默認的 Adaptive類生成過程及例子
所以此時加載的將是 RegistryProtocol,並且執行它的 refer方法。
下面給出 Protocol$Adaptive 類的refer方法

    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);
    }

RegistryProtocol

上一小節發現,由於url 協議爲 registry,所以當它調用 ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); 將傳入 registry
毫無疑問是返回一個 RegistryProtocol,那麼 RegistryProtocol是怎樣的呢?

ExtensionLoader的 getExtension

首先看看 ExtensionLoadergetExtension 方法,它是如何組裝並返回這個類的呢?

    public T getExtension(String name) {
		// 檢查參數
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        // 嘗試從緩存中獲取 實例
        Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                // 沒有則加鎖創建
                    instance = createExtension(name);
                  // 重新創建回去
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

上面代碼主要有以下邏輯:

  1. 檢查參數
  2. 檢查是否已有緩存
  3. 沒有則調用 createExtension(name) 創建一個對應實例

createExtension

createExtension中,就是完成了具體實例的組裝過程:

    private T createExtension(String name) {
    // 獲取對應的擴展
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
        // 嘗試看是否有緩存起來
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // 注入相關extension註解
            injectExtension(instance);
            // 獲取該SPI 裏面所有的包裝類
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                // 由 SPI 文件中定義順序,遍歷
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

這段代碼邏輯比較有意思,除了前面的檢查參數、讀取緩存外,就是使用到了SPI 文件中的包裝類 cachedWrapperClasses
而Protocol的SPI 文件名爲:org.apache.dubbo.rpc.Protocol

filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol

org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
native-thrift=org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
xmlrpc=org.apache.dubbo.xml.rpc.protocol.xmlrpc.XmlRpcProtocol
registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper

所以wrapperClasses 包裝類集合順序爲ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper
Dubbo 中 包裝類的特性就是擁有一個 SPI 對應類型的 單參數構造方法。
這樣設計的結構很明顯是可以組裝成爲鏈式調用。
經過遍歷組裝後,得到的鏈式結構爲:
ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper->RegistryProtocol
當然上面鏈式結構也不一定準確,但是`RgistryProtocol一定是在最後調用。

開始比較糾結的點是,既然按照順序讀取,爲啥不用List或者其他具有順序性特性的集合呢?
這樣做沒有意義,因爲 classloader 加載的順序也是未知的。應當確保 wrapper class 本身的實現與順序無關。
即保證在 任何一個 Protocol 時候,都會先調用 多個Wrapper 類。
https://github.com/apache/dubbo/issues/4578

RegistryProtocol 的 refer

所以這個refer調用流程,則會按照如下流程調用:

  1. ProtocolFilterWrapperrefer 方法,目的是初始化以及調用Filter
  2. QosProtocolWrapperrefer 方法,目的是初始化QoS 功能,默認是開啓的
  3. ProtocolListenerWrapperrefer 方法,初始化 listener 功能
  4. RegistryProtocolrefer 方法,初始化相關注冊信息,以及註冊監聽器

所以劃重點: classloader加載SPI 文件類中無需性,所以每個類之前都會調用相應的 Wrapper 類,最後纔會調用相關SPI 類。

Protocol 的 SPI 文件中的這三個 Wrapper,當然有不同的功能,但是他們的 refer 方法則都會考慮 是否爲registry 服務。

  1. ProtocolFilterWrapperrefer 方法:
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
    }
  1. QosProtocolWrapperrefer 方法:
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            startQosServer(url);
            return protocol.refer(type, url);
        }
        return protocol.refer(type, url);
    }
  1. RegistryProtocolrefer 方法:
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (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, INVOKER_LISTENER_KEY)));
    }

如果是,則會直接去先初始化 RegistryProtocol,相關注冊中心初始化的且看下回分解。

覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,Dubbo小吃街不迷路:

在這裏插入圖片描述

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