​ 上一章分析了服务暴露的源码,这一章继续分析服务引用的源码。在Dubbo中有两种引用方式:第一种是服务直连,第二种是基于注册中心进行引用。服务直连一般用在测试的场景下,线上更多的是基于注册中心的方式。

​ 服务的引用分为饿汉式和懒汉式,饿汉即调用ReferenceBean的afterPropertiesSet方法时引用;懒汉即ReferenceBean对应的服务被注入到其他类时引用,也就是用到了这个服务才会引用。Dubbo默认是懒汉式引用。

​ 同样的,可以在DubboNamespaceHandler里面找到服务引用的入口,即ReferenceBean类。入口方法为getObject:

  1. public Object getObject() throws Exception {
  2. return get();
  3. }
  4. public synchronized T get() {
  5. if (destroyed) {
  6. throw new IllegalStateException("Already destroyed!");
  7. }
  8. if (ref == null) {
  9. init();
  10. }
  11. return ref;
  12. }

​ 以上两个方法比较简单,如果ref不为空就进入初始化方法。但是这里有个bug,如果Dubbo源码在2.6.5以下,debug调试时,ref不为空,也就不会进入到init方法了。解决方法是:断点直接打在init方法那一行。或者在IDEA的设置里面做如下设置:

0.处理配置

​ 进入到init方法,这个方法做的工作比较多,总的来说就是从各种地方获取配置信息,详细解释如下:

  1. private void init() {
  2. //避免重复初始化
  3. if (initialized) {
  4. return;
  5. }
  6. initialized = true;
  7. //检查接口名是否合法
  8. if (interfaceName == null || interfaceName.length() == 0) {
  9. throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
  10. }
  11. // get consumer's global configuration
  12. //略。。。
  13. //从系统变量中获取与接口名对应的属性值
  14. String resolve = System.getProperty(interfaceName);
  15. String resolveFile = null;
  16. //如果没有获取到,就去加载
  17. if (resolve == null || resolve.length() == 0) {
  18. //从系统变量中获取获取解析文件路径
  19. resolveFile = System.getProperty("dubbo.resolve.file");
  20. if (resolveFile == null || resolveFile.length() == 0) {
  21. //如果没获取到,就从指定位置加载
  22. File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
  23. if (userResolveFile.exists()) {
  24. //获取绝对路径
  25. resolveFile = userResolveFile.getAbsolutePath();
  26. }
  27. }
  28. if (resolveFile != null && resolveFile.length() > 0) {
  29. Properties properties = new Properties();
  30. FileInputStream fis = null;
  31. try {
  32. fis = new FileInputStream(new File(resolveFile));
  33. //从文件中加载配置
  34. properties.load(fis);
  35. } catch (IOException e) {
  36. throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
  37. } finally {
  38. try {
  39. if (null != fis) fis.close();
  40. } catch (IOException e) {
  41. logger.warn(e.getMessage(), e);
  42. }
  43. }
  44. //获取与接口对应的配置
  45. resolve = properties.getProperty(interfaceName);
  46. }
  47. }
  48. if (resolve != null && resolve.length() > 0) {
  49. url = resolve;
  50. //打印日志,略。。。
  51. }
  52. //-------------------------------------------------------------------------------------//
  53. //获取application、registries、monitor信息,如果没有就为null
  54. //略。。。
  55. //检查application合法性
  56. checkApplication();
  57. checkStubAndMock(interfaceClass);
  58. //-------------------------------------------------------------------------------------//
  59. //添加side、协议版本、时间戳、进程号等信息到map中
  60. Map<String, String> map = new HashMap<String, String>();
  61. Map<Object, Object> attributes = new HashMap<Object, Object>();
  62. map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
  63. map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
  64. map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
  65. if (ConfigUtils.getPid() > 0) {
  66. map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
  67. }
  68. //非泛化服务(什么是泛化,最后解析)
  69. if (!isGeneric()) {
  70. //获取版本
  71. String revision = Version.getVersion(interfaceClass, version);
  72. if (revision != null && revision.length() > 0) {
  73. map.put("revision", revision);
  74. }
  75. //获取方法列表,并添加到map中
  76. String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
  77. if (methods.length == 0) {
  78. logger.warn("NO method found in service interface " + interfaceClass.getName());
  79. map.put("methods", Constants.ANY_VALUE);
  80. } else {
  81. map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
  82. }
  83. }
  84. //添加前面获取的各种信息到map中
  85. map.put(Constants.INTERFACE_KEY, interfaceName);
  86. appendParameters(map, application);
  87. appendParameters(map, module);
  88. appendParameters(map, consumer, Constants.DEFAULT_KEY);
  89. appendParameters(map, this);
  90. //-------------------------------------------------------------------------------------//
  91. //遍历MethodConfig,处理事件通知配置
  92. //略。。。
  93. //-------------------------------------------------------------------------------------//
  94. //获取消费者IP地址
  95. String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
  96. if (hostToRegistry == null || hostToRegistry.length() == 0) {
  97. hostToRegistry = NetUtils.getLocalHost();
  98. } else if (isInvalidLocalHost(hostToRegistry)) {
  99. throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
  100. }
  101. map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
  102. //存储属性值到系统上下文中,即前面获取的各种属性值
  103. StaticContext.getSystemContext().putAll(attributes);
  104. //创建代理对象
  105. ref = createProxy(map);
  106. ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
  107. ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
  108. }

