dubbo源碼解析(四十五)服務引用過程

dubbo服務引用過程

目標:從源碼的角度分析服務引用過程。

前言

前面服務暴露過程的文章講解到,服務引用有兩種方式,一種就是直連,也就是直接指定服務的地址來進行引用,這種方式更多的時候被用來作服務測試,不建議在生產環境使用這樣的方法,由於直連不適合服務治理,dubbo自己就是一個服務治理的框架,提供了不少服務治理的功能。因此更多的時候,咱們都不會選擇繞過註冊中心,而是經過註冊中心的方式來進行服務引用。html

服務引用過程

dubbo-refer

大體能夠分爲三個步驟:java

  1. 配置加載
  2. 建立invoker
  3. 建立服務接口代理類

引用起點

dubbo服務的引用起點就相似於bean加載。dubbo中有一個類ReferenceBean,它實現了FactoryBean接口,繼承了ReferenceConfig,因此ReferenceBean做爲dubbo中能生產對象的工廠Bean,而咱們要引用服務,也就是要有一個該服務的對象。spring

服務引用被觸發有兩個時機:apache

  • Spring 容器調用 ReferenceBean 的 afterPropertiesSet 方法時引用服務(餓漢式)
  • 在 ReferenceBean 對應的服務被注入到其餘類中時引用(懶漢式)

默認狀況下,Dubbo 使用懶漢式引用服務。若是須要使用餓漢式,可經過配置 <dubbo:reference> 的 init 屬性開啓。segmentfault

由於ReferenceBean實現了FactoryBean接口的getObject()方法,因此在加載bean的時候,會調用ReferenceBean的getObject()方法數組

ReferenceBean的getObject()
public Object getObject() {
    return get();
}

這個get方法是ReferenceConfig的get()方法緩存

ReferenceConfig的get()
public synchronized T get() {
    // 檢查而且更新配置
    checkAndUpdateSubConfigs();

    // 若是被銷燬,則拋出異常
    if (destroyed) {
        throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
    }
    // 檢測 代理對象ref 是否爲空,爲空則經過 init 方法建立
    if (ref == null) {
        // 用於處理配置,以及調用 createProxy 生成代理類
        init();
    }
    return ref;
}

關於checkAndUpdateSubConfigs()方法前一篇文章已經講了,我就再也不講述。這裏關注init方法。該方法也是處理各種配置的開始。服務器

配置加載(1)

ReferenceConfig的init()
private void init() {
    // 若是已經初始化過,則結束
    if (initialized) {
        return;
    }
    // 設置初始化標誌爲true
    initialized = true;
    // 本地存根合法性校驗
    checkStubAndLocal(interfaceClass);
    // mock合法性校驗
    checkMock(interfaceClass);
    // 用來存放配置
    Map<String, String> map = new HashMap<String, String>();

    // 存放這是消費者側
    map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);

    // 添加 協議版本、發佈版本,時間戳 等信息到 map 中
    appendRuntimeParameters(map);
    // 若是是泛化調用
    if (!isGeneric()) {
        // 得到版本號
        String revision = Version.getVersion(interfaceClass, version);
        if (revision != null && revision.length() > 0) {
            // 設置版本號
            map.put(Constants.REVISION_KEY, revision);
        }

        // 得到全部方法
        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
        if (methods.length == 0) {
            logger.warn("No method found in service interface " + interfaceClass.getName());
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            // 把全部方法簽名拼接起來放入map
            map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), Constants.COMMA_SEPARATOR));
        }
    }
    // 加入服務接口名稱
    map.put(Constants.INTERFACE_KEY, interfaceName);
    // 添加metrics、application、module、consumer、protocol的全部信息到map
    appendParameters(map, metrics);
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, consumer, Constants.DEFAULT_KEY);
    appendParameters(map, this);
    Map<String, Object> attributes = null;
    if (CollectionUtils.isNotEmpty(methods)) {
        attributes = new HashMap<String, Object>();
        // 遍歷方法配置
        for (MethodConfig methodConfig : methods) {
            // 把方法配置加入map
            appendParameters(map, methodConfig, methodConfig.getName());
            // 生成重試的配置key
            String retryKey = methodConfig.getName() + ".retry";
            // 若是map中已經有該配置,則移除該配置
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                // 若是配置爲false,也就是不重試,則設置重試次數爲0次
                if ("false".equals(retryValue)) {
                    map.put(methodConfig.getName() + ".retries", "0");
                }
            }
            // 設置異步配置
            attributes.put(methodConfig.getName(), convertMethodConfig2AyncInfo(methodConfig));
        }
    }

    // 獲取服務消費者 ip 地址
    String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
    // 若是爲空,則獲取本地ip
    if (StringUtils.isEmpty(hostToRegistry)) {
        hostToRegistry = NetUtils.getLocalHost();
    }
    // 設置消費者ip
    map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

    // 建立代理對象
    ref = createProxy(map);

    // 生產服務key
    String serviceKey = URL.buildKey(interfaceName, group, version);
    // 根據服務名,ReferenceConfig,代理類構建 ConsumerModel,
    // 並將 ConsumerModel 存入到 ApplicationModel 中
    ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
}

