Dubbo源码详解(2.7.3)暴露服务的过程

Dubbo官网上的文档是2.6.X的版本的,因为需要用到了比较新的Dubbo2.7.3,并且需要修改服务的暴露和引入的逻辑来适应项目。所以研究了一下,自己总结的笔记。(大量伪代码,自己下一份同版本的源码对比的看——可以像看小说一样阅读)

所需要的的前置理解

spring容器的刷新事件
(ApplicationEvent)事件抽象类,它下面已经有4个已经实现好的事件。
(ContextRefreshedEvent)容器刷新事件、(ContextStartedEvent)容器启动事件
(ContextClosedEvent)容器关闭事件、(ContextStoppedEvent)容器停止事件
URL
在Dubbo中,URL的作用十分重要,采用URL作为配置信息的统一格式,通过传递URL来传递配置信息。
注意:这里的URL 并非 java.net.URL,而是 com.alibaba.dubbo.common.URL。

Dubbo服务暴露(导出)的过程

Dubbo服务的导出过程基于spring容器发布刷新事件,Dubbo在接受到事件后,会立即执行服务的导出逻辑。
1、前置工作,主要是检查参数(配置文件)、组装URL(根据配置文件,组装URL ,通过传递URL,来传递配置信息)
2、导出服务,包含导出服务到本地(JVM)和导出服务到远程
3、向注册中心注册服务,用于服务的发现
一、起点

 服务导出的入口方法是ServiceBean的onApplicationEvent方法。
    //ServiceBean 是 Dubbo 与 Spring 框架进行整合的关键,可以看做是两个框架之间的桥梁。具有同样作用的类还有 ReferenceBean。
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
    	//是否已导出&&是否已被取消导出     就是这个服务没有导出,并且(这个服务没有被取消导出),进入方法
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            //导出服务的方法
            export();
        }
    }    

二、前置工作
前置工作包含两个部分,配置检查URL装配
在导出服务之前,Dubbo需要检查用户的配置是否合理(包括为用户补充默认配置),检查完成后,根据这些配置组装URL。(URL作为配置信息的同一格式,通过传递URL来传递配置信息)
2.1.1检查配置

    @Override
    public void export() {
    	//先执行父类的export()方法
        super.export();
        // Publish ServiceBeanExportedEvent()
        publishExportEvent();
    }

------------------------分-------割--------线-----------------------------
父类的export()方法

public synchronized void export() {
    //检查和更新附属的配置文件
    checkAndUpdateSubConfigs();
    // shouldExport 默认值为 true    如果export==null?true:export
    // 应该导出为true导出,为null也导出 ,  为false则不导出
    //如果只想本地启动服务,不把服务暴露出去给别人使用,可以配置<dubbo:provider export="false" />禁止服务导出
    if (!shouldExport()) {
        return;
    }
	//如果应该延迟导出,就去延迟导出 ,这里不研究
    if (shouldDelay()) {
        DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
    } else {
        //   应该导出+不延迟导出=立即导出服务
        doExport();
    }
}

------------------------分-------割--------线-----------------------------
下面分析 doExport方法

 protected synchronized void doExport() {
        // 这个服务不需要导出  
        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();
    }

------------------------分-------割--------线-----------------------------
2.1.2多协议多注册中心导出服务

//Dubbo允许我们使用不同的协议导出服务,也允许我们向多个	注册中心注册服务
    //多协议多注册中心导出服务
    private void doExportUrls() {
        //通过loadRegistries加载注册中心链接(下面会分析这个方法) 
        List<URL> registryURLs = loadRegistries(true);
        //遍历协议、在每个协议下导出服务
        for (ProtocolConfig protocolConfig : protocols) {
            // 通过(路径+guoup+version)构建一个string类型的 路径key  从协议配置类中,获取上下文路径.
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            //提供者模型
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            //ApplicationModel持有所有的提供者模型 .init初始化提供者模型 
            ApplicationModel.initProviderModel(pathKey, providerModel);
            //组装URL                传入协议配置类     注册集合
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

loadRegistries 方法的逻辑

	1、检测是否存在注册配置类集合,如果存在,遍历这个集合
    2、检测address是否合法,如果合法,构建参数映射集合,也就是 map
	3、构建注册中心链接列表		//List<URL> urls = UrlUtils.parseURLs(address, map);
	4、遍历链接列表,并根据条件决定是否将其添加到 registryList	(注册集合)   List<URL>

------------------------分-------割--------线-----------------------------
2.1.3组装URL
配置检查完毕之后,根据配置以及一些其他信息,组装URL。URL是Dubbo配置的载体,通过URL可以让Dubbo的各种配置,在各个模块之间传递。URL 非常重要。下面是URL的组装过程。

doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
如果协议名为空,或空串,则将协议名变量设置为 dubbo
添加 side、版本、时间戳以及进程号等信息到 map 中
通过反射将对象的字段信息添加到 map 中
检测 <dubbo:method> 标签中的配置信息,并将相关配置添加到 map 中。
methods 为 MethodConfig 集合 ,MethodConfig中 存储了<dubbo:method> 的配置信息
检测 generic(通用的) 是否为 "true",并根据检测结果向 map 中添加不同的信息
添加 token 到 map 中
官方文档还有 (判断协议名是否为 injvm 也改到下面了、获取上下文路径 下面)
// 上述的大量文字只做了一件事,就是把版本、时间戳、方法名、以及各种配置对象信息放入Map中。
//配置对象信息有点笼统,其实就是你配置文件里写的标签。比如:<dubbo:application name="demo-provider"/>,对应源码里的application实体。<dubbo:protocol name="dubbo" host="192.168.0.29" port="20880" />,对应源码里的ProtocolConfig实体。这样是不是好理解多了。
获取IP
获取端口
URL 的构造方法 (协议名、IP、端口、上下文路径、map )   创建URL对象
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
到此-->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);
        }
        //  作用范围三个取值(none:什么都不做、scope != remote,导出到本地scope != local,导出到远程)
        String scope = url.getParameter(SCOPE_KEY);
        //如果scope !=none 则进入方法
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
            // export to local if the config is not remote (export to remote only when config is remote)
            // scope != remote 导出到本地
            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  导出到远程
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                // 日志
                if (!isOnlyInJvm() && logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        //if protocol is only injvm ,not register
                        // 如果协议只是 injvm , 而不是redister
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        // 加载监视器链接
                        URL monitorUrl = loadMonitor(registryURL);
                        // 如果 monitor不等于null     将监视器链接作为参数添加到URL中
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(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(PROXY_KEY);
                        //如果秘钥 不为空
                        if (StringUtils.isNotEmpty(proxy)) {
                            // 把秘钥 添加到 registryURL  的map参数中
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }
                        // 为服务提供类(ref),生成Imvoker
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        // DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                        //导出 服务  并生成exporter
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                    //不存在注册中心 ,仅仅导出服务
                } else {
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
                // 从 2.7.0版本 开始有了服务数据的存储
                MetadataReportService metadataReportService = null;
                if ((metadataReportService = getMetadataReportService()) != null) {
                    metadataReportService.publishProvider(url);
                }
            }
        }
}

