1. 程式人生 > 其它 >Dubbo解析之服務暴露流程(上)

Dubbo解析之服務暴露流程(上)

前面已經講到Dubbo的初始化流程,Dubbo的初始化是隨著Spring容器Bean的例項化而進行的,今天重點看這樣一個節點,它在配置檔案中是這樣的:
<dubbo:service interface="com.viewscenes.netsupervisor.service.InfoUserService" ref="service" />
它會完成Dubbo服務暴露的邏輯,先看下大概流程。

一、開始

上述配置檔案中的節點資訊對應的處理類是ServiceBean。先看下它的結構
public class ServiceBean<T> extends ServiceConfig<T> implements 
            InitializingBean, DisposableBean, ApplicationContextAware,
                    ApplicationListener
<ContextRefreshedEvent>, BeanNameAware {}

可以看到,這個類實現了Spring的不同介面,這就意味著Spring在不同的時機就會呼叫到相應方法。

1、設定上下文

在Spring完成例項化和IOC之後,會呼叫到invokeAwareInterfaces方法,來判斷Bean是否實現了Aware介面,然後呼叫對應的方法。
public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
    SpringExtensionFactory.addApplicationContext(applicationContext);
    
if (applicationContext != null) { SPRING_CONTEXT = applicationContext; try { Method method = applicationContext.getClass().getMethod( "addApplicationListener", new Class<?>[]{ApplicationListener.class}); method.invoke(applicationContext,
new Object[]{this}); supportedApplicationListener = true; } catch (Throwable t) { //省略無關程式碼.... } } }

2、初始化方法

然後我們還看到它實現了InitializingBean介面,那麼初始化方法afterPropertiesSet也是跑不掉呀。在這裡面,就是拿到Dubbo中的應用資訊、註冊資訊、協議資訊等,設定到變數中。最後有個判斷方法值得我們注意isDelay 當方法返回 true 時,表示無需延遲匯出。返回 false 時,表示需要延遲匯出。
public void afterPropertiesSet() throws Exception {
    if (getProvider() == null) {
        //......
    }
    if (getApplication() == null
            && (getProvider() == null || getProvider().getApplication() == null)) {
        //......
    }
    if (getModule() == null
            && (getProvider() == null || getProvider().getModule() == null)) {
        //......
    }
    if ((getRegistries() == null || getRegistries().isEmpty())) {
        //......
    }
    if ((getProtocols() == null || getProtocols().isEmpty())
            && (getProvider() == null || getProvider().getProtocols() == null || 
            getProvider().getProtocols().isEmpty())) {
        //......
    }
    if (getPath() == null || getPath().length() == 0) {
        if (beanName != null && beanName.length() > 0
                && getInterface() != null && getInterface().length() > 0
                && beanName.startsWith(getInterface())) {
            setPath(beanName);
        }
    }
    if (!isDelay()) {
        export();
    }
}

3、開始暴露

這個方法是在Spring 上下文重新整理事件後被呼叫到,它是服務暴露的入口方法。
public void onApplicationEvent(ContextRefreshedEvent event) {
    //是否有延遲暴露 && 是否已暴露 && 是不是已被取消暴露
    if (isDelay() && !isExported() && !isUnexported()) {
        if (logger.isInfoEnabled()) {
            logger.info("The service ready on spring started. service: " + getInterface());
        }
        //暴露服務
        export();
    }
}

二、準備工作

Dubbo在暴露服務之前,要檢查各種配置 ,設定引數資訊,還要補充一些預設的配置項,然後封裝URL物件資訊 。在這裡,我們必須重視URL物件,Dubbo 使用 URL 作為配置載體,所有的拓展點都是通過 URL 獲取配置。

1、檢查配置

export()方法在服務暴露之前,有兩個配置項要檢查。是否暴露服務以及是否延遲暴露服務。
public synchronized void export() {
    // 獲取 export 和 delay 配置
    if (provider != null) {
        if (export == null) {
            export = provider.getExport();
        }
        if (delay == null) {
            delay = provider.getDelay();
        }
    }
    // 如果 export 為 false,則不暴露服務
    if (export != null && !export) {
        return;
    }
    // delay > 0,延時暴露服務
    if (delay != null && delay > 0) {
        delayExportExecutor.schedule(new Runnable() {
            @Override
            public void run() {
                doExport();
            }
        }, delay, TimeUnit.MILLISECONDS);
    }else {
        doExport();
    }
}
很顯然,如果我們不想暴露服務,這樣可以來配置:
<dubbo:provider export="false"/>

同樣的,如果我們想延遲暴露服務,就可以這樣來配置它:

<dubbo:provider delay="100"/>
在Dubbo決定要暴露服務之後,還要做一些事情。

檢查服務介面合法性

  • 檢查Conifg核心配置類等是否為空,為空就從其他配置中獲取相應例項
  • 區分泛化服務和普通服務
  • 檢查各種物件是否為空,建立或丟擲異常等
protected synchronized void doExport() {
    if (unexported) {
        throw new IllegalStateException("Already unexported!");
    }
    if (exported) {
        return;
    }
    exported = true;
    if (interfaceName == null || interfaceName.length() == 0) {
        throw new IllegalStateException("
            <dubbo:service interface=\"\" /> interface not allow null!");
    }
    checkDefault();
    if (provider != null) {
        //檢查配置物件是否為空並賦值
    }
    if (module != null) {
        //檢查配置物件是否為空並賦值
    }
    if (application != null) {
        //檢查配置物件是否為空並賦值
    }
    //區分泛化類和普通類
    if (ref instanceof GenericService) {
        interfaceClass = GenericService.class;
        if (StringUtils.isEmpty(generic)) {
            generic = Boolean.TRUE.toString();
        }
    } else {
        //返回介面Class物件並檢查
        try {
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        checkInterfaceAndMethods(interfaceClass, methods);
        checkRef();
        generic = Boolean.FALSE.toString();
    }
    //檢查各種物件是否為空,建立或丟擲異常
    checkApplication();
    checkRegistry();
    checkProtocol();
    appendProperties(this);
    checkStubAndMock(interfaceClass);
    if (path == null || path.length() == 0) {
        path = interfaceName;
    }
    //服務暴露
    doExportUrls();
    ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
    ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}

2、多協議多註冊中心

2.1、配置

Dubbo 允許我們使用不同的協議暴露服務,也允許我們向多個註冊中心註冊服務。 比如我們想同時使用dubbo、rmi兩種協議來暴露服務:
<dubbo:protocol name="dubbo" port="20880"/>  
<dubbo:protocol name="rmi" port="1099" />
又或者我們需要把服務註冊到zookeeper、redis:
<dubbo:registry address="zookeeper://192.168.139.129:2181"/>
<dubbo:registry address="redis://192.168.139.129:6379"/>
我們這樣配置後,在zookeeper、redis裡面都會儲存基於兩種協議的服務資訊。 首先在redis中我們執行命令:
hgetall /dubbo/com.viewscenes.netsupervisor.service.InfoUserService/providers
得到以下結果:
"rmi://192.168.100.74:1099/com.viewscenes.netsupervisor.service.InfoUserService?anyhost=true&application=dubbo_producer1&dubbo=2.6.2&generic=false&interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserById&pid=7064&side=provider&timestamp=1545804399128"
"1545805385379"
"dubbo://192.168.100.74:20880/com.viewscenes.netsupervisor.service.InfoUserService?anyhost=true&application=dubbo_producer1&dubbo=2.6.2&generic=false&interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserById&pid=7064&side=provider&timestamp=1545804391176"
"1545805385379"

同時,我們在zookeeper中執行命令:

ls /dubbo/com.viewscenes.netsupervisor.service.InfoUserService/providers

得到以下結果:

[dubbo%3A%2F%2F192.168.100.74%3A20880%2Fcom.viewscenes.netsupervisor.service.InfoUserService%3Fanyhost%3Dtrue%26application%3
Ddubbo_producer1%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Dcom.viewscenes.netsupervisor.service.InfoUserService%26methods%3D
getUserById%2CgetAllUser%2CinsertInfoUser%2CdeleteUserById%26pid%3D7064%26side%3Dprovider%26timestamp%3D1545804391176, 

rmi%3A%2F%2F192.168.100.74%3A1099%2Fcom.viewscenes.netsupervisor.service.InfoUserService%3Fanyhost%3Dtrue%26application%3
Ddubbo_producer1%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Dcom.viewscenes.netsupervisor.service.InfoUserService%26methods%3D
getUserById%2CgetAllUser%2CinsertInfoUser%2CdeleteUserById%26pid%3D7064%26side%3Dprovider%26timestamp%3D1545804399128]

2.2、多註冊中心

首先是多個註冊中心的載入,loadRegistries方法返回一個List registryURLs。基於上面的配置檔案,這裡的registries就是一個長度為2的List,最終將它們解析為List registryURLs。
protected List<URL> loadRegistries(boolean provider) {
    //檢查配置
    checkRegistry();
    List<URL> registryList = new ArrayList<URL>();
    if (registries != null && !registries.isEmpty()) {
        for (RegistryConfig config : registries) {
            String address = config.getAddress();
            if (address == null || address.length() == 0) {
                // 若 address 為空,則將其設為 0.0.0.0
                address = Constants.ANYHOST_VALUE;
            }
            // 從系統屬性中載入註冊中心地址
            String sysaddress = System.getProperty("dubbo.registry.address");
            if (sysaddress != null && sysaddress.length() > 0) {
                address = sysaddress;
            }
            //檢查address合法性以及從封裝map配置資訊
            if (address != null && address.length() > 0
                    && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                Map<String, String> map = new HashMap<String, String>();
                appendParameters(map, application);
                appendParameters(map, config);
                map.put("path", RegistryService.class.getName());
                map.put("dubbo", Version.getVersion());
                map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
                if (ConfigUtils.getPid() > 0) {
                    map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
                }
                if (!map.containsKey("protocol")) {
                    if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).
                                                                hasExtension("remote")) {
                        map.put("protocol", "remote");
                    } else {
                        map.put("protocol", "dubbo");
                    }
                }
                //將配置資訊封裝成URL物件
                List<URL> urls = UrlUtils.parseURLs(address, map);
                for (URL url : urls) {
                    url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
                    url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
                    if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
                            || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
                        registryList.add(url);
                    }
                }
            }
        }
    }
    return registryList;
}

