源码分析Dubbo服务提供者启动流程-上篇

   本节将详细分析Dubbo服务提供者的启动流程,请带着以下几个疑问进行本节的阅读,由于这几个问题将是接下来几篇文章分析的重点内容。web

  1. 何时创建与注册中心的链接。
  2. 服务提供者何时向注册中心注册服务。
  3. 服务提供者与注册中心的心跳机制。

   从上文中咱们得知,服务提供者启动的核心入口为ServiceBean,本节将从源码级别详细剖析ServcieBean的实现原理,即Dubbo服务提供者的启动流程,ServiceBean的继承层次如图所示,dubbo:service标签的全部属性都被封装在此类图结构中。
这里写图片描述spring

一、源码分析ServiceBean#afterPropertiesSet
   ServiceBean#afterPropertiesSetapi

if (getProvider() == null) {  // @1
    Map<String, ProviderConfig> provide
             ConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class,   false, false); // @2
              // ......  具体解析代码省略。
    }
}

   Step1:若是provider为空,说明dubbo:service标签未设置provider属性,若是一个dubbo:provider标签,则取该实例,若是存在多个dubbo:provider配置则provider属性不能为空,不然抛出异常:“Duplicate provider configs”。网络

   ServiceBean#afterPropertiesSetapp

if (getApplication() == null
         && (getProvider() == null || getProvider().getApplication() == null)) {
       Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
                  ApplicationConfig.class, false, false);
        // ...省略
 }

   Step2:若是application为空,则尝试从BeanFactory中查询dubbo:application实例,若是存在多个dubbo:application配置,则抛出异常:“Duplicate application configs”。
   Step3:若是ServiceBean的module为空,则尝试从BeanFactory中查询dubbo:module实例,若是存在多个dubbo:module,则抛出异常:"Duplicate module configs: "。
   Step4:尝试从BeanFactory中加载全部的注册中心,注意ServiceBean的List< RegistryConfig> registries属性,为注册中心集合。
   Step5:尝试从BeanFacotry中加载一个监控中心,填充ServiceBean的MonitorConfig monitor属性,若是存在多个dubbo:monitor配置,则抛出"Duplicate monitor configs: "。
   Step6:尝试从BeanFactory中加载全部的协议,注意:ServiceBean的List< ProtocolConfig> protocols是一个集合,也即一个服务能够经过多种协议暴露给消费者。dom

   ServiceBean#afterPropertiesSetjvm

if (getPath() == null || getPath().length() == 0) {
       if (beanName != null && beanName.length() > 0 && getInterface() != null && getInterface().length() > 0  && beanName.startsWith(getInterface())) {
                setPath(beanName);
       }
 }

   Step7:设置ServiceBean的path属性,path属性存放的是dubbo:service的beanName(dubbo:service id)。socket

   ServiceBean#afterPropertiesSetide

if (!isDelay()) {
     export();
}

   Step8:若是为启用延迟暴露机制,则调用export暴露服务。首先看一下isDelay的实现,而后重点分析export的实现原理(服务暴露的整个实现原理)。svg

   ServiceBean#isDelay

private boolean isDelay() {
        Integer delay = getDelay();
        ProviderConfig provider = getProvider();
        if (delay == null && provider != null) {
            delay = provider.getDelay();
        }
        return supportedApplicationListener && (delay == null || delay == -1);
    }

   若是有设置dubbo:service或dubbo:provider的属性delay,或配置delay为-1,都表示启用延迟机制,单位为毫秒,设置为-1,表示等到Spring容器初始化后再暴露服务。从这里也能够看出,Dubbo暴露服务的处理入口为ServiceBean#export—》ServiceConfig#export。

1.1 源码分析ServiceConfig#export 暴露服务
   调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export

