Dubbo2.7.6之服务暴露流程

在上一篇文章Dubbo2.7.6启动原理之Provider中我已经探讨了dubbo服务启动的大致流程。可以看到,暴露服务逻辑在DubboBootstrap#exportServices()方法中。今天就来详细来探索一下暴露服务的实现细节。

如下为服务暴露过程中的涉及到的主要组件:
在这里插入图片描述
注意:

  1. 一个服务可以同时以多种协议暴露
  2. 一个服务可以同时注册在多个不同的注册中心
  3. 每一个服务接口都会转换为对应的ServiceConfig实例

如下是dubbo官方给出的服务暴露时序图:
在这里插入图片描述

如下就是服务接口暴露过程中的转换逻辑:
在这里插入图片描述
说明:

  1. 每一个接口根据一个协议暴露到一个注册中心时,最终会装换为Exporter实例!

上面整理了一下服务暴露的整体流程图,
下面就开始继续撸源码DubboBootstrap#exportServices()

//暴露所有的服务
private void exportServices() {
    //从ConfigManager中获取所有的ServiceConfig实例,然后遍历,一个一个的暴露
    configManager.getServices().forEach(sc -> {
        // 服务export的核心逻辑方法是ServiceConfig#export()
        ServiceConfig serviceConfig = (ServiceConfig) sc;
        //将DubboBootstrap实例赋值给ServiceConfig
        serviceConfig.setBootstrap(this);
        //是否异步Export
        if (exportAsync) {
            //如果是异步,就获取一个线程池,来异步执行export逻辑
            ExecutorService executor = executorRepository.getServiceExporterExecutor();
            Future<?> future = executor.submit(() -> {
                sc.export();
            });
            asyncExportingFutures.add(future);
        } else {
            //同步export
            sc.export();
            exportedServices.add(sc);
        }
    });
}

再来看ServiceConfig#export()

private DubboBootstrap bootstrap;
protected ServiceMetadata serviceMetadata;

public synchronized void export() {
    //如果服务已经export就返回
    if (!shouldExport()) {
        return;
    }
    //如果DubboBootstrap为空,也就没有初始化,就初始化一下DubboBootstrap
    if (bootstrap == null) {
        //这里使用了单例模式,DubboBootstrap不会重复创建
        bootstrap = DubboBootstrap.getInstance();
        bootstrap.init();
    }

    checkAndUpdateSubConfigs();

    //init serviceMetadata 初始化服务的原数据信息
    serviceMetadata.setVersion(version);
    serviceMetadata.setGroup(group);
    serviceMetadata.setDefaultGroup(group);
    serviceMetadata.setServiceType(getInterfaceClass());
    //设置服务接口
    serviceMetadata.setServiceInterfaceName(getInterface());
    //设置服务的实现类
    serviceMetadata.setTarget(getRef());
    //export服务,判断是否延时export
    if (shouldDelay()) {
        //如果是延时export,就使用时间调度器延时执行服务export逻辑
        DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
    } else {
        //直接export服务
        doExport();
    }
    //发布ServiceConfigExportedEvent事件
    exported();
}
public void exported() {
    // dispatch a ServiceConfigExportedEvent since 2.7.4
    dispatch(new ServiceConfigExportedEvent(this));
}

可以看到,在最后使用事件模型,dubbo事件模型以后再研究,由于篇幅原因,这里不做介绍。

然后在看ServiceConfig#doExport()

/**
 * The flag whether a service has unexported ,if the method unexported is invoked, the value is true
 */
private transient volatile boolean unexported;
/**
 * The service name
 */
protected String path;

protected synchronized void doExport() {
    //如果service已经被unexported,就抛出异常
    if (unexported) {
        throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
    }
    //如果服务已经被暴露,就返回
    if (exported) {
        return;
    }
    exported = true;
    //设置path值,只为接口的全限定名
    if (StringUtils.isEmpty(path)) {
        path = interfaceName;
    }
    //执行服务暴露逻辑
    doExportUrls();
}

继续看ServiceConfig#doExportUrls() ,这里才是export核心逻辑的开始

/**
 * The protocol list the service will export with
 * Also see {@link #protocolIds}, only one of them will work.
 * 
 * 一个服务ServiceConfig可能同时以多个协议export
 */
