概述
1. 简介
本文分析服务引用的原理。在 Dubbo 中,我们可以通过两种方式引用远程服务。第一种是使用服务直联的方式引用服务,第二种方式是基于注册中心进行引用。服务直联的方式仅适合在调试或测试服务的场景下使用,不适合在线上环境使用。因此,本文我将重点分析通过注册中心引用服务的过程。从注册中心中获取服务配置只是服务引用过程中的一环,除此之外,服务消费者还需要经历 Invoker 创建、代理类创建等步骤。这些步骤,我将在后续章节中一一进行分析。
2.服务引用原理
Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于,第一个是饿汉式的,第二个是懒汉式的。默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置 的 init 属性开启。下面我们按照 Dubbo 默认配置进行分析,整个分析过程从 ReferenceBean 的 getObject 方法开始。当我们的服务被注入到其他类中时,Spring 会第一时间调用 getObject 方法,并由该方法执行服务引用逻辑。按照惯例,在进行具体工作之前,需先进行配置检查与收集工作。接着根据收集到的信息决定服务用的方式,有三种,第一种是引用本地 (JVM) 服务,第二是通过直联方式引用远程服务,第三是通过注册中心引用远程服务。不管是哪种引用方式,最后都会得到一个 Invoker 实例。如果有多个注册中心,多个服务提供者,这个时候会得到一组 Invoker 实例,此时需要通过集群管理类 Cluster 将多个 Invoker 合并成一个实例。合并后的 Invoker 实例已经具备调用本地或远程服务的能力了,但并不能将此实例暴露给用户使用,这会对用户业务代码造成侵入。此时框架还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类,并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入,同时也让框架更容易使用。
以上就是 Dubbo 引用服务的大致原理,下面我们深入到代码中,详细分析服务引用细节。
3.源码分析
服务引用的入口方法为 ReferenceBean 的 getObject 方法,该方法定义在 Spring 的 FactoryBean 接口中,ReferenceBean 实现了这个方法。实现代码如下:
public Object getObject() throws Exception { return get();}public synchronized T get() { if (destroyed) { throw new IllegalStateException("Already destroyed!"); } // 检测 ref 是否为空,为空则通过 init 方法创建 if (ref == null) { // init 方法主要用于处理配置,以及调用 createProxy 生成代理类 init(); } return ref;}
这里两个方法代码都比较简短,并不难理解。不过这里需要特别说明一下,如果大家从 getObject 方法进行代码调试时,会碰到比较诧异的问题。这里假设你使用 IDEA,且保持了 IDEA 的默认配置。当你面调试到 get 方法的 if (ref == null) 时,你会惊奇的发现 ref 不为空,导致你无法进入到 init 方法中继续调试。导致这个现象的原因是 Dubbo 框架本身有点小问题,这个小问题会引发一些让人诧异的现象。关于这个问题,我进行了将近两个小时的排查。查明问题后,我给 Dubbo 提交了一个 pull request ( #2754 ) 修复了此问题。另外,beiwei30 前辈开了一个 issue ( #2757 ) 介绍这个问题,有兴趣的朋友可以去看看。大家如果想规避这个问题,可以修改一下 IDEA 的配置。在配置面板中搜索 toString,然后取消 Enable 'toString' object view 前的对号。具体如下:
讲完需要注意的点,我们继续向下分析,接下来将分析配置的处理过程。
3.1 处理配置
Dubbo 提供了丰富的配置,用于调整和优化框架行为,性能等。Dubbo 在引用或导出服务时,首先会对这些配置进行检查和处理,以保证配置到正确性。如果大家不是很熟悉 Dubbo 配置,建议先阅读以下官方文档。配置解析的方法为 ReferenceConfig 的 init 方法,下面来看一下方法逻辑。
private void init() { if (initialized) { return; } initialized = true; if (interfaceName == null || interfaceName.length() == 0) { throw new IllegalStateException("interface not allow null!"); } // 检测 consumer 变量是否为空,为空则创建 checkDefault(); appendProperties(this); if (getGeneric() == null && getConsumer() != null) { // 设置 generic setGeneric(getConsumer().getGeneric()); } // 检测是否为泛化接口 if (ProtocolUtils.isGeneric(getGeneric())) { interfaceClass = GenericService.class; } else { try { // 加载类 interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() .getContextClassLoader()); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } checkInterfaceAndMethods(interfaceClass, methods); } // -------------------------------:sparkles: 分割线1 :sparkles:------------------------------ // 从系统变量中获取与接口名对应的属性值 String resolve = System.getProperty(interfaceName); String resolveFile = null; if (resolve == null || resolve.length() == 0) { // 从系统属性中获取解析文件路径 resolveFile = System.getProperty("dubbo.resolve.file"); if (resolveFile == null || resolveFile.length() == 0) { // 从指定位置加载配置文件 File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties"); if (userResolveFile.exists()) { // 获取文件绝对路径 resolveFile = userResolveFile.getAbsolutePath(); } } if (resolveFile != null && resolveFile.length() > 0) { Properties properties = new Properties(); FileInputStream fis = null; try { fis = new FileInputStream(new File(resolveFile)); // 从文件中加载配置 properties.load(fis); } catch (IOException e) { throw new IllegalStateException("Unload ..., cause:..."); } finally { try { if (null != fis) fis.close(); } catch (IOException e) { logger.warn(e.getMessage(), e); } } // 获取与接口名对应的配置 resolve = properties.getProperty(interfaceName); } } if (resolve != null && resolve.length() > 0) { // 将 resolve 赋值给 url url = resolve; } // -------------------------------:sparkles: 分割线2 :sparkles:------------------------------ if (consumer != null) { if (application == null) { // 从 consumer 中获取 Application 实例,下同 application = consumer.getApplication(); } if (module == null) { module = consumer.getModule(); } if (registries == null) { registries = consumer.getRegistries(); } if (monitor == null) { monitor = consumer.getMonitor(); } } if (module != null) { if (registries == null) { registries = module.getRegistries(); } if (monitor == null) { monitor = module.getMonitor(); } } if (application != null) { if (registries == null) { registries = application.getRegistries(); } if (monitor == null) { monitor = application.getMonitor(); } } // 检测本地 Application 和本地存根配置合法性 checkApplication(); checkStubAndMock(interfaceClass); // -------------------------------:sparkles: 分割线3 :sparkles:------------------------------ Map map = new HashMap(); Map attributes = new HashMap(); // 添加 side、协议版本信息、时间戳和进程号等信息到 map 中 map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE); map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()); map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis())); if (ConfigUtils.getPid() > 0) { map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid())); } if (!isGeneric()) { // 非泛化服务 // 获取版本 String revision = Version.getVersion(interfaceClass, version); if (revision != null && revision.length() > 0) { map.put("revision
最后
以上就是害怕书本为你收集整理的script5007: 无法获取未定义或 null 引用的属性“call”_金九银十,一篇Dubbo 源码分析 - 服务引用给大家查漏补缺...1. 简介2.服务引用原理3.源码分析的全部内容,希望文章能够帮你解决script5007: 无法获取未定义或 null 引用的属性“call”_金九银十,一篇Dubbo 源码分析 - 服务引用给大家查漏补缺...1. 简介2.服务引用原理3.源码分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复