public synchronized void export() {
        if (provider != null) {
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        if (export != null && !export) {   // @1
            return;
        }

        if (delay != null && delay > 0) {    // @2
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {
            doExport();    //@3
        }
    }

   代码@1:判断是否暴露服务,由dubbo:service export="true|false"来指定。
   代码@2:若是启用了delay机制,若是delay大于0,表示延迟多少毫秒后暴露服务,使用ScheduledExecutorService延迟调度,最终调用doExport方法。
   代码@3:执行具体的暴露逻辑doExport,须要你们留意:delay=-1的处理逻辑(基于Spring事件机制触发)。

1.2 源码分析ServiceConfig#doExport暴露服务
   调用链:ServiceBean#afterPropertiesSet—调用------>ServiceConfig#export------>ServiceConfig#doExport

   ServiceConfig#checkDefault

private void checkDefault() {
        if (provider == null) {
            provider = new ProviderConfig();
        }
        appendProperties(provider);
 }

   Step1:若是dubbo:servce标签也就是ServiceBean的provider属性为空,调用appendProperties方法,填充默认属性,其具体加载顺序:

  1. 从系统属性加载对应参数值,参数键:dubbo.provider.属性名,System.getProperty。
  2. 加载属性配置文件的值。属性配置文件,可经过系统属性:dubbo.properties.file,若是该值未配置,则默认取dubbo.properties属性配置文件。

   ServiceConfig#doExport

if (ref instanceof GenericService) {
      interfaceClass = GenericService.class;
      if (StringUtils.isEmpty(generic)) {
           generic = Boolean.TRUE.toString();
      }
 } else {
      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();
 }

   Step2:校验ref与interface属性。若是ref是GenericService,则为dubbo的泛化实现,而后验证interface接口与ref引用的类型是否一致。

   ServiceConfig#doExport

if (local != null) {
      if ("true".equals(local)) {
            local = interfaceName + "Local";
      }
      Class<?> localClass;
      try {
             localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
       } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
       }
      if (!interfaceClass.isAssignableFrom(localClass)) {
           throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
       }
 }

   Step3:dubbo:service local机制,已经废弃,被stub属性所替换。
   Step4:处理本地存根Stub,<dubbo:service 的stub属性,能够设置为true,此时Stub的类名为:interface+Stub,stub也能够指定自定义的全类名。本地存根说明如图所示(Dubbo官方文档)
这里写图片描述
   ServiceConfig#doExport

checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);

   Step5:校验ServiceBean的application、registry、protocol是否为空,并从系统属性(优先)、资源文件中填充其属性。
   系统属性、资源文件属性的配置以下:
   application dubbo.application.属性名,例如 dubbo.application.name
   registry dubbo.registry.属性名,例如 dubbo.registry.address
   protocol dubbo.protocol.属性名,例如 dubbo.protocol.port
   service dubbo.service.属性名,例如 dubbo.service.stub

   ServiceConfig#doExport

checkStubAndMock(interfaceClass);

   Step6:校验stub、mock类的合理性,是不是interface的实现类。

   ServiceConfig#doExport

doExportUrls();

   Step7:执行doExportUrls()方法暴露服务,接下来会重点分析该方法。

   ServiceConfig#doExport

ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);

   Step8:将服务提供者信息注册到ApplicationModel实例中。

1.3 源码分析ServiceConfig#doExportUrls暴露服务具体实现逻辑
   调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport

private void doExportUrls() {
        List<URL> registryURLs = loadRegistries(true);  // @1
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);    // @2
        }
 }

   代码@1:首先遍历ServiceBean的List< RegistryConfig> registries(全部注册中心的配置信息),而后将地址封装成URL对象,关于注册中心的全部配置属性,最终转换成url的属性(?属性名=属性值),loadRegistries(true),参数的意思:true,表明服务提供者,false:表明服务消费者,若是是服务提供者,则检测注册中心的配置,若是配置了register=“false”,则忽略该地址,若是是服务消费者,并配置了subscribe="false"则表示不从该注册中心订阅服务,故也不返回,一个注册中心URL示例:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=7072&qos.port=22222&registry=zookeeper&timestamp=1527308268041
   代码@2:而后遍历配置的全部协议,根据每一个协议,向注册中心暴露服务,接下来重点分析doExportUrlsFor1Protocol方法的实现细节。

