Dubbo源码分析(四)服务暴露的具体流程(上)

前言

我们在第2章节里面,已经讲到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
得到以下结果:

1) "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"
2) "1545805385379"
3) "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"
4) "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<URL> registryURLs。基于上面的配置文件,这里的registries就是一个长度为2的List<RegistryConfig>,最终将它们解析为List<URL> 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的实例,如下图示:

Invoker

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 参数,获得对应的拓展实现。比如这里是本地暴露,那么它就会调用到InjvmProtocolInjvmProtocol的 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进行服务暴露、向注册中心注册服务

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容