​ 初始化配置工作大概就这么多,其中省略了一些代码,可以通过IDEA调试过一遍。下面的方法才是关键。

1.服务引用

​ 我们进入到createProxy方法中,其字面意思是创建代理对象,该方法还会调用其他方法构建以及合并Invoker实例。

  1. private T createProxy(Map<String, String> map) {
  2. URL tmpUrl = new URL("temp", "localhost", 0, map);
  3. final boolean isJvmRefer;
  4. if (isInjvm() == null) {
  5. //如果url不为空,不做本地引用
  6. if (url != null && url.length() > 0) { // if a url is specified, don't do local reference
  7. isJvmRefer = false;
  8. //根据url的协议、scope、injvm等参数检查是否需要本地引用
  9. } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
  10. // by default, reference local service if there is
  11. isJvmRefer = true;
  12. } else {
  13. isJvmRefer = false;
  14. }
  15. } else {
  16. isJvmRefer = isInjvm().booleanValue();
  17. }
  18. //本地引用
  19. if (isJvmRefer) {
  20. //生成本地引用url,协议为injvm
  21. URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
  22. //构建InjvmInvoker实例
  23. invoker = refprotocol.refer(interfaceClass, url);
  24. if (logger.isInfoEnabled()) {
  25. logger.info("Using injvm service " + interfaceClass.getName());
  26. }
  27. //远程引用
  28. } else {
  29. //如果url不为空,说明可能是直连服务(即我们在调用时写明了Provider的地址)
  30. if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
  31. String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
  32. if (us != null && us.length > 0) {
  33. for (String u : us) {
  34. URL url = URL.valueOf(u);
  35. if (url.getPath() == null || url.getPath().length() == 0) {
  36. //设置接口全限定名为url路径
  37. url = url.setPath(interfaceName);
  38. }
  39. //检查url协议是否为registry,如果是,代表用户是想使用指定的注册中心,而非直连服务
  40. if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
  41. urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
  42. } else {
  43. urls.add(ClusterUtils.mergeUrl(url, map));
  44. }
  45. }
  46. }
  47. } else {
  48. //加载注册中心url
  49. List<URL> us = loadRegistries(false);
  50. if (us != null && us.size() > 0) {
  51. for (URL u : us) {
  52. URL monitorUrl = loadMonitor(u);
  53. if (monitorUrl != null) {
  54. map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
  55. }
  56. //添加refer参数到url中,并把url添加到urls中
  57. urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
  58. }
  59. }
  60. //如果没有注册中心,就抛出异常
  61. if (urls == null || urls.size() == 0) {
  62. 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.");
  63. }
  64. }
  65. //如果只有一个注册中心,或者是服务直连
  66. if (urls.size() == 1) {
  67. //构建Invoker实例
  68. invoker = refprotocol.refer(interfaceClass, urls.get(0));
  69. } else {
  70. //多个注册中心或者多个服务提供者
  71. List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
  72. URL registryURL = null;
  73. //获取所有的Invoker实例
  74. for (URL url : urls) {
  75. invokers.add(refprotocol.refer(interfaceClass, url));
  76. if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
  77. registryURL = url; // use last registry url
  78. }
  79. }
  80. if (registryURL != null) { // registry url is available
  81. // use AvailableCluster only when register's cluster is available
  82. URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
  83. invoker = cluster.join(new StaticDirectory(u, invokers));
  84. } else { // not a registry url
  85. invoker = cluster.join(new StaticDirectory(invokers));
  86. }
  87. }
  88. }
  89. //略。。。
  90. //返回代理对象
  91. return (T) proxyFactory.getProxy(invoker);
  92. }

