dubbo源码之export

本文深入剖析Dubbo服务导出机制,详细解读Dubbo如何通过DubboNamespaceHandler类初始化配置,以及ServiceBean中export方法如何实现服务导出。文章探讨了Dubbo支持的两种导出方式:延迟导出与立即导出,以及Invoker在服务导出中的关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在dubbo中,dubbo通过DubboNamespaceHandler类对dubbo配置文件中的各个标签内容进行初始化操作。

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
        registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }
}

其中"service"对应的ServiceBean.class中定义了Dubbo中服务是如何export的,即调用export()方法。

    @Override
    public void export() {
        super.export();
        // Publish ServiceBeanExportedEvent
        publishExportEvent();
    }

在dubbo中支持两种导出方式:延迟导出和立即导出。延迟导出入口对应的为ServiceBean中的afterPropertiesSet方法,立即导出的入口对应了ServiceBean中的onApplicationEvent方法。

onApplicationEvent

我们主要关注立即导出onApplicationEvent方法。如下所示:

    /**
     * export
     * @param event
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 已经暴露  不暴露,初始二者均为true
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }
    
    /**
     * @since 2.6.5
     */
    @Override
    public void export() {
        super.export();
        // Publish ServiceBeanExportedEvent
        publishExportEvent();
    }

publishExportEvent()中内容很简单,我们主要关注super.export()中的内容,super.export()即调用了org.apache.dubbo.config.ServiceConfig#export()方法。

    public synchronized void export() {
        // 一系列校验
        checkAndUpdateSubConfigs();

        // 是否暴露,若不暴露,则直接返回
        if (!shouldExport()) {
            return;
        }

        // 判断是否需要延迟加载
        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            doExport();
        }
    }

checkAndUpdateSubConfigs()方法中主要做了一系列的检验工作,校验各种对象是否为空,若为空则新创建或者抛出异常,我们不做深究,关注主流程。在export()方法中判断是否export(),如果为false,则直接return。如果不需要延迟加载,则直接doExport(),下面主要来看这个方法。

    protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
        }
        // 如果配置export=false,禁止服务导出,则返回。<dubbo:provider export="false" />
        if (exported) {
            return;
        }
        exported = true;

        if (StringUtils.isEmpty(path)) {
            path = interfaceName;
        }
        doExportUrls();
    }

我们可以看到在doExport()方法中,最终调入了doExportUrls()方法。

    /**
     * 进行导出
     * dubbo 支持多协议的导出
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private void doExportUrls() {
        // Dubbo是基于URL驱动的。获取的List<URL>是zookeeper://localhost:2181类型的URL。我们可以将服务发布到多个注册中心
        // 所以该registryURLs为一个list
        List<URL> registryURLs = loadRegistries(true);
        // 遍历protocols,dubbo支持多协议的暴露,如rmi、dubbo
        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            ApplicationModel.initProviderModel(pathKey, providerModel);
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

在loadRegistries中,主要是获得注册中心的URL,因为dubbo允许我们将服务发布到多个注册中心中,所以loadRegistries的返回值是一个List。

    /**
     * Load the registry and conversion it to {@link URL}, the priority order is: system property > dubbo registry config
     * 加载注册中心的URL
     * <p>
     * loadRegistries 方法主要包含如下的逻辑:
     * <p>
     * 检测是否存在注册中心配置类,不存在则抛出异常
     * 构建参数映射集合,也就是 map
     * 构建注册中心链接列表
     * 遍历链接列表,并根据条件决定是否将其添加到 registryList 中
     *
     * @param provider whether it is the provider side
     * @return
     */
    protected List<URL> loadRegistries(boolean provider) {
        // check && override if necessary
        List<URL> registryList = new ArrayList<URL>();
        // 当注册中心配置不为空,遍历得到每个注册中心的地址。
        if (CollectionUtils.isNotEmpty(registries)) {
            for (RegistryConfig config : registries) {
                String address = config.getAddress();
                if (StringUtils.isEmpty(address)) {
                    // 如果address为空,则将其设置为0.0.0.0
                    address = ANYHOST_VALUE;
                }
                // 注册中心地址不为N/A时,添加进registryList中
                if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map<String, String> map = new HashMap<String, String>();
                    // 添加 ApplicationConfig RegistryConfig字段信息添加到map中。
                    appendParameters(map, application);
                    appendParameters(map, config);
                    // 添加path、protocol、pid、release、timestap等信息到map中
                    map.put(PATH_KEY, RegistryService.class.getName());
                    appendRuntimeParameters(map);
                    if (!map.containsKey(PROTOCOL_KEY)) {
                        map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
                    }

                    // 解析得到URL列表,address中可能包含多个注册中心的ip,因此解析得到的是一个URL列表
                    List<URL> urls = UrlUtils.parseURLs(address, map);

                    for (URL url : urls) {
                        // 在url中设置协议头为registry,并添加参数。
                        url = URLBuilder.from(url)
                                .addParameter(REGISTRY_KEY, url.getProtocol())
                                .setProtocol(REGISTRY_PROTOCOL)
                                .build();

                        // 通过条件判断,决定是否添加url到registryList中,条件判断:
                        // (服务提供者 && registry == true 或 null) ||
                        //      (非活动提供者 && subscribe = true 或 null)
                        if ((provider && url.getParameter(REGISTER_KEY, true))
                                || (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    }

在上述代码中已经做了详尽的注释,暂且不做过多的解释,我们还是去关注export()的主流程,进入doExportUrlsFor1Protocol()方法。

    /**
     * 方法中出现的 URL 并非 java.net.URL,而是 com.alibaba.dubbo.common.URL。
     *
     * Invoker在dubbo中是一个非常重要的模型,在服务提供端和服务引用段都会出现Invoker。
     * - Invoker是实体域,是dubbo的核心模型,其他模型向它靠拢或转换成它,它代表了一个可执行体。可以向它发起Invoke调用,可能是一个本地的实现
     * 也可能是一个远程的是实现,也可能是一个集群实现。
     *
     * @param protocolConfig
     * @param registryURLs
     */
    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        // 一系列的组装参数。构造URL,注意:这个URL不是java.net.URL,而是org.apache.dubbo.common.URL

        // ======================================= 分割线 =======================================
        // 下面这一部分将进入真正的dubbo服务的导出

        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            // 加载ConfiguratorFactory,并生成Configurator实例,然后加载上面生成的url
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        // 从url中得到scope
        String scope = url.getParameter(SCOPE_KEY);
        // don't export when none is configured
        // 如果scope为none时,什么都不做
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            // export to local if the config is not remote (export to remote only when config is remote)
            // 如果配置的不是远程导出(remote),则进行本地导出,调用exportLocal(url);
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            // 如果不是本地导出(local),则进行远程暴露.只有当scope配置为local的时候,才进行本地导出。
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        //if protocol is only injvm ,not register
                        // 如果url中的protocol,不为injvm,则继续处理下一个注册中心url
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        // 加载监听器连接,如果监听器url不为null,则将监听器链接作为参数添加到url中
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        }
                        // 加载监控、URL中添加监控地址信息、日志
                        if (logger.isInfoEnabled()) {
                            if (url.getParameter(REGISTER_KEY, true)) {
                                logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                            } else {
                                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                            }
                        }

                        // For providers, this is used to enable custom proxy to generate invoker
                        // 获得url中的代理自动,才可以创建一个代理,进行invoke
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }

                        // 为服务提供类生成Invoker,代理工厂创建一个代理,获得一个invoker。默认使用javassist方式
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        // 将Invoker、serviceConfig传递进去,DelegateProviderMetaDataInvoker持有了他们
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        // 按照相应的protocol暴露wrapperInvoker对象。
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    if (logger.isInfoEnabled()) {
                        logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                    }
                    // 当注册中心url的list为空时,默认生成一个代理类。
                    // 即不存在注册中心时,仅导出服务
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
                /**
                 * @since 2.7.0
                 * ServiceData Store
                 */
                MetadataReportService metadataReportService = null;
                if ((metadataReportService = getMetadataReportService()) != null) {
                    metadataReportService.publishProvider(url);
                }
            }
        }
        this.urls.add(url);
    }

