Dubbo源码分析3之服务发布

dubbo-export-mulu.png

1.服务发布概述

Dubbo 服务导出过程始于 Spring 容器发布刷新事件[dubbo:service --> ServiceBean --> onApplicationEvent(ContextRefreshedEvent event)],在接收到ContextRefreshedEvent 事件后执行服务导出逻辑。整个逻辑大致可分为三个部分:

第一部分是前置工作,主要用于检查参数,组装 URL;

第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程;

第三部分是向注册中心注册服务,用于服务发现,包括注册到zk和订阅zk。

本文的重点实在整个发布流程,一些细节简单描述省略,比如配置检查,URL组装。

2.源码环境说明

基于dubbo2.6.4版本,使用官方的dubbo-demo项目,项目结构图如下:

dubbo-demo.png

修改注册中心为zookeeper

接口和实现类代码:

public interface DemoService {
   
    String sayHello(String name);
}
public class DemoServiceImpl implements DemoService {
   
    @Override
    public String sayHello(String name) {
   
        System.out.println("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
        return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
    }
}

3.源码分析

服务发布的入口方法是 ServiceBean 的 onApplicationEvent,如下:

代码块 ServiceBean #onApplicationEvent

  @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
   
        // 是否有延迟导出 && 是否已导出 && 是不是已被取消导出
        if (isDelay() && !isExported() && !isUnexported()) {
   
            if (logger.isInfoEnabled()) {
   
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }

3.1 服务发布前置工作

3.1.1 概述

前置工作主要包含两个部分,分别是配置检查,以及 URL 装配。在导出服务之前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省配置。配置检查完成后,接下来需要根据这些配置组装 URL。在 Dubbo 中,URL 的作用十分重要。Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获取配置。

代码块 ServiceConfig#doExport

protected synchronized void doExport() {
   
    if (unexported) {
   
        throw new IllegalStateException("Already unexported!");
    }
    if (exported) {
   
        return;
    }
    exported = true;
    // 检测 interfaceName 是否合法
    if (interfaceName == null || interfaceName.length() == 0) {
   
        throw new IllegalStateException("interface not allow null!");
    }
    // 检测 provider 是否为空,为空则新建一个,并通过系统变量为其初始化
    checkDefault();

    // 下面几个 if 语句用于检测 provider、application 等核心配置类对象是否为空,
    // 若为空,则尝试从其他配置类对象中获取相应的实例。
    if (provider != null) {
   
        if (application == null) {
   
            application = provider.getApplication();
        }
        if (module == null) {
   
            module = provider.getModule();
        }
        if (registries == null) {
   ...}
        if (monitor == null) {
   ...}
        if (protocols == null) {
   ...}
    }
    if (module != null) {
   
        if (registries == null) {
   
            registries = module.getRegistries();
        }
        if (monitor == null) {
   ...}
    }
    if (application != null) {
   
        if (registries == null) {
   
            registries = application.getRegistries();
        }
        if (monitor == null) {
   ...}
    }

    // 检测 ref 是否为泛化服务类型
    if (ref instanceof GenericService) {
   
        // 设置 interfaceClass 为 GenericService.class
        interfaceClass = GenericService.class;
        if (StringUtils.isEmpty(generic)) {
   
            // 设置 generic = "true"
            generic = Boolean.TRUE.toString();
        }
        
    // ref 非 GenericService 类型
    } else {
   
        try {
   
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
   
            throw new IllegalStateException(e.getMessage(), e);
        }
        // 对 interfaceClass,以及 <dubbo:method> 标签中的必要字段进行检查
        checkInterfaceAndMethods(interfaceClass, methods);
        // 对 ref 合法性进行检测
        checkRef();
        // 设置 generic = "false"
        generic = Boolean.FALSE.toString();
    }

    // local 和 stub 在功能应该是一致的,用于配置本地存根
    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);
        }
    }

    if (stub != null) {
   
        // 此处的代码和上一个 if 分支的代码基本一致,这里省略
    }

    // 检测各种对象是否为空,为空则新建,或者抛出异常
    checkApplication();
    checkRegistry();
    checkProtocol();
    appendProperties(this);
    checkStubAndMock(interfaceClass);
    if (path == null || path.length() == 0) {
   
        path = interfaceName;
    }

    // 导出服务
    doExportUrls();

    // ProviderModel 表示服务提供者模型,此对象中存储了与服务提供者相关的信息。
    // 比如服务的配置信息,服务实例等。每个被导出的服务对应一个 ProviderModel。
    // ApplicationModel 持有所有的 ProviderModel。
    ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
    ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
3.1.2 对配置检查的逻辑进行简单的总结:
  1. 检测 dubbo:service 标签的 interface 属性合法性,不合法则抛出异常

  2. 检测 ProviderConfig、ApplicationConfig 等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例。

  3. 检测并处理泛化服务和普通服务类

  4. 检测本地存根配置,并进行相应的处理

  5. 对 ApplicationConfig、RegistryConfig 等配置类进行检测,为空则尝试创建,若无法创建则抛出异常

3.2 服务暴露

下面进入doExportUrls();方法:

    private void doExportUrls() {
   
         // 加载注册中心链接
        List<URL> registryURLs = loadRegistries(true);
        // 遍历 protocols,并在每个协议下导出服务
        for (ProtocolConfig protocolConfig : protocols) {
   
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

代码块:ServiceConfig#doExportUrlsFor1Protocol

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
   
        /***
        代码有点长,省略组装url部分的代码
        配置检查完毕后,紧接着要做的事情是根据配置,以及其他一些信息组装 URL。
        URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。
        ***/
        //...
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
        /***此处组装的url示例:
        dubbo://192.168.43.174:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.43.174&bind.port=20880&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=8564&qos.port=22222&side=provider&timestamp=1578456375449
        ***/

        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
   
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        //下面开始要进入暴露服务的代码了
        String scope = url.getParameter(Constants.SCOPE_KEY);
        // don't export when none is configured
        if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
   

            // export to local if the config is not remote (export to remote only when config is remote)
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
   
                //暴露服务到本地
                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)) {
   
                if (logger.isInfoEnabled()) {
   
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                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);
                        }

                        // For providers, this is used to enable custom proxy to generate invoker
                        String proxy = url.getParameter(Constants.PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
   
                            registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                        }

                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        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<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值