​ createProxy方法的主要作用就是先区分是本地引用还是远程引用,接着区分是直连还是注册中心、是多注册中心(服务)还是单注册中心。最终都会构建出Invoker实例,并返回生成的代理类。Invoker是Dubbo的核心,在消费者这边,Invoker用于执行远程调用,其是由Protocol实现类构建而来。Protocol实现类有很多,主要由协议区分,分析服务暴露时我们也看到过。关于消费者这边的Invoker创建,为了不影响文章的流畅性,放到最后再分析。

2.创建代理

​ Invoker创建完毕后,接下来就是为服务接口生成代理对象。我们找到getProxy的实现方法,在AbstractProxyFactory抽象类中,这个类中的getProxy方法主要是获取接口列表,最后还会调用一个getProxy的抽象方法,这个抽象方法的实现方法在JavassistProxyFactory类中,如下:

  1. public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
  2. //通过newInstance创建代理对象
  3. return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
  4. }

​ 虽然这个Proxy并非Java中的,而是Dubbo自己封装的,但是依旧是基于Java反射实现的。InvokerInvocationHandler则完全属于Java动态代理的内容了,主要是实现invoke方法,从而实现调用目标方法。可以在调用目标方法前后添加一些额外的方法,这也是AOP的原理。

疑问解析

  • 消费者的Invoker是怎么创建的?

    • 从上面的源码分析中的createProxy方法可以发现,消费者的Invoker是由refer方法构建。不同的协议会进入不同的Protocol实现类中,调用对应的refer方法。debug源码可以发现,url的协议是registry,我们进入到对应的实现类。

      1. public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
      2. //设置协议头,此时url=zookeeper://192.168.174.128:2181/com.alibaba.dubbo.registry.RegistryService......
      3. url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
      4. //获取注册中心
      5. Registry registry = registryFactory.getRegistry(url);
      6. if (RegistryService.class.equals(type)) {
      7. return proxyFactory.getInvoker((T) registry, type, url);
      8. }
      9. //将url上的参数转化为Map
      10. Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
      11. //获取group配置
      12. String group = qs.get(Constants.GROUP_KEY);
      13. if (group != null && group.length() > 0) {
      14. if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
      15. || "*".equals(group)) {
      16. return doRefer(getMergeableCluster(), registry, type, url);
      17. }
      18. }
      19. //继续执行服务引用逻辑
      20. return doRefer(cluster, registry, type, url);
      21. }

      接着进入doRefer方法:

      1. private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
      2. //创建RegistryDirectory实例
      3. RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
      4. //设置注册中心和协议
      5. directory.setRegistry(registry);
      6. directory.setProtocol(protocol);
      7. // all attributes of REFER_KEY
      8. Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
      9. //生成消费者URL
      10. URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
      11. //注册消费者服务,存在consumer节点
      12. if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
      13. && url.getParameter(Constants.REGISTER_KEY, true)) {
      14. registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
      15. Constants.CHECK_KEY, String.valueOf(false)));
      16. }
      17. //订阅providers、configuration、routers等节点数据
      18. directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
      19. Constants.PROVIDERS_CATEGORY
      20. + "," + Constants.CONFIGURATORS_CATEGORY
      21. + "," + Constants.ROUTERS_CATEGORY));
      22. //注册中心可能存在多个服务提供者,需要合并为一个Invoker
      23. Invoker invoker = cluster.join(directory);
      24. ProviderConsumerRegTable.registerConsuemr(invoker, url, subscribeUrl, directory);
      25. return invoker;
      26. }

      最后,我们启动消费者,打开Zookeeper客户端看一下consumer节点下是否有消费者的信息:

    • 需要注意一点,要让消费者保持运行,不然看不到。

    • 看到这里其实有点疑惑,没看到RegistryProtocol启动网络连接,为什么就和zk通上信了?其实在上面那个方法中,还调用了DubboProtocol的refer方法,启动Server就在DubboProtocol中。我们接着分析一下,进入DubboProtocol的refer方法:

      1. public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
      2. optimizeSerialization(url);
      3. //创建DubboInvoker
      4. DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
      5. invokers.add(invoker);
      6. return invoker;
      7. }
    • 这个方法比较简单,关键在于getClients。它会获取通信客户端实例,类型为ExchangeClient,但是ExchangeClient并不具备通信能力,其底层默认是Netty客户端。我们看一下getClients方法:

      1. private ExchangeClient[] getClients(URL url) {
      2. // whether to share connection
      3. boolean service_share_connect = false;
      4. int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
      5. // if not configured, connection is shared, otherwise, one connection for one service
      6. if (connections == 0) {
      7. service_share_connect = true;
      8. connections = 1;
      9. }
      10. ExchangeClient[] clients = new ExchangeClient[connections];
      11. for (int i = 0; i < clients.length; i++) {
      12. if (service_share_connect) {
      13. //获取共享客户端
      14. clients[i] = getSharedClient(url);
      15. } else {
      16. //初始化新的客户端
      17. clients[i] = initClient(url);
      18. }
      19. }
      20. return clients;
      21. }
    • getSharedClient方法也会调用initClient方法,我们直接分析一下initClient方法:

      1. private ExchangeClient initClient(URL url) {
      2. // 获取客户端类型,默认为Netty
      3. String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
      4. String version = url.getParameter(Constants.DUBBO_VERSION_KEY);
      5. boolean compatible = (version != null && version.startsWith("1.0."));
      6. //添加编解码和心跳包参数到url中
      7. url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
      8. url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
      9. // BIO is not allowed since it has severe performance issue.
      10. if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
      11. throw new RpcException("Unsupported client type: " + str + "," +
      12. " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
      13. }
      14. ExchangeClient client;
      15. try {
      16. // connection should be lazy
      17. if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
      18. client = new LazyConnectExchangeClient(url, requestHandler);
      19. } else {
      20. //创建普通的ExchangeClient实例
      21. client = Exchangers.connect(url, requestHandler);
      22. }
      23. } catch (RemotingException e) {
      24. throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
      25. }
      26. return client;
      27. }
    • 接下来就比较简单了,可以自己debug一层层跟进connect方法,就会看到NettyClient的构造方法。

