dubbo客户端源码分析(一)

rpc框架有很多,公司自研、开源的thrift、dubbo、grpc等。我用过几个框架,了解了一下实现原理,客户端基本都是用代理实现,jdk动态代理、cglib等。最近一段时间想了解一下dubbo源码,看下工作原理。今天看了一下客户端初始化源码

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <dubbo:application name="demo-consumer"/>
    <!--<dubbo:registry address="multicast://224.5.6.7:1234"/>-->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    <dubbo:reference id="demoService" interface="com.gxf.dubbo.demo.DemoSerivce"/>
</beans>

这个是dubbo客户端配置,注册中心是本地zk。其中,dubbo是阿里基于spring扩展的schema

https://gist.github.com/dchjmichael/07dfd189c4c29bab63ec

这个文档关于spring schema扩展用法写的很不错,要定义xsd, handler, 解析xml的parser

http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

这是dubbo的spring-handler.xml,可以找到spring handler

 1 public class DubboNamespaceHandler extends NamespaceHandlerSupport {
 2
 3     static {
 4         Version.checkDuplicate(DubboNamespaceHandler.class);
 5     }
 6
 7     public void init() {
 8         registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
 9         registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
10         registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
11         registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
12         registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
13         registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
14         registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
15         registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
16         registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
17         registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
18     }
19
20 }

这里我们关注客户端,看reference相关的就可以了,看下DubboBeanDefinitionParser

类的内容有点多,主要工作是注入了一个ReferenceBean的bean

我们可以看下这个类

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {

这个类实现了FactoryBean, InitializingBean这个就有点生成动态代理的套路了

我们看getObject()方法

public Object getObject() throws Exception {
        return get();
    }

进入get()方法

public synchronized T get() {
        if (destroyed) {
            throw new IllegalStateException("Already destroyed!");
        }
        if (ref == null) {
            init();
        }
        return ref;
    }

跟进init()方法

ref = createProxy(map);

init()方法内容有点多,主要看下这个段, ref也是getBean()返回的对象,这里看方法名可以推测是用的代理

private T createProxy(Map<String, String> map) {
        URL tmpUrl = new URL("temp", "localhost", 0, map);
        final boolean isJvmRefer;
        if (isInjvm() == null) {
            if (url != null && url.length() > 0) { //指定URL的情况下,不做本地引用
                isJvmRefer = false;
            } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
                //默认情况下如果本地有服务暴露,则引用本地服务.
                isJvmRefer = true;
            } else {
                isJvmRefer = false;
            }
        } else {
            isJvmRefer = isInjvm().booleanValue();
        }

        if (isJvmRefer) {
            URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
            invoker = refprotocol.refer(interfaceClass, url);
            if (logger.isInfoEnabled()) {
                logger.info("Using injvm service " + interfaceClass.getName());
            }
        } else {
            if (url != null && url.length() > 0) { // 用户指定URL,指定的URL可能是对点对直连地址,也可能是注册中心URL
                String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
                if (us != null && us.length > 0) {
                    for (String u : us) {
                        URL url = URL.valueOf(u);
                        if (url.getPath() == null || url.getPath().length() == 0) {
                            url = url.setPath(interfaceName);
                        }
                        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                            urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                        } else {
                            urls.add(ClusterUtils.mergeUrl(url, map));
                        }
                    }
                }
            } else { // 通过注册中心配置拼装URL
                List<URL> us = loadRegistries(false);
                if (us != null && us.size() > 0) {
                    for (URL u : us) {
                        URL monitorUrl = loadMonitor(u);
                        if (monitorUrl != null) {
                            map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                        }
                        urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    }
                }
                if (urls == null || urls.size() == 0) {
                    throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
                }
            }

            if (urls.size() == 1) {
                invoker = refprotocol.refer(interfaceClass, urls.get(0));
            } else {
                List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
                URL registryURL = null;
                for (URL url : urls) {
                    invokers.add(refprotocol.refer(interfaceClass, url));
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        registryURL = url; // 用了最后一个registry url
                    }
                }
                if (registryURL != null) { // 有 注册中心协议的URL
                    // 对有注册中心的Cluster 只用 AvailableCluster
                    URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                    invoker = cluster.join(new StaticDirectory(u, invokers));
                } else { // 不是 注册中心的URL
                    invoker = cluster.join(new StaticDirectory(invokers));
                }
            }
        }

        Boolean c = check;
        if (c == null && consumer != null) {
            c = consumer.isCheck();
        }
        if (c == null) {
            c = true; // default true
        }
        if (c && !invoker.isAvailable()) {
            throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
        }
        if (logger.isInfoEnabled()) {
            logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
        }
        // 创建服务代理
        return (T) proxyFactory.getProxy(invoker);
    }

这个方法内容也挺多,今天没看完。应该是代理模式,

这里有个有意思的地方是,如果服务端没有启动在zk中注册,这里生成客户端代理的时候会抛异常。这个是后面需要去分析源码的,还有server端如何暴露服务的,即监听对应的端口,接收客户端的请求。以及在zk里面保存的内容

今天还发现一个有意思的地方,dubbo里面有泛化调用。看了泛化调用部分代码,感觉公司用的泛化调用,应该是参考了dubbo的

原文地址:https://www.cnblogs.com/luckygxf/p/9966915.html