2.3 多協議支援

多協議的支援,也是一樣,遍歷陣列迴圈暴露服務。其中,doExportUrlsFor1Protocol方法為服務暴露的具體流程。
private void doExportUrls() {
    //載入註冊中心
    List<URL> registryURLs = loadRegistries(true);
    //遍歷協議,為每個協議暴露一個服務
    for (ProtocolConfig protocolConfig : protocols) {
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

三、服務暴露

經過各種配置檢查後,來到doExportUrlsFor1Protocol方法,它是基於單個協議來暴露服務。方法的前半部分是根據配置資訊組裝URL物件。前面我們說過,對於Dubbo來說,URL物件資訊至關重要,它是Dubbo配置的載體。 關於它的組裝過程,無非就是各種設定屬性,我們不再細看,我們看看封裝好的URL物件資訊是什麼樣的。
dubbo://192.168.100.74:20880/com.viewscenes.netsupervisor.service.InfoUserService?
anyhost=true&application=dubbo_producer1&bind.ip=192.168.100.74&bind.port=20880&dubbo=2.6.2&generic=false&
interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserById&pid=8596&side=provider&timestamp=1545807234666

rmi://192.168.100.74:1099/com.viewscenes.netsupervisor.service.InfoUserService?
anyhost=true&application=dubbo_producer1&bind.ip=192.168.100.74&bind.port=1099&dubbo=2.6.2&generic=false&
interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserById&pid=8596&side=provider&timestamp=1545807267085
有服務暴露之前,我們還可以對暴露方式有所選擇。
  • 本地暴露
  • 遠端暴露
  • 不暴露
它們的配置對應如下:
<dubbo:service scope="local" />
<dubbo:service scope="remote" />
<dubbo:service scope="none" />
預設情況下,Dubbo兩種方式都會暴露。即本地+遠端。
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    
    //省略設定URL物件資訊過程...
    String scope = url.getParameter(Constants.SCOPE_KEY);
    // 如果 scope = none,則不暴露服務
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

        // scope != remote,服務暴露到本地
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
            exportLocal(url);
        }
        // scope != local,服務暴露到遠端
        if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
            if (registryURLs != null && !registryURLs.isEmpty()) {
                //迴圈註冊中心進行暴露服務
                for (URL registryURL : registryURLs) {
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                    URL monitorUrl = loadMonitor(registryURL);
                    if (monitorUrl != null) {
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }
                    if (logger.isInfoEnabled()) {
                        logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                    }
                    
                    //為服務類ref生成invoker
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, 
                            registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                    // DelegateProviderMetaDataInvoker 用於持有 Invoker 和 ServiceConfig
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                    //呼叫對應的協議,暴露服務
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            } else {
                Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                Exporter<?> exporter = protocol.export(wrapperInvoker);
                exporters.add(exporter);
            }
        }
    }
    this.urls.add(url);
}