Dubbo服务引用源码解析③的更多相关文章

  1. Dubbo服务暴露源码解析②

    目录 0.配置解析 1.开始export 2.组装URL 3.服务暴露 疑问解析 ​ 先放一张官网的服务暴露时序图,对我们梳理源码有很大的帮助.注:不论是暴露还是导出或者是其他翻译,都是描述expor ...

  2. 13.1 dubbo服务降级源码解析

    从 9.1 客户端发起请求源码 的客户端请求总体流程图中,截取部分如下: //代理发出请求 proxy0.sayHello(String paramString) -->InvokerInvoc ...

  3. Netty5服务端源码解析

    Netty5源码解析 今天让我来总结下netty5的服务端代码. 服务端(ServerBootstrap) 示例代码如下: import io.netty.bootstrap.ServerBootst ...

  4. SpringCloud服务调用源码解析汇总

    相信我,你会收藏这篇文章的,本篇文章涉及Ribbon.Hystrix.Feign三个组件的源码解析 Ribbon架构剖析 这篇文章介绍了Ribbon的基础架构,也就是下图涉及到的6大组件: Ribbo ...

  5. Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器解析)

    make解析 服务容器对对象的自动解析是服务容器的核心功能,make 函数.build 函数是实例化对象重要的核心,先大致看一下代码: public function make($abstract) ...

  6. Laravel开发:Laravel核心——Ioc服务容器源码解析(服务器绑定)

    服务容器的绑定 bind 绑定 bind 绑定是服务容器最常用的绑定方式,在 上一篇文章中我们讨论过,bind 的绑定有三种: 绑定自身 绑定闭包 绑定接口 今天,我们这篇文章主要从源码上讲解 Ioc ...

  7. nacos服务注册源码解析

    1.客户端使用 compile 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2.2.3.RELEASE' compi ...

  8. Nacos服务发现源码解析

    1.Spring服务发现的统一规范 Spring将这套规范定义在Spring Cloud Common中 discovery包下面定义了服务发现的规范 核心接口:DiscoveryClient 用于服 ...

  9. 第零章 dubbo源码解析目录

    第一章 第一个dubbo项目 第二章  dubbo内核之spi源码解析 2.1  jdk-spi的实现原理 2.2 dubbo-spi源码解析 第三章 dubbo内核之ioc源码解析 第四章 dubb ...