时间: 2024-10-11 21:15:31

dubbo客户端源码分析(一)的相关文章

Eoe客户端源码分析---SlidingMenu的使用

Eoe客户端源码分析及代码注释 使用滑动菜单SlidingMenu,单击滑动菜单的不同选项,可以通过ViewPager和PagerIndicator显示对应的数据内容. 0  BaseSlidingFragmentActivity.java 主要函数: (1)showMenu() /** * Opens the menu and shows the menu view.*/ public void showMenu() { showMenu(true); } (2)showContent() /

Eureka 系列(02)客户端源码分析

Eureka 系列(02)客户端源码分析 [TOC] 在上一篇 Eureka 系列(01)最简使用姿态 中对 Eureka 的简单用法做了一个讲解,本节分析一下 EurekaClient 的实现 DiscoveryClient.本文的源码是基于 Eureka-1.9.8. 1)服务注册(发送注册请求到注册中心) 2)服务发现(本质就是获取调用服务名所对应的服务提供者实例信息,包括IP.port等) 3)服务续约(本质就是发送当前应用的心跳请求到注册中心) 4)服务下线(本质就是发送取消注册的HT

开源中国 OsChina Android 客户端源码分析(9)下载APK功能

源码中用以下载客户端的类为DownloadService,是一个服务.如果你对android服务不够理解的话,建议先查阅下有关服务的知识点.源码分析如下: 1首先我们先来看下该服务中几个重写的方法: 1.1onCreate()中 首先声明了自定义的绑定器对象,并在自定义的绑定器中添加了几个界面可以访问服务的方法,我们发现在这几个方法中,目前实际用到的start()方法用以开始下载APK,其他的没有用到.获取通知管理器.设置服务为 非前台服务.代码注释中,火蚁表明了不确定性. 其实如果将服务设置为

开源中国 OsChina Android 客户端源码分析(10)双击退出程序

在源码中,火蚁完全封装了  双击退出程序的功能 : DoubleClickExitHelper类 该类的源码分析如下: 1  构造函数中传入了 设备上下文,实现退出功能的界面,完成消息处理器的初始化:既然要有提示条,那么也需要一个Toast对象. 2 既然完全封装,那么 我们需要将 按键的事件及按键码传进去,因为界面重写的onKeyDown 事件需要返回一个boolean值,那么在DoubleClickExitHelper类中也自定义了一个返回布尔值的onKeyDown 函数. 3onKeyDo

开源中国 OsChina Android 客户端源码分析(12)清理缓存

上一篇中 缓存对象中我们提到了,将对象数据缓存的地方有三个地方,这里我们详细的说下: 1(/data/data/com.xxx.xxx/cache) 应用私有的缓存目录,属于内部缓存,其他应用无法访问,一般存储较小的数据: 2(/mnt/sdcard/android/data/com.xxx.xxx/cache),应用私有的外部缓存目录. 3SD卡下的自定义目录,共用的. 要实现清除缓存,那么首先你需要知道: 1应用是否采用了缓存策略: 2缓存的数据是什么,在哪里进行了缓存? 3缓存大小的计算实

Eureka 客户端源码分析

Eureka作为服务注册中心,主要的功能是服务注册和服务发现,是微服务框架的基础功能和核心功能. Eureka的使用可参考: Eureka服务端:Spring Cloud Eureka Server使用(注册中心), Eureka客户端:Eureka Client的使用, Eureka服务端:Eureka的高可用 Eureka分为客户端和服务端,这里主要介绍客户端源码 1.Eureka客户端主要使用EnableDiscoveryClient注解 1)@EnableDiscoveryClient源

Dubbo实现源码分析

Dubbo概述 Dubbo是阿里巴巴开源出来的一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及作为SOA服务治理的方案.它的核心功能包括: #remoting:远程通讯基础,提供对多种NIO框架抽象封装,包括"同步转异步"和"请求-响应"模式的信息交换方式. #Cluster: 服务框架核心,提供基于接口方法的远程过程调用,包括多协议支持,并提供软负载均衡和容错机制的集群支持. #registry: 服务注册中心,使服务消费方能动态的查××

开源中国 OsChina Android 客户端源码分析(3)可以拖拽的ScrollView

oschina客户端滑动菜单的View的布局使用了可以拖拽的ScrollView,类文件为CustomerScrollView. 1 我们需要分析下为什么要用ScrollView?用过的其实很容易理解避免其内部的子View的布局较大,在较小设备上无法完全显示. 2实现可拖拽的效果,只是从用户体验角度去考虑的,接下来我们详细分析下其自定义的ScrollView. 2.1拖拽的目标是ScrollView内的菜单的布局View,所以在CustomerScrollView内的onFinishInflat

开源中国 OsChina Android 客户端源码分析(8)数据库Sqlite

1开源中国客户端使用的数据库部分的源码在net.oschina.app.db包下,两个类一个是用于管理数据库的创建类DatabaseHelper,继承SQLiteOpenHelper,另一个是用于数据库的增删改查的工具类NoteDatabase.那么数据库在开源中国源码中哪一模块用到了呢? 便签管理,便签是什么?就是一个记事本的功能o(^▽^)o 2关于SQLiteOpenHelper的使用,自己之前的项目中没有用到过,看了下,这里有个体会:当获取到SQLiteOpenHelper实例,并使用g