3.1、建立Invoker

在開始之前,我們必須瞭解另外一個東西:Invoker 。在 Dubbo 中,Invoker 是一個非常重要的模型。在服務提供端,以及服務引用端均會出現 Invoker。 Dubbo官網對它是這樣說明的:
Invoker 是實體域,它是 Dubbo 的核心模型,其它模型都向它靠擾,或轉換成它,它代表一個可執行體,可向它發起 invoke 呼叫,它有可能是一個本地的實現,也可能是一個遠端的實現,也可能一個叢集實現。
在Dubbo中,Invoker通過以下程式碼建立:
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).
                  getAdaptiveExtension();
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
首先通過擴充套件點載入器建立ProxyFactory介面的自適應擴充套件類,在Dubbo中,預設是JavassistProxyFactory,所以當呼叫proxyFactory.getInvoker就會呼叫到JavassistProxyFactory
public class JavassistProxyFactory extends AbstractProxyFactory {

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // 為目標類建立 Wrapper
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().
                    indexOf('$') < 0 ? proxy.getClass() : type);
        // 建立匿名 Invoker 類物件,並實現 doInvoke 方法。
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                // 呼叫 Wrapper 的 invokeMethod 方法,invokeMethod 最終會呼叫目標方法
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }
}
如上方法,它主要完成了兩件事:為目標類建立 Wrapper和實現doInvoke方法。這樣一來,當呼叫AbstractProxyInvoker.doInvoke()方法時,實際則呼叫的是,wrapper的invokeMethod()方法。 那麼,wrapper又是怎麼來的呢?它通過ClassGenerator建立而來。 ClassGenerator是 Dubbo 自己封裝的,該類的核心是 toClass() 的過載方法 toClass(ClassLoader, ProtectionDomain),該方法通過 javassist 構建 Class。建立過程涉及的程式碼較長,我們不再細看,主要看看這個wrapper類生成後的樣子。
package com.alibaba.dubbo.common.bytecode;