1.4 源码分析doExportUrlsFor1Protocol
   调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport------>ServiceConfig#doExportUrlsFor1Protocol
   ServiceConfig#doExportUrlsFor1Protocol

String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
     name = "dubbo";
}
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
    map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);

   Step1:用Map存储该协议的全部配置参数,包括协议名称、dubbo版本、当前系统时间戳、进程ID、application配置、module配置、默认服务提供者参数(ProviderConfig)、协议配置、服务提供Dubbo:service的属性。

   ServiceConfig#doExportUrlsFor1Protocol

if (methods != null && !methods.isEmpty()) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && !arguments.isEmpty()) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (methods != null && methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // target the method, and get its signature
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) {
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + 
                                                      argument.getType());
                                            }
                                        } else {
                                            // multiple callbacks in the method
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                        throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", 
                                                             type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }

                    }
                }
            } // end of methods for
        }

   Step2:若是dubbo:service有dubbo:method子标签,则dubbo:method以及其子标签的配置属性,都存入到Map中,属性名称加上对应的方法名做为前缀。dubbo:method的子标签dubbo:argument,其键为方法名.参数序号。

   ServiceConfig#doExportUrlsFor1Protocol

if (ProtocolUtils.isGeneric(generic)) {
      map.put(Constants.GENERIC_KEY, generic);
      map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
 } else {
      String revision = Version.getVersion(interfaceClass, version);
      if (revision != null && revision.length() > 0) {
          map.put("revision", 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.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
      }
}

   Step3:添加methods键值对,存放dubbo:service的全部方法名,多个方法名用,隔开,若是是泛化实现,填充genric=true,methods为"*";

   ServiceConfig#doExportUrlsFor1Protocol

if (!ConfigUtils.isEmpty(token)) {
      if (ConfigUtils.isDefault(token)) {
            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
       } else {
            map.put(Constants.TOKEN_KEY, token);
       }
}

   Step4:根据是否开启令牌机制,若是开启,设置token键,值为静态值或uuid。

   ServiceConfig#doExportUrlsFor1Protocol

if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
       protocolConfig.setRegister(false);
       map.put("notify", "false");
}

   Step5:若是协议为本地协议(injvm),则设置protocolConfig#register属性为false,表示不向注册中心注册服务,在map中存储键为notify,值为false,表示当注册中心监听到服务提供者发送变化(服务提供者增长、服务提供者减小等事件时不通知。

   ServiceConfig#doExportUrlsFor1Protocol

// export service
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
     contextPath = provider.getContextpath();
}

   Step6:设置协议的contextPath,若是未配置,默认为/interfacename

   ServiceConfig#doExportUrlsFor1Protocol

String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);

   Step7:解析服务提供者的IP地址与端口。
服务IP地址解析顺序:(序号越小越优先)

  1. 系统环境变量,变量名:DUBBO_DUBBO_IP_TO_BIND
  2. 系统属性,变量名:DUBBO_DUBBO_IP_TO_BIND
  3. 系统环境变量,变量名:DUBBO_IP_TO_BIND
  4. 系统属性,变量名:DUBBO_IP_TO_BIND
  5. dubbo:protocol 标签的host属性 --》 dubbo:provider 标签的host属性
  6. 默认网卡IP地址,经过InetAddress.getLocalHost().getHostAddress()获取,若是IP地址不符合要求,继续下一个匹配。

   判断IP地址是否符合要求的标准是:

public static boolean isInvalidLocalHost(String host) {
        return host == null
                || host.length() == 0
                || host.equalsIgnoreCase("localhost")
                || host.equals("0.0.0.0")
                || (LOCAL_IP_PATTERN.matcher(host).matches());
      }
  1. 选择第一个可用网卡,其实现方式是创建socket,链接注册中心,获取socket的IP地址。其代码:
Socket socket = new Socket();
        try {
              SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
              socket.connect(addr, 1000);
              hostToBind = socket.getLocalAddress().getHostAddress();
              break;
         } finally {
              try {
                      socket.close();
              } catch (Throwable e) {
              }
        }

服务提供者端口解析顺序:(序号越小越优先)

  1. 系统环境变量,变量名:DUBBO_DUBBO_PORT_TO_BIND
  2. 系统属性,变量名:DUBBO_DUBBO_PORT_TO_BIND
  3. 系统环境变量,变量名:DUBBO_PORT_TO_BIND
  4. 系统属性,变量名DUBBO_PORT_TO_BIND
  5. dubbo:protocol标签port属性、dubbo:provider标签的port属性。
  6. 随机选择一个端口。

   ServiceConfig#doExportUrlsFor1Protocol

URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

   Step8:根据协议名称、协议host、协议端口、contextPath、相关配置属性(application、module、provider、protocolConfig、service及其子标签)构建服务提供者URI。
   URL运行效果图:
这里写图片描述
   以dubbo协议为例,展现最终服务提供者的URL信息以下:dubbo://192.168.56.1:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.56.1&bind.port=20880&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5916&qos.port=22222&side=provider&timestamp=1527168070857

   ServiceConfig#doExportUrlsFor1Protocol

String scope = url.getParameter(Constants.SCOPE_KEY);
// don't export when none is configured
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
     // 接口暴露实现逻辑
}

   Step9:获取dubbo:service标签的scope属性,其可选值为none(不暴露)、local(本地)、remote(远程),若是配置为none,则不暴露。默认为local。

   ServiceConfig#doExportUrlsFor1Protocol

// export to local if the config is not remote (export to remote only when config is remote)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {     // @1
       exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {     // @2
        if (logger.isInfoEnabled()) {
              logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
         }
        if (registryURLs != null && !registryURLs.isEmpty()) {   // @3
              for (URL registryURL : registryURLs) {
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));  // @4
                    URL monitorUrl = loadMonitor(registryURL);       // @5
                    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);
                    }
                   Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));  // @6
                   DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);    
                   Exporter<?> exporter = protocol.export(wrapperInvoker);    // @7
                   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);
         }
}

   Step10:根据scope来暴露服务,若是scope不配置,则默认本地与远程都会暴露,若是配置成local或remote,那就只能是二选一。
   代码@1:若是scope不为remote,则先在本地暴露(injvm):,具体暴露服务的具体实现,将在remote 模式中详细分析。
   代码@2:若是scope不为local,则将服务暴露在远程。
   代码@3:remote方式,检测当前配置的全部注册中心,若是注册中心不为空,则遍历注册中心,将服务依次在不一样的注册中心进行注册。
   代码@4:若是dubbo:service的dynamic属性未配置, 尝试取dubbo:registry的dynamic属性,该属性的做用是否启用动态注册,若是设置为false,服务注册后,其状态显示为disable,须要人工启用,当服务不可用时,也不会自动移除,一样须要人工处理,此属性不要在生产环境上配置。
   代码@5:根据注册中心url(注册中心url),构建监控中心的URL,若是监控中心URL不为空,则在服务提供者URL上追加monitor,其值为监控中心url(已编码)。

  • 若是dubbo spring xml配置文件中没有配置监控中心(dubbo:monitor),若是从系统属性-Ddubbo.monitor.address,-Ddubbo.monitor.protocol构建MonitorConfig对象,不然从dubbo的properties配置文件中寻找这个两个参数,若是没有配置,则返回null。
  • 若是有配置,则追加相关参数,dubbo:monitor标签只有两个属性:address、protocol,其次会追加interface(MonitorService)、协议等。

   代码@6:经过动态代理机制建立Invoker,dubbo的远程调用实现类。