随机推荐

  1. 编程C语言进阶篇——自定义数据类型:共同体

    什么是"自定义数据类型"?顾名思义,就是用户可以随时在程序中自行定义新的数据类型.自定义数据类型时需要设置数据类型的名称及其成员.数据类型成员各属性的设置方法等同于变量设置时相应属 ...

  2. 磁盘冗余阵列之RAID5、RAID10

    RAID技术主要有以下三个基本功能: (1).通过对磁盘上的数据进行条带化,实现对数据成块存取,减少磁盘的机械寻道时间,提高了数据存取速度. (2).通过对一个阵列中的几块磁盘同时读取,减少了磁盘的机 ...

  3. Windows 的这款工具,有时让我觉得 Mac 不是很香

    上次写了个 cheat.sh 在手,天下我有,小伙伴们热情高涨,觉得这是一个没有杂质的好工具:也有小伙伴抱怨说对 Windows 用户不是特别友好 (其实用 curl API 是没啥问题的).为了「雨 ...

  4. JZOJ 【NOIP2017提高A组模拟9.14】捕老鼠

    JZOJ [NOIP2017提高A组模拟9.14]捕老鼠 题目 Description 为了加快社会主义现代化,建设新农村,农夫约(Farmer Jo)决定给农庄里的仓库灭灭鼠.于是,猫被农夫约派去捕 ...

  5. Pentaho Report Designer 报表系统 - 入门详解

    目录 简介 安装与配置 环境要求 运行方式 使用教学 数据源配置与原始数据获取 报表布局设计与格式化 布局设计 模块结构 控件 示例 报表预览与发布 报表访问与获取 参考材料 简介 ​ Pentaho ...

  6. Robot framework 环境搭建+图标处理

    场景:随着现在项目各种赶工,很多时候界面上的功能还没有实现,这时就可以先对接口进行验证,提早发现一些和预期不一致的错误. Robot framework需要的几个知识点: 测试库:RF是大树,测试库就 ...

  7. Python追加文件内容

    测试中需要造几百个账号,写了个脚本可以自动生成账号,但想把生成的账号写入一个文件, 开始用的如下的write()方法,发下会先把原文件的内容清空再写入新的东西,文件里面每次都是最新生成的一个账号 mo ...

  8. 归并排序(c++,递归)

    放上c++代码模板(但是该版本中,还可以再进一步优化成原地算法,即不开辟新的空间:本代码中空间复杂度为n,不是1) 1 #include <iostream> 2 #include< ...

  9. Java基础学习之基础概念与环境搭建(1)

    1.Java基础概念 1.1.Java语言的特点 Java语言是简单易学的 Java语言是面向对象(封装.继承和多态) Java语言是平台无关的(一次编译,到处运行) Java语言是可靠的.安全的(异 ...

  10. (数据科学学习手札99)掌握pandas中的时序数据分组运算

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 我们在使用pandas分析处理时间序列数据 ...