該方法大體分爲如下幾個步驟:併發

  1. 檢測本地存根和mock合法性。
  2. 添加協議版本、發佈版本,時間戳、metrics、application、module、consumer、protocol等的全部信息到 map 中
  3. 單獨處理方法配置,設置重試次數配置以及設置該方法對異步配置信息。
  4. 添加消費者ip地址到map
  5. 建立代理對象
  6. 生成ConsumerModel存入到 ApplicationModel 中

在這裏處理配置到邏輯比較清晰。下面就是看ReferenceConfig的createProxy()方法。app

建立invoker

ReferenceConfig的createProxy()
private T createProxy(Map<String, String> map) {
    // 根據配置檢查是否爲本地調用
    if (shouldJvmRefer(map)) {
        // 生成url,protocol使用的是injvm
        URL url = new URL(Constants.LOCAL_PROTOCOL, Constants.LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
        // 利用InjvmProtocol 的 refer 方法生成 InjvmInvoker 實例
        invoker = refprotocol.refer(interfaceClass, url);
        if (logger.isInfoEnabled()) {
            logger.info("Using injvm service " + interfaceClass.getName());
        }
    } else {
        // 若是url不爲空,則用戶可能想進行直連來調用
        if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
            // 當須要配置多個 url 時,可用分號進行分割,這裏會進行切分
            String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
            // 遍歷全部的url
            if (us != null && us.length > 0) {
                for (String u : us) {
                    URL url = URL.valueOf(u);
                    if (StringUtils.isEmpty(url.getPath())) {
                        // 設置接口全限定名爲 url 路徑
                        url = url.setPath(interfaceName);
                    }
                    // 檢測 url 協議是否爲 registry,如果,代表用戶想使用指定的註冊中心
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        // 將 map 轉換爲查詢字符串,並做爲 refer 參數的值添加到 url 中
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    } else {
                        // 合併 url,移除服務提供者的一些配置(這些配置來源於用戶配置的 url 屬性),
                        // 好比線程池相關配置。並保留服務提供者的部分配置,好比版本,group,時間戳等
                        // 最後將合併後的配置設置爲 url 查詢字符串中。
                        urls.add(ClusterUtils.mergeUrl(url, map));
                    }
                }
            }
        } else { // assemble URL from register center's configuration
            // 校驗註冊中心
            checkRegistry();
            // 加載註冊中心的url
            List<URL> us = loadRegistries(false);
            if (CollectionUtils.isNotEmpty(us)) {
                // 遍歷全部的註冊中心
                for (URL u : us) {
                    // 生成監控url
                    URL monitorUrl = loadMonitor(u);
                    if (monitorUrl != null) {
                        // 加入監控中心url的配置
                        map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                    }
                    // 添加 refer 參數到 url 中,並將 url 添加到 urls 中
                    urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                }
            }
            // 若是urls爲空,則拋出異常
            if (urls.isEmpty()) {
                throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
            }
        }

        // 若是隻有一個註冊中心,則直接調用refer方法
        if (urls.size() == 1) {
            // 調用 RegistryProtocol 的 refer 構建 Invoker 實例
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
        } else {
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null;
            // 遍歷全部的註冊中心url
            for (URL url : urls) {
                // 經過 refprotocol 調用 refer 構建 Invoker,
                // refprotocol 會在運行時根據 url 協議頭加載指定的 Protocol 實例,並調用實例的 refer 方法
                // 把生成的Invoker加入到集合中
                invokers.add(refprotocol.refer(interfaceClass, url));
                // 若是是註冊中心的協議
                if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                    // 則設置registryURL
                    registryURL = url; // use last registry url
                }
            }
            // 優先用註冊中心的url
            if (registryURL != null) { // registry url is available
                // use RegistryAwareCluster only when register's cluster is available
                // 只有當註冊中心當連接可用當時候,採用RegistryAwareCluster
                URL u = registryURL.addParameter(Constants.CLUSTER_KEY, RegistryAwareCluster.NAME);
                // The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
                // 由集羣進行多個invoker合併
                invoker = cluster.join(new StaticDirectory(u, invokers));
            } else { // not a registry url, must be direct invoke.
                // 直接進行合併
                invoker = cluster.join(new StaticDirectory(invokers));
            }
        }
    }

    // 若是須要覈對該服務是否可用,而且該服務不可用
    if (shouldCheck() && !invoker.isAvailable()) {
        // make it possible for consumer to retry later if provider is temporarily unavailable
        // 修改初始化標誌爲false
        initialized = false;
        // 拋出異常
        throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
    }
    if (logger.isInfoEnabled()) {
        logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
    }
    /**
     * @since 2.7.0
     * ServiceData Store
     */
    // 元數據中心服務
    MetadataReportService metadataReportService = null;
    // 加載元數據服務,若是成功
    if ((metadataReportService = getMetadataReportService()) != null) {
        // 生成url
        URL consumerURL = new URL(Constants.CONSUMER_PROTOCOL, map.remove(Constants.REGISTER_IP_KEY), 0, map.get(Constants.INTERFACE_KEY), map);
        // 把消費者配置加入到元數據中心中
        metadataReportService.publishConsumer(consumerURL);
    }
    // create service proxy
    // 建立服務代理
    return (T) proxyFactory.getProxy(invoker);
}