这里写图片描述
   Dubbo远程调用器如何构建,这里不详细深刻,重点关注WrapperInvoker的url为:registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo%3A%2F%2F192.168.56.1%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.56.1%26bind.port%3D20880%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D6328%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1527255510215&pid=6328&qos.port=22222&registry=zookeeper&timestamp=1527255510202,这里有两个重点值得关注:

  • path属性:com.alibaba.dubbo.registry.RegistryService,注册中心也相似于服务提供者。
  • export属性:值为服务提供者的URL,为何须要关注这个URL呢?请看代码@7,protocol属性为Protocol$Adaptive,Dubbo在加载组件实现类时采用SPI(插件机制,有关于插件机制,在该专题后续文章将重点分析),在这里咱们只须要知道,根据URL冒号以前的协议名将会调用相应的方法。
    这里写图片描述
       其映射关系(列出与服务启动相关协议实现类):
    dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol //文件位于dubbo-rpc-dubbo/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
    registry=com.alibaba.dubbo.registry.integration.RegistryProtocol //文件位于dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
       代码@7:根据代码@6的分析,将调用RegistryProtocol#export方法。

1.5 源码分析RegistryProtocol#export方法
   调用链:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport------>ServiceConfig#doExportUrlsFor1Protocol------>RegistryProtocol#export

   RegistryProtocol#export

@Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //export invoker
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);   // @1

        URL registryUrl = getRegistryUrl(originInvoker);       // @2

        //registry provider
        final Registry registry = getRegistry(originInvoker);                          // @3
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);     // @4start

        //to judge to delay publish whether or not
        boolean register = registedProviderUrl.getParameter("register", true);  

        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);

        if (register) {  
            register(registryUrl, registedProviderUrl);      // @4 end
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }

        // Subscribe the override data
        // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);          // @5 start
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);            // @5 end
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    }

   代码@1:启动服务提供者服务,监听指定端口,准备服务消费者的请求,这里其实就是从WrapperInvoker中的url(注册中心url)中提取export属性,描述服务提供者的url,而后启动服务提供者。
这里写图片描述
   从上图中,能够看出,将调用DubboProtocol#export完成dubbo服务的启动,利用netty构建一个微型服务端,监听端口,准备接受服务消费者的网络请求,本节旨在梳理其启动流程,具体实现细节,将在后续章节中详解,这里咱们只要知道,< dubbo:protocol name=“dubbo” port=“20880” />,会再这次监听该端口,而后将dubbo:service的服务handler加入到命令处理器中,当有消息消费者链接该端口时,经过网络解包,将须要调用的服务和参数等信息解析处理后,转交给对应的服务实现类处理便可。
   代码@2:获取真实注册中心的URL,例如zookeeper注册中心的URL:zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo%3A%2F%2F192.168.56.1%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.56.1%26bind.port%3D20880%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D10252%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1527263060882&pid=10252&qos.port=22222&timestamp=1527263060867
   代码@3:根据注册中心URL,从注册中心工厂中获取指定的注册中心实现类:zookeeper注册中心的实现类为:ZookeeperRegistry
   代码@4:获取服务提供者URL中的register属性,若是为true,则调用注册中心的ZookeeperRegistry#register方法向注册中心注册服务(实际由其父类FailbackRegistry实现)。
   代码@5:服务提供者向注册中心订阅本身,主要是为了服务提供者URL发送变化后从新暴露服务,固然,会将dubbo:reference的check属性设置为false。

   到这里就对文章开头提到的问题1,问题2作了一个解答,其与注册中心的心跳机制等将在后续章节中详细分析。

   文字看起来可能不是很直观,现整理一下Dubbo服务提供者启动流程图以下:
这里写图片描述

   本文重点梳理了Dubbo服务提供者启动流程,其中Dubbo服务提供者在指定端口监听服务的启动流程将在下一节中详细分析。