Invoker
上一段代码看完的话,发现不管是导出到本地,还是导出到远程。进行服务导出之前,均需要先创建Invoker,这是一个很重要的步骤。
Dubbo 官方解释:
Invoker是实体域,它是Dubbo的核心模型,其他模型都向它靠拢,或者转换成它。它代表一个可执行体,可向它发起Invoker调用,这个调用有可能是一个本地的实现,也有可能是一个远程的实现,也有可能是一个集群的实现。
Invoker是ProxyFactory创建的,Dubbo的默认代理工厂实现类是JavassistProxyFactory,对了说到这里,大家都知道的动态代理有三种(JDK的基于接口的动态代理、CGLib基于父类的动态代理、javassist基于字节码的动态代理)Dubbo源码中的动态代理默认采用的是javassist动态生成字节码来实现的。这个可以在配置文件中指定动态代理的方式,只不过一般没人用。

<!-- 默认情况下使用Javassist来进行动态代理-->
<dubbo:provider proxy="jdk" /><dubbo:consumer proxy="jdk" /> 

下面进行Invoker的分析

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        // 为目标类创建 Wrapper(方法就是,从缓存中获取Wrepper ,如果缓存中没有,创建,写入缓存)
        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);
            }
        };
    }

------------------------分-------割--------线-----------------------------
这里分析的是getWrapper方法中,如果缓存没有命中,创建Wrapper的方法makeWrapper();

//       makeWrapper();方法分块理解(其实就是class的生成)	
    //一
        进行一些初始化操作,创建c1、c2、c3、以及pts、ms、mns、等变量以及向c1、c2、c3中添加方法定义和类型装换代码。
    //二
        用于public级别的的字段生成天剑判断取值与赋值代码。
    //三
        为定义在当前类的方法生成判断语句,和方法调用语句。(需要对方法的重载进行校验)
    //四
        用于处理getter、setter以及以is\has\can开头的方法。处理方式是通过正则表达式获取方法的类型,以及属性名。之后为属性名生成判断语句,然后为方法生成调用语句。
    //五
        通过ClassGenerator 为刚刚生成的代码构建Class类,并通过反射创建对象。ClassGenerator 是Dubbo自己封装的。
**导出服务到本地**
//导出服务到本地(暴露在JVM中,不需要网络通信)
     // 创建一个新的URL 将协议头、Host、端口设置成新的值(指向本地的值),
     // 然后创建Invoker,InjvmProtocol的 export方法导出服务  
     private void exportLocal(URL url) {
        URL local = URLBuilder.from(url)
                .setProtocol(LOCAL_PROTOCOL)   //设置协议头为 injvm
                .setHost(LOCALHOST_VALUE)      //设置Host为localhost
                .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);
    }

InjvmProtocol 的 export 方法仅创建了一个 InjvmExporter,无其他逻辑。到此导出服务到本地就完了
------------------------分-------割--------线-----------------------------
导出服务到远程
//导出服务到远程(将信息暴露给远程客户端,调用时需要网络通信启动netty,打开端口)javassist生成字节码实现代理

	打印日志,判断registryURLs不为空,则遍历,获取到每一个注册中心的registryURL对象
    如果协议只是 injvm , 而不是redister,continue;
	加载monitor链接,如果monitor链接!=null,把monitor链接作为参数添加到url中
    //获取代理秘钥,对于提供程序,这用于启动自定义代理来生成调用程序。
    String proxy = url.getParameter(PROXY_KEY);
    //如果秘钥 不为空
    if (StringUtils.isNotEmpty(proxy)) {
        // 把秘钥 添加到 registryURL  的map参数中
        registryURL = registryURL.addParameter(PROXY_KEY, proxy);
    }
	// 为服务提供类(ref),生成Imvoker
    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
    // DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    //导出 服务  并生成exporter
    Exporter<?> exporter = protocol.export(wrapperInvoker);
    exporters.add(exporter);
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值