該方法的大體邏輯可用分爲如下幾步:

  1. 若是是本地調用,則直接使用InjvmProtocol 的 refer 方法生成 Invoker 實例。
  2. 若是不是本地調用,可是是選擇直連的方式來進行調用,則分割配置的多個url。若是協議是配置是registry,則代表用戶想使用指定的註冊中心,配置url後將url而且保存到urls裏面,不然就合併url,而且保存到urls。
  3. 若是是經過註冊中心來進行調用,則先校驗全部的註冊中心,而後加載註冊中心的url,遍歷每一個url,加入監控中心url配置,最後把每一個url保存到urls。
  4. 針對urls集合的數量,若是是單註冊中心,直接引用RegistryProtocol 的 refer 構建 Invoker 實例,若是是多註冊中心,則對每一個url都生成Invoker,利用集羣進行多個Invoker合併。
  5. 最終輸出一個invoker。

Invoker 是 Dubbo 的核心模型,表明一個可執行體。在服務提供方,Invoker 用於調用服務提供類。在服務消費方,Invoker 用於執行遠程調用。Invoker 是由 Protocol 實現類構建而來。關於這幾個接口的定義介紹能夠參考《dubbo源碼解析(十九)遠程調用——開篇》,Protocol 實現類有不少,下面會分析 RegistryProtocol 和 DubboProtocol,咱們能夠看到上面的源碼中講到,當只有一個註冊中心的時候,會直接使用RegistryProtocol。因此先來看看RegistryProtocol的refer()方法。