import com.viewscenes.netsupervisor.entity.InfoUser;
import com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

public class Wrapper1 extends Wrapper implements ClassGenerator.DC {
    public static String[] pns;
    public static Map pts;
    public static String[] mns;
    public static String[] dmns;
    public static Class[] mts0;
    public static Class[] mts1;
    public static Class[] mts2;
    public static Class[] mts3;
    
    public Class getPropertyType(String paramString) {
        return ((Class) pts.get(paramString));
    }
    public String[] getPropertyNames() {
        return pns;
    }
    public Object invokeMethod(Object paramObject, String paramString, Class[] paramArrayOfClass,
            Object[] paramArrayOfObject) throws InvocationTargetException {
        InfoUserServiceImpl localInfoUserServiceImpl;
        try {
            localInfoUserServiceImpl = (InfoUserServiceImpl) paramObject;
        } catch (Throwable localThrowable1) {
            throw new IllegalArgumentException(localThrowable1);
        }
        try {
            if (("getAllUser".equals(paramString)) && (paramArrayOfClass.length == 0))
                return localInfoUserServiceImpl.getAllUser();
            if (("getUserById".equals(paramString)) && (paramArrayOfClass.length == 1))
                return localInfoUserServiceImpl.getUserById((String) paramArrayOfObject[0]);
            if (("insertInfoUser".equals(paramString)) && (paramArrayOfClass.length == 1)) {
                localInfoUserServiceImpl.insertInfoUser((InfoUser) paramArrayOfObject[0]);
                return null;
            }
            if (("deleteUserById".equals(paramString)) && (paramArrayOfClass.length == 1)) {
                localInfoUserServiceImpl.deleteUserById((String) paramArrayOfObject[0]);
                return null;
            }
        } catch (Throwable localThrowable2) {
            throw new InvocationTargetException(localThrowable2);
        }
        throw new NoSuchMethodException("Not found method \"" + paramString
                + "\" in class com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl.");
    }

    public Object getPropertyValue(Object paramObject, String paramString) {
        InfoUserServiceImpl localInfoUserServiceImpl;
        try {
            localInfoUserServiceImpl = (InfoUserServiceImpl) paramObject;
        } catch (Throwable localThrowable) {
            throw new IllegalArgumentException(localThrowable);
        }
        if (paramString.equals("allUser"))
            return localInfoUserServiceImpl.getAllUser();
        throw new NoSuchPropertyException("Not found property \"" + paramString
                + "\" filed or setter method in class com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl.");
    }