doExportUrlsFor1Protocol()方法中我们省略了一些构造URL的无关代码,主要关心dubbo服务导出的过程。首先加载了ConfiguratorFactory扩展点,并生成Configurator实例,配置构造完成的URL。之后从url中取得参数:scope,又根据的scope的值决定了不同的导出方式,具体如下:

  • scope = none,不导出服务
  • scope != remote,则进行本地导出服务
  • scope != local,则进行远程导出服务

若scope != remote,则进行本地导出服务,则直接调用exportLocal(url)方法;若scope != local,则进行远程导出服务,会根据URL的list加载监听器、日志等参数,最终生成Invoker,完成对protocol的export调用。在上述代码中,做了较为详细的注释解释,大家可以参考理解。
不论是local导出或者remote导出,在导出之前都会生成一个Invoker对象,由此可见Invoker的创建过程是一个很重要的过程。

Invoker的创建过程

在dubbo中,Invoker是一个非常重要的概念,在服务的提供端以及引用端都会出现Invoker,在dubbo官方文档中,Invoker被这样介绍:Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。在dubbo中,Invoker的默认实现为javassist方式。下面我们来看一下JavassistProxyFactory代码,代码如下:

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        // 为目标类创建Wrapper。
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        // 创建匿名Invoker类对象,并实现doInvoker方法。
        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);
            }
        };
    }

我们省去其他不必要的代码,主要看getInvoker方法。getInvoker方法中,首先通过Wrapper为目标类创建Wrapper,然后创建匿名Inovker类对象,并实现doInvoke方法,在doInvoke方法中,会调用上面创建的Wrapper的invokeMehtod方法,invokeMethod方法最终会调用目标方法。到这Invoker的生成过程就结束了。

在获得invoke后,封装DelegateProviderMetaDataInvoker,它将持有invoke和serviceConfig对象。最后调用protocol.export(wrapperInvoker)进行服务的导出。

导出服务

首先我们先来看本地导出exportLocal(),由代码的注释可以看出,仅导出URL的协议头等于 injvm的URL。代码如下:

    /**
     * always export injvm
     */
    private void exportLocal(URL url) {
        URL local = URLBuilder.from(url)
                .setProtocol(LOCAL_PROTOCOL)
                .setHost(LOCALHOST_VALUE)
                .setPort(0)
                .build();

        // 创建Invoker,并导出服务,这里的protocol会在运行时调用InjvmProtocol的export方法
        Exporter<?> exporter = protocol.export(
                PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
    }

上面本地导出的逻辑较为简单,不做过多的解释。远程导出代码在上面代码中已经有过分析,不在赘述。之后会进入真正进入export的过程,期间会经过一个调用链过程。具体过程如下图UML所示:
export过程拦截链

虽然在export中还存在一些创建server的过程,但是不在服务导出的主流程内,在这里就不做分析了,如果大家有兴趣可以单独进行了解,下附一张export的导出过程流程图,供大家参考,如有不妥之处,请大家指出。
服务导出过程流程图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值