RegistryProtocol生成invoker

RegistryProtocol的refer()
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // 取 registry 參數值,並將其設置爲協議頭,默認是dubbo
    url = URLBuilder.from(url)
            .setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
            .removeParameter(REGISTRY_KEY)
            .build();
    // 得到註冊中心實例
    Registry registry = registryFactory.getRegistry(url);
    // 若是是註冊中心服務,則返回註冊中心服務的invoker
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // group="a,b" or group="*"
    // 將 url 查詢字符串轉爲 Map
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
    // 得到group值
    String group = qs.get(Constants.GROUP_KEY);
    if (group != null && group.length() > 0) {
        // 若是有多個組,或者組配置爲*,則使用MergeableCluster,並調用 doRefer 繼續執行服務引用邏輯
        if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
            return doRefer(getMergeableCluster(), registry, type, url);
        }
    }
    // 只有一個組或者沒有組配置,則直接執行doRefer
    return doRefer(cluster, registry, type, url);
}

上面的邏輯比較簡單,若是是註冊服務中心,則直接建立代理。若是不是,先處理組配置,根據組配置來決定Cluster的實現方式,而後調用doRefer方法。

RegistryProtocol的doRefer()
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    // 建立 RegistryDirectory 實例
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    // 設置註冊中心
    directory.setRegistry(registry);
    // 設置協議
    directory.setProtocol(protocol);
    // all attributes of REFER_KEY
    // 全部屬性放到map中
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    // 生成服務消費者連接
    URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
    // 註冊服務消費者,在 consumers 目錄下新節點
    if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
        directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
        // 註冊服務消費者
        registry.register(directory.getRegisteredConsumerUrl());
    }
    // 建立路由規則鏈
    directory.buildRouterChain(subscribeUrl);
    // 訂閱 providers、configurators、routers 等節點數據
    directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
            PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
    // 一個註冊中心可能有多個服務提供者,所以這裏須要將多個服務提供者合併爲一個,生成一個invoker
    Invoker invoker = cluster.join(directory);
    // 在服務提供者處註冊消費者
    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
    return invoker;
}

該方法大體能夠分爲如下步驟:

  1. 建立一個 RegistryDirectory 實例,而後生成服務者消費者連接。
  2. 向註冊中心進行註冊。
  3. 緊接着訂閱 providers、configurators、routers 等節點下的數據。完成訂閱後,RegistryDirectory 會收到這幾個節點下的子節點信息。
  4. 因爲一個服務可能部署在多臺服務器上,這樣就會在 providers 產生多個節點,這個時候就須要 Cluster 將多個服務節點合併爲一個,並生成一個 Invoker。關於 RegistryDirectory 和 Cluster,能夠看我前面寫的一些文章介紹。

DubboProtocol生成invoker

首先仍是從DubboProtocol的refer()開始。

DubboProtocol的refer()
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
    optimizeSerialization(url);

    // create rpc invoker.
    // 建立一個DubboInvoker實例
    DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
    // 加入到集合中
    invokers.add(invoker);

    return invoker;
}

建立DubboInvoker比較簡單,調用了構造方法,這裏主要講這麼生成ExchangeClient,也就是getClients方法。

DubboProtocol的getClients()

能夠參考《dubbo源碼解析(二十四)遠程調用——dubbo協議》的(三)DubboProtocol中的源碼分析。最新版本基本沒有什麼變化,只是由於加入了配置中心,配置的優先級更加明確了,因此增長了xml配置優先級高於properties配置的代碼邏輯,都比較容易理解。

其中若是是配置的共享,則得到共享客戶端對象,也就是getSharedClient()方法,不然新建客戶端也就是initClient()方法。

DubboProtocol的getSharedClient()