    public void setPropertyValue(Object paramObject1, String paramString, Object paramObject2) {
        try {
            InfoUserServiceImpl localInfoUserServiceImpl = (InfoUserServiceImpl) paramObject1;
        } catch (Throwable localThrowable) {
            throw new IllegalArgumentException(localThrowable);
        }
        throw new NoSuchPropertyException("Not found property \"" + paramString
                + "\" filed or setter method in class com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl.");
    }

    public String[] getMethodNames() {
        return mns;
    }

    public String[] getDeclaredMethodNames() {
        return dmns;
    }

    public boolean hasProperty(String paramString) {
        return pts.containsKey(paramString);
    }
}
我們重點可以關注invokeMethod方法,當呼叫到它的時候,它根據引數直接呼叫的是目標類(ref)的對應方法。 綜上所述,proxyFactory.getInvoker(ref, (Class) interfaceClass, url);這句程式碼的返回值就是JavassistProxyFactory的例項,如下圖示:

3.2、本地暴露

上面我們已經看到,如果scope != remote 就呼叫本地暴露。
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
    exportLocal(url);
}
本地暴露方法比較簡單,首先根據 URL 協議頭決定是否匯出服務。若需匯出,則建立一個新的 URL 並將協議頭、主機名以及埠設定成新的值。然後建立 Invoker,並呼叫 InjvmProtocol 的 export 方法匯出服務。
private void exportLocal(URL url) {
    //判斷協議頭
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
        //設定本地暴露協議URL
        URL local = URL.valueOf(url.toFullString())
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(LOCALHOST)
                .setPort(0);
        ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
        
        //建立Invoker物件
        //根據協議頭,這裡的 protocol 會在執行時呼叫 InjvmProtocol 的 export 方法
        Exporter<?> exporter = protocol.export(
                proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
        exporters.add(exporter);
    }
}
如上程式碼,當呼叫protocol.export的時候,Dubbo SPI 自適應的特性的好處就出來了,可以自動根據 URL 引數,獲得對應的拓展實現。比如這裡是本地暴露,那麼它就會呼叫到InjvmProtocol。InjvmProtocol的 export 方法僅建立了一個 InjvmExporter,無其他邏輯。至此,本地暴露完畢。
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

3.3、遠端暴露

遠端暴露也一樣,如果scope != local 則呼叫方法。值得注意的是,它的方法是在註冊中心列表迴圈裡面呼叫的。
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
    if (registryURLs != null && !registryURLs.isEmpty()) {
        //迴圈註冊中心
        for (URL registryURL : registryURLs) {
            //省略無關程式碼...         
            //先獲取invoker
            Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, 
                    registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
            //將invoker和this物件封裝成DelegateProviderMetaDataInvoker
            DelegateProviderMetaDataInvoker wrapperInvoker = new 
                                                DelegateProviderMetaDataInvoker(invoker, this);
            //服務暴露
            Exporter<?> exporter = protocol.export(wrapperInvoker);
            exporters.add(exporter);
        }
    }
}
先注意這句程式碼:
registryURL.addParameterAndEncoded("export",url.toFullString())
它把當前的url資訊,新增到registryURL物件中去,key為export。 然後當獲取到invoker後,此時的url協議頭會變成registry 此時,下面再呼叫protocol.export 就會呼叫到RegistryProtocol.export()。下面我們把目光移動到 RegistryProtocol 的 export 方法上。
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
            
    //暴露服務
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
    // 獲取註冊中心 URL,以 zookeeper 註冊中心為例,得到的示例 URL 如下:
    //zookeeper://192.168.139.131:2181/com.alibaba.dubbo.registry.RegistryService...
    URL registryUrl = getRegistryUrl(originInvoker);

    //根據 URL 載入 Registry 實現類,比如 ZookeeperRegistry
    final Registry registry = getRegistry(originInvoker);
    
    // 獲取已註冊的服務提供者 URL,比如:
    //dubbo://192.168.100.74:20880/com.viewscenes.netsupervisor.service.InfoUserService...
    final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);

    boolean register = registedProviderUrl.getParameter("register", true);
    ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
    if (register) {
        // 向註冊中心註冊服務
        register(registryUrl, registedProviderUrl);
        ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
    }
    // 獲取訂閱 URL,比如:
    //provider://192.168.100.74:20880/com.viewscenes.netsupervisor.service.InfoUserService...
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl); 
    // 建立監聽器
    OverrideListener overrideSubscribeListener = 
                                new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    // 向註冊中心進行訂閱 override 資料
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    // 建立並返回 DestroyableExporter
    return new 
    DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
}
如上程式碼,主要完成了兩件事: 呼叫doLocalExport進行服務暴露、向註冊中心註冊服務。