protected List<ProtocolConfig> protocols;
private void doExportUrls() {
    //从ApplicationModel容器中获取服务仓库ServiceRepository(也是一个容器)
    ServiceRepository repository = ApplicationModel.getServiceRepository();
    //生产一个服务描述实例ServiceDescriptor
    ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
    //将服务提供者的信息存放到ServiceRepository中
    repository.registerProvider(
            getUniqueServiceName(),
            ref,
            serviceDescriptor,
            this,
            serviceMetadata
    );
    //获取服务注册中心的URL地址信息,一个服务可以同事在多个注册中心注册。所以此处是List
    List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
    //遍历服务要暴露的协议集合。一个服务可能同时以dubbo协议,rest协议等export。一般默认是dubbo协议,也推荐是用dubbo协议
    for (ProtocolConfig protocolConfig : protocols) {
        //构建服务的path的key
        String pathKey = URL.buildKey(getContextPath(protocolConfig)
                .map(p -> p + "/" + path)
                .orElse(path), group, version);
        // In case user specified path, register service one more time to map it to path.
        repository.registerService(pathKey, interfaceClass);
        // TODO, uncomment this line once service key is unified
        serviceMetadata.setServiceKey(pathKey);
        //真对一个协议,在一个或多个注册中心来export这个服务
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

可以看到,ServiceConfig#doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs)是真对一个协议,在一个或多个注册中心来export服务。

/**
 * The exported services
 */
private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    //获取协议名称,没有则默认使用dubbo
    String name = protocolConfig.getName();
    if (StringUtils.isEmpty(name)) {
        name = DUBBO;
    }
    //组装参数
    Map<String, String> map = new HashMap<String, String>();
    map.put(SIDE_KEY, PROVIDER_SIDE);
    ... 
  
    //init serviceMetadata attachments, ServiceMetadata中的attachments will be transferred to remote side
    serviceMetadata.getAttachments().putAll(map);

    // export service , 构建URL实例
    String host = findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = findConfigedPorts(protocolConfig, name, map);
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
    
    // You can customize Configurator to append extra parameters
    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }
    //获取scope ,如果没有配置服务的scope时就不暴露服务
    String scope = url.getParameter(SCOPE_KEY);
    // don't export when none is configured
    if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

        // export to local if the config is not remote (export to remote only when config is remote)
        //如果scope != remote , 就以本地方式export
        if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
            exportLocal(url);
        }
        // export to remote if the config is not local (export to local only when config is local)
        //如果scope不是local,就默认以remote方式export
        if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
            if (CollectionUtils.isNotEmpty(registryURLs)) {
                //遍历注册中心的地址。来一个一个的export
                for (URL registryURL : registryURLs) {
                    //if protocol is only injvm ,not register
                    //如果协议为injvm, 就不需要暴露,在同一个jvm内的服务
                    if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                        continue;
                    }
                    //添加dynamic参数配置
                    url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                    //添加监控中心url配置, 可选
                    URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                    if (monitorUrl != null) {
                        url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                    }
                   ...

                    // For providers, this is used to enable custom proxy to generate invoker
                    String proxy = url.getParameter(PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                    }
                    // 使用ProxyFactory来生成接口的代理
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                    //生成Invoker的包装类,实现了Invoker接口,其本身也是一个Invoker,只是在invoker的基础上扩展了ServiceConfig 
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                    //使用Protocol来暴露服务接口到对应的注册中心
                    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                    //将这个Exporter实例存入ServiceConfig中的exporters属性中
                    exporters.add(exporter);
                }
            } else {
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                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
             * 处理服务的元数据信息
             */
            WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
            if (metadataService != null) {
                metadataService.publishServiceDefinition(url);
            }
        }
    }
    this.urls.add(url);
}

至此大致的服务暴露流程就整理完毕。再来看实现细节。

  • Protocol 协议
    可以看到:这里的协议实现是通过ExtensionLoader.getAdaptiveExtension()去加载的
Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

那么这样得到的是一个什么样的结果呢?先看一下Protocol的相关实现结构图:
在这里插入图片描述
Protocol源码如下:

@SPI("dubbo")
public interface Protocol {

    int getDefaultPort();
    
    //provider端暴露接口
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    
    //consumer端引用接口
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    void destroy();

    default List<ProtocolServer> getServers() {
        return Collections.emptyList();
    }

}

在@SPI中指定了默认实现的名称为dubbo ,那么就要去找SPI的配置文件啦,如下:
在这里插入图片描述
可以看到dubbo对应的实现类为DubboProtocol ,因此Protocol接口的默认实现类是DubboProtocol 。

那么上文中PROTOCOL就是DubboProtocol吗?

ExtensionLoader#getAdaptiveExtension();返回的实际上是dubbo通过字节码技术重新生成的一个类,生产的类的代码如下:

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    public java.util.List getServers() {
        throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public void destroy() {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

可以看到,重新生成的类,对于接口中标注有@Adaptive注解的方法才给出了实现。
而实现的方式也就是通过ExtensionLoader#getExtension(extName)来获取对于的类的实例。然后代其执行对于的逻辑。

下面再来看一下ExtensionLoader#getExtension(extName)的返回接口,这里extName=dubbo 。

private T createExtension(String name) {
    //获取对于名称为dubbo的class ,这里值为org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        //从缓存中加载class的实例,如果没有就新建一个
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        //对类中的属性进行依赖注入
        injectExtension(instance);
        //处理包装类 , cachedWrapperClasses存放的是Protocol所有的Wrapper类,也就是上图中的三个
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
            //循环wrapper类,将DubboProtocol一层一层包装起来
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        initExtension(instance);
        //然后再返回实例。这时候返回的就是包装了DubboProtocol的包装类的实例了
        return instance;
    } catch (Throwable t) {
        ...
    }
}

由于Protocol接口存在三个Wrapper类,因此这里返回的是一个包装了DubboProtocol的包装类。如下图所示:
在这里插入图片描述

ExtensionLoader的相关源码解读在以前章节已经有详细讨论,这里就不在赘述。
服务暴露的所有细节都在 PROTOCOL.export(wrapperInvoker);中。篇幅原因这个下篇文章中再详细讨论

  • ProxyFactory 代理工厂
ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

ProxyFactory也是一个SPI接口,并指定了默认实现是javassist对应的是JavassistProxyFactory 。然后这里在方法的上也都标注了@Adaptive ,并指定了参数PROXY_KEY ,也就是说我们可以指定PROXY_KEY的值来指定默认的实现.

源码如下:

/**
 * ProxyFactory. (API/SPI, Singleton, ThreadSafe)
 */
@SPI("javassist")
public interface ProxyFactory {
    
    @Adaptive({PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker) throws RpcException;

    @Adaptive({PROXY_KEY})
    <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;

    @Adaptive({PROXY_KEY})
    <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;

}

可以看到,它也有一个Wrapper类。结构的原理如Protocol 。最终PROXY_FACTORY的值为一个包装了JavassistProxyFactory的StubProxyFactoryWrapper类
在这里插入图片描述

PROXY_FACTORY值为:
在这里插入图片描述
到此dubbo中暴露服务的流程整理完毕!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值