能夠參考《dubbo源碼解析(二十四)遠程調用——dubbo協議》的(三)DubboProtocol中的源碼分析,該方法比較簡單,先訪問緩存,若緩存未命中,則經過 initClient 方法建立新的 ExchangeClient 實例,並將該實例傳給 ReferenceCountExchangeClient 構造方法建立一個帶有引用計數功能的 ExchangeClient 實例。

DubboProtocol的initClient()

能夠參考《dubbo源碼解析(二十四)遠程調用——dubbo協議》的(三)DubboProtocol中的源碼分析,initClient 方法首先獲取用戶配置的客戶端類型,最新版本已經改成默認 netty4。而後設置用戶心跳配置,而後檢測用戶配置的客戶端類型是否存在,不存在則拋出異常。最後根據 lazy 配置決定建立什麼類型的客戶端。這裏的 LazyConnectExchangeClient 代碼並非很複雜,該類會在 request 方法被調用時經過 Exchangers 的 connect 方法建立 ExchangeClient 客戶端。下面咱們分析一下 Exchangers 的 connect 方法。

Exchangers的connect()
public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handler == null) {
        throw new IllegalArgumentException("handler == null");
    }
    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    // 獲取 Exchanger 實例,默認爲 HeaderExchangeClient
    return getExchanger(url).connect(url, handler);
}

getExchanger 會經過 SPI 加載 HeaderExchangeClient 實例,這個方法比較簡單。接下來分析 HeaderExchangeClient 的connect的實現。

HeaderExchangeClient 的connect()
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    // 建立 HeaderExchangeHandler 對象
    // 建立 DecodeHandler 對象
    // 經過 Transporters 構建 Client 實例
    // 建立 HeaderExchangeClient 對象
    return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}

其中HeaderExchangeHandler、DecodeHandler等能夠參考《dubbo源碼解析(九)遠程通訊——Transport層》《dubbo源碼解析(十)遠程通訊——Exchange層》的分析。這裏重點關注Transporters 構建 Client,也就是Transporters的connect方法。

Transporters的connect()

能夠參考《dubbo源碼解析(八)遠程通訊——開篇》的(十)Transporters中源碼分析。其中得到自適應拓展類,該類會在運行時根據客戶端類型加載指定的 Transporter 實現類。若用戶未配置客戶端類型,則默認加載 NettyTransporter,並調用該類的 connect 方法。假設是netty4的實現,則執行如下代碼。

public Client connect(URL url, ChannelHandler listener) throws RemotingException {
    return new NettyClient(url, listener);
}

到這裏爲止,DubboProtocol生成invoker過程也結束了。再回到createProxy方法的最後一句代碼,根據invoker建立服務代理對象。

建立代理

爲服務接口生成代理對象。有了代理對象,便可進行遠程調用。首先來看AbstractProxyFactory 的 getProxy()方法。

AbstractProxyFactory 的 getProxy()

能夠參考《dubbo源碼解析(二十三)遠程調用——Proxy》的(一)AbstractProxyFactory的源碼分析。能夠看到第二個getProxy方法其實就是獲取 interfaces 數組,調用到第三個getProxy方法時,該getProxy是個抽象方法,由子類來實現,咱們仍是默認它的代理實現方式爲Javassist。因此能夠看JavassistProxyFactory的getProxy方法。

JavassistProxyFactory的getProxy()
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    // 生成 Proxy 子類(Proxy 是抽象類)。並調用 Proxy 子類的 newInstance 方法建立 Proxy 實例
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

咱們重點看Proxy的getProxy方法。

/**
 * Get proxy.
 *
 * @param ics interface class array.
 * @return Proxy instance.
 */
public static Proxy getProxy(Class<?>... ics) {
    // 得到Proxy的類加載器來進行生成代理類
    return getProxy(ClassHelper.getClassLoader(Proxy.class), ics);
}

/**
 * Get proxy.
 *
 * @param cl  class loader.
 * @param ics interface class array.
 * @return Proxy instance.
 */
public static Proxy getProxy(ClassLoader cl, Class<?>... ics) {
    if (ics.length > Constants.MAX_PROXY_COUNT) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    StringBuilder sb = new StringBuilder();
    // 遍歷接口列表
    for (int i = 0; i < ics.length; i++) {
        String itf = ics[i].getName();
        // 檢測是不是接口,若是不是,則拋出異常
        if (!ics[i].isInterface()) {
            throw new RuntimeException(itf + " is not a interface.");
        }

        Class<?> tmp = null;
        try {
            // 從新加載接口類
            tmp = Class.forName(itf, false, cl);
        } catch (ClassNotFoundException e) {
        }
        // 檢測接口是否相同,這裏 tmp 有可能爲空,也就是該接口沒法被類加載器加載的。
        if (tmp != ics[i]) {
            throw new IllegalArgumentException(ics[i] + " is not visible from class loader");
        }

        // 拼接接口全限定名,分隔符爲 ;
        sb.append(itf).append(';');
    }

    // use interface class name list as key.
    // 使用拼接後的接口名做爲 key
    String key = sb.toString();

    // get cache by class loader.
    Map<String, Object> cache;
    // 把該類加載器加到本地緩存
    synchronized (ProxyCacheMap) {
        cache = ProxyCacheMap.computeIfAbsent(cl, k -> new HashMap<>());
    }

    Proxy proxy = null;
    synchronized (cache) {
        do {
            // 從緩存中獲取 Reference<Proxy> 實例
            Object value = cache.get(key);
            if (value instanceof Reference<?>) {
                proxy = (Proxy) ((Reference<?>) value).get();
                if (proxy != null) {
                    return proxy;
                }
            }
            // 併發控制,保證只有一個線程能夠進行後續操做
            if (value == PendingGenerationMarker) {
                try {
                    // 其餘線程在此處進行等待
                    cache.wait();
                } catch (InterruptedException e) {
                }
            } else {
                // 放置標誌位到緩存中,並跳出 while 循環進行後續操做
                cache.put(key, PendingGenerationMarker);
                break;
            }
        }
        while (true);
    }

    long id = PROXY_CLASS_COUNTER.getAndIncrement();
    String pkg = null;
    ClassGenerator ccp = null, ccm = null;
    try {
        // 建立 ClassGenerator 對象
        ccp = ClassGenerator.newInstance(cl);

        Set<String> worked = new HashSet<>();
        List<Method> methods = new ArrayList<>();

        for (int i = 0; i < ics.length; i++) {
            // 檢測接口訪問級別是否爲 protected 或 privete
            if (!Modifier.isPublic(ics[i].getModifiers())) {
                // 獲取接口包名
                String npkg = ics[i].getPackage().getName();
                if (pkg == null) {
                    pkg = npkg;
                } else {
                    // 非 public 級別的接口必須在同一個包下,否者拋出異常
                    if (!pkg.equals(npkg)) {
                        throw new IllegalArgumentException("non-public interfaces from different packages");
                    }
                }
            }
            // 添加接口到 ClassGenerator 中
            ccp.addInterface(ics[i]);

            // 遍歷接口方法
            for (Method method : ics[i].getMethods()) {
                // 獲取方法描述,可理解爲方法簽名
                String desc = ReflectUtils.getDesc(method);
                // 若是方法描述字符串已在 worked 中,則忽略。考慮這種狀況,
                // A 接口和 B 接口中包含一個徹底相同的方法
                if (worked.contains(desc)) {
                    continue;
                }
                worked.add(desc);

                int ix = methods.size();
                // 獲取方法返回值類型
                Class<?> rt = method.getReturnType();
                // 獲取參數列表
                Class<?>[] pts = method.getParameterTypes();

                // 生成 Object[] args = new Object[1...N]
                StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
                for (int j = 0; j < pts.length; j++) {
                    // 生成 args[1...N] = ($w)$1...N;
                    code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
                }
                // 生成 InvokerHandler 接口的 invoker 方法調用語句,以下:
                // Object ret = handler.invoke(this, methods[1...N], args);
                code.append(" Object ret = handler.invoke(this, methods[").append(ix).append("], args);");
                // 返回值不爲 void
                if (!Void.TYPE.equals(rt)) {
                    // 生成返回語句,形如 return (java.lang.String) ret;
                    code.append(" return ").append(asArgument(rt, "ret")).append(";");
                }

                methods.add(method);
                // 添加方法名、訪問控制符、參數列表、方法代碼等信息到 ClassGenerator 中
                ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());
            }
        }

        if (pkg == null) {
            pkg = PACKAGE_NAME;
        }

        // create ProxyInstance class.
        // 構建接口代理類名稱:pkg + ".proxy" + id,好比 org.apache.dubbo.proxy0
        String pcn = pkg + ".proxy" + id;
        ccp.setClassName(pcn);
        ccp.addField("public static java.lang.reflect.Method[] methods;");
        // 生成 private java.lang.reflect.InvocationHandler handler;
        ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
        // 爲接口代理類添加帶有 InvocationHandler 參數的構造方法,好比:
        // porxy0(java.lang.reflect.InvocationHandler arg0) {
        //     handler=$1;
        // }
        ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{InvocationHandler.class}, new Class<?>[0], "handler=$1;");
        // 爲接口代理類添加默認構造方法
        ccp.addDefaultConstructor();
        // 生成接口代理類
        Class<?> clazz = ccp.toClass();
        clazz.getField("methods").set(null, methods.toArray(new Method[0]));

        // create Proxy class.
        // 構建 Proxy 子類名稱,好比 Proxy1,Proxy2 等
        String fcn = Proxy.class.getName() + id;
        ccm = ClassGenerator.newInstance(cl);
        ccm.setClassName(fcn);
        ccm.addDefaultConstructor();
        ccm.setSuperClass(Proxy.class);
        // 爲 Proxy 的抽象方法 newInstance 生成實現代碼,形如:
        // public Object newInstance(java.lang.reflect.InvocationHandler h) {
        //     return new org.apache.dubbo.proxy0($1);
        // }
        ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
        Class<?> pc = ccm.toClass();
        // 生成 Proxy 實現類
        proxy = (Proxy) pc.newInstance();
    } catch (RuntimeException e) {
        throw e;
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e);
    } finally {
        // release ClassGenerator
        if (ccp != null) {
            // 釋放資源
            ccp.release();
        }
        if (ccm != null) {
            ccm.release();
        }
        synchronized (cache) {
            if (proxy == null) {
                cache.remove(key);
            } else {
                // 寫緩存
                cache.put(key, new WeakReference<Proxy>(proxy));
            }
            // 喚醒其餘等待線程
            cache.notifyAll();
        }
    }
    return proxy;
}

代碼比較多,大體能夠分爲如下幾步:

  1. 對接口進行校驗,檢查是不是一個接口,是否不能被類加載器加載。
  2. 作併發控制,保證只有一個線程能夠進行後續的代理生成操做。
  3. 建立cpp,用做爲服務接口生成代理類。首先對接口定義以及包信息進行處理。
  4. 對接口的方法進行處理,包括返回類型,參數類型等。最後添加方法名、訪問控制符、參數列表、方法代碼等信息到 ClassGenerator 中。
  5. 建立接口代理類的信息,好比名稱,默認構造方法等。
  6. 生成接口代理類。
  7. 建立ccm,ccm 則是用於爲 org.apache.dubbo.common.bytecode.Proxy 抽象類生成子類,主要是實現 Proxy 類的抽象方法。
  8. 設置名稱、建立構造方法、添加方法
  9. 生成 Proxy 實現類。
  10. 釋放資源
  11. 建立弱引用,寫入緩存,喚醒其餘線程。

到這裏,接口代理類生成後,服務引用也就結束了。

後記

參考官方文檔: https://dubbo.apache.org/zh-c...

該文章講解了dubbo的服務引用過程,下一篇就講解服務方法調用過程。

相關文章
相關標籤/搜索