Dubbo架构设计、服务注册与消费源码剖析

本文知识点较多,篇幅较长,请耐心学习

题记:
文章内容输出来源:拉勾教育Java高薪训练营。
本篇文章是 Dubbo 学习课程中的一部分笔记。

Dubbo架构设计和服务注册与消费源码剖析

一、源码下载和编译

源码下载、编译和导入步骤如下:
(1)dubbo的项目在github中的地址为: https://github.com/apache/dubbo
(2)进入需要进行下载的地址,执行 git clone https://github.com/apache/dubbo.git
(3)为了防止master中代码不稳定,进入dubbo项目 cd dubbo 可以切入到最近的release分支 git checkout 2.7.6-release
(4)进行本地编译,进入dubbo项目 cd dubbo , 进行编译操作 mvn clean install -DskipTests
(5)使用IDE引入项目。

二、架构整体设计

1、Dubbo调用关系说明
在这里插入图片描述
在这里主要由四部分组成:

  • Provider: 暴露服务的服务提供方
    Protocol 负责提供者和消费者之间协议交互数据
    Service 真实的业务服务信息 可以理解成接口 和 实现
    Container Dubbo的运行环境
  • Consumer: 调用远程服务的服务消费方
    Protocol 负责提供者和消费者之间协议交互数据
    Cluster 感知提供者端的列表信息
    Proxy 可以理解成 提供者的服务调用代理类 由它接管 Consumer中的接口调用逻辑
  • Registry: 注册中心,用于作为服务发现和路由配置等工作,提供者和消费者都会在这里进行注册
  • Monitor: 用于提供者和消费者中的数据统计,比如调用频次,成功失败次数等信息。

启动和执行流程说明:
提供者端启动 容器负责把Service信息加载 并通过Protocol 注册到注册中心
消费者端启动 通过监听提供者列表来感知提供者信息 并在提供者发生改变时 通过注册中心及时通知消费端
消费方发起 请求 通过Proxy模块
利用Cluster模块 来选择真实的要发送给的提供者信息
交由Consumer中的Protocol 把信息发送给提供者
提供者同样需要通过 Protocol 模块来处理消费者的信息
最后由真正的服务提供者 Service 来进行处理

2、整体的调用链路
在这里插入图片描述
说明 淡绿色代表了 服务生产者的范围 淡蓝色 代表了服务消费者的范围 红色箭头代表了调用的方向
业务逻辑层 RPC层(远程过程调用) Remoting (远程数据传输)
整体链路调用的流程:
1.消费者通过Interface进行方法调用 统一交由消费者端的 Proxy 通过ProxyFactory 来进行代理对象的创建 使用到了 jdk javassist技术
2.交给Filter 这个模块 做一个统一的过滤请求 在SPI案例中涉及过
3.接下来会进入最主要的Invoker调用逻辑
通过Directory 去配置中新读取信息 最终通过list方法获取所有的Invoker
通过Cluster模块 根据选择的具体路由规则 来选取Invoker列表
通过LoadBalance模块 根据负载均衡策略 选择一个具体的Invoker 来处理我们的请求
如果执行中出现错误 并且Consumer阶段配置了重试机制 则会重新尝试执行
4.继续经过Filter 进行执行功能的前后封装 Invoker 选择具体的执行协议
5.客户端 进行编码和序列化 然后发送数据
6.到达Consumer中的 Server 在这里进行 反编码 和 反序列化的接收数据
7.使用Exporter选择执行器
8.交给Filter 进行一个提供者端的过滤 到达 Invoker 执行器
9.通过Invoker 调用接口的具体实现 然后返回

3、Dubbo源码整体设计
在这里插入图片描述
图例说明:

  • 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
  • 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
  • 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
  • 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。

各层说明:

  • config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
  • proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
  • registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
  • cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
  • monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
  • protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
  • exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
  • transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
  • serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool

关系说明:

  • 在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用,然后在 Invoker 的主过程上 Filter 拦截点。
  • 图中的 Consumer 和 Provider 是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用 Client 和 Server 的原因是 Dubbo 在很多场景下都使用 Provider, Consumer, Registry, Monitor 划分逻辑拓普节点,保持统一概念。
  • 而 Cluster 是外围概念,所以 Cluster 的目的是将多个 Invoker 伪装成一个 Invoker,这样其它人只要关注 Protocol 层 Invoker 即可,加上 Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要 Cluster 的。
  • Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将 Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以 Run 的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。
  • 而 Remoting 实现是 Dubbo 协议的实现,如果你选择 RMI 协议,整个 Remoting 都不会用上,Remoting 内部再划为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输,而 Exchange 层是在传输层之上封装了 Request-Response 语义。
  • Registry 和 Monitor 实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。

4、领域模型

在 Dubbo 的核心领域模型中:

  • Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
  • Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
  • Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。

5、基本设计原则

  • 采用 Microkernel + Plugin 模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。
  • 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。

三、服务注册与消费源码剖析

1、注册中心Zookeeper剖析

注册中心是Dubbo的重要组成部分,主要用于服务的注册与发现,我们可以选择Redis、Nacos、Zookeeper作为Dubbo的注册中心,Dubbo推荐用户使用Zookeeper作为注册中心。
注册中心Zookeeper目录结构:
在这里插入图片描述

  • 所有的都是在dubbo层级下的
  • dubbo跟节点下面是当前所拥有的接口名称,如果有多个接口,则会以多个子节点的形式展开
  • 每个服务下面又分别有四个配置项
    • consumers: 当前服务下面所有的消费者列表(URL)
    • providers: 当前服务下面所有的提供者列表(URL)
    • configuration: 当前服务下面的配置信息信息,provider或者consumer会通过读取这里的配置信息来获取配置
    • routers: 当消费者在进行获取提供者的时,会通过这里配置好的路由来进行适配匹配规则。
  • dubbo基本上很多时候都是通过URL的形式来进行交互获取数据的,在URL中也会保存很多的信息。

在这里插入图片描述

通过这张图我们可以了解到如下信息:

  • 提供者会在 providers 目录下进行自身的进行注册。
  • 消费者会在 consumers 目录下进行自身注册,并且监听 provider 目录,以此通过监听提供者增加或者减少,实现服务发现。
  • Monitor模块会对整个服务级别做监听,用来得知整体的服务情况。以此就能更多的对整体情况做监控。

2、服务的注册过程分析

服务注册(暴露)过程
在这里插入图片描述
首先 ServiceConfig 类拿到对外提供服务的实际类 ref(如:HelloServiceImpl),然后通过ProxyFactory 接口实现类中的 getInvoker 方法使用 ref 生成一个 AbstractProxyInvoker 实例,到这一步就完成具体服务到 Invoker 的转化。
下面我们就看一下Invoker 转换成 Exporter的过程:
其中会涉及到 RegistryService接口 RegistryFactory 接口 和 注册provider到注册中心流程的过程
(1)RegistryService代码解读,这块儿的代码比较简单,主要是对指定的路径进行注册,解绑,监听和取消监听,查询操作。也是注册中心中最为基础的类。

public interface RegistryService { 
	/**
	* 进行对URL的注册操作,比如provider,consumer,routers等 
	*/ 
	void register(URL url);
	/**
	* 解除对指定URL的注册,比如provider,consumer,routers等 
	*/ 
	void unregister(URL url); 
	/**
	* 增加对指定URL的路径监听,当有变化的时候进行通知操作 
	*/ 
	void subscribe(URL url, NotifyListener listener); 
	/**
	* 解除对指定URL的路径监听,取消指定的listener 
	*/
	void unsubscribe(URL url, NotifyListener listener); 
	/**
	* 查询指定URL下面的URL列表,比如查询指定服务下面的consumer列表 
	*/ 
	List<URL> lookup(URL url); 
}

(2)我们再来看 RegistryFactory ,是通过他来生成真实的注册中心。通过这种方式,也可以保证一个应用中可以使用多个注册中心。可以看到这里也是通过不同的protocol参数,来选择不同的协议。

@SPI("dubbo") 
public interface RegistryFactory { 
	/**
	* 获取注册中心地址 
	*/ 
	@Adaptive({"protocol"}) 
	Registry getRegistry(URL url); 
}

(3)下面我们就来跟踪一下,一个服务是如何注册到注册中心上去的。其中比较关键的一个类是RegistryProtocol ,他负责管理整个注册中心相关协议。并且统一对外提供服务。这里我们主要以RegistryProtocol.export 方法作为入口,这个方法主要的作用就是将我们需要执行的信息注册并且导出。

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { 
	// 获取注册中心的地址 
	URL registryUrl = getRegistryUrl(originInvoker);
	// 获取当前提供者需要注册的地址 
	URL providerUrl = getProviderUrl(originInvoker); 
	// 获取进行注册override协议的访问地址 
	URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl); 
	// 增加override的监听器 
	final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); 
	overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); 
	// 根据现有的override协议,对注册地址进行改写操作 
	providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
	// 对当前的服务进行本地导出 
	// 完成后即可在看到本地的20880端口号已经启动,并且暴露服务 
	final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); 
	// 获取真实的注册中心, 比如我们常用的ZookeeperRegistry 
	final Registry registry = getRegistry(originInvoker); 
	// 获取当前服务需要注册到注册中心的providerURL,主要用于去除一些没有必要的参数(比如在本 地导出时所使用的qos参数等值) 
	final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); 
	// 获取当前url是否需要进行注册参数 
	boolean register = providerUrl.getParameter(REGISTER_KEY, true); 
	if (register) { 
		// 将当前的提供者注册到注册中心上去 
		register(registryUrl, registeredProviderUrl); 
	}
	// 对override协议进行注册,用于在接收到override请求时做适配,这种方式用于适配2.6.x及之 前的版本(混用) 
	registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); 
	// 设置当前导出中的相关信息
	exporter.setRegisterUrl(registeredProviderUrl);
	exporter.setSubscribeUrl(overrideSubscribeUrl); 
	// 返回导出对象(对数据进行封装) 
	return new DestroyableExporter<>(exporter); 
}

(4)下面我们再来看看 register 方法, 这里面做的比较简单,主要是从 RegistoryFactory 中获取注册中心,并且进行地址注册

public void register(URL registryUrl, URL registeredProviderUrl) { 
	// 获取注册中心 
	Registry registry = registryFactory.getRegistry(registryUrl); 
	// 对当前的服务进行注册 
	registry.register(registeredProviderUrl); 
	// ProviderModel 表示服务提供者模型,此对象中存储了与服务提供者相关的信息。 
	// 比如服务的配置信息,服务实例等。每个被导出的服务对应一个 ProviderModel。 
	ProviderModel model = ApplicationModel.getProviderModel(registeredProviderUrl.getServiceKey()); 
	model.addStatedUrl(new ProviderModel.RegisterStatedURL( registeredProviderUrl, registryUrl, true )); 
}

(5)这里我们再跟里面的register方法之前,先来介绍一下Registry中的类目录结构
在这里插入图片描述
目录结构描述如下:

  • 在这里每个层级代表继承自父级
  • 这里面 RegistryService 就是我们之前所讲对外提供注册机制的接口。
  • 其下面 Registry 也同样是一个接口,是对 RegistryService 的集成,并且继承了 Node 接口,说明注册中心也是基于URL去做的。
  • AbstractRegistry 是对注册中心的封装,其主要会对本地注册地址的封装,主要功能在于远程注册中心不可用的时候,可以采用本地的注册中心来使用。
  • FailbackRegistry 从名字中可以看出来,失败自动恢复,后台记录失败请求,定时重发功能。
  • 最深的一层则更多是真实的第三方渠道实现

(6)下面我们来看一下在 FailbackRegistry 中的实现, 可以在这里看到他的主要作用是调用第三方的实现方式,并且在出现错误时增加重试机制。

public void register(URL url) { 
	// 上层调用 
	// 主要用于保存已经注册的地址列表 
	super.register(url);
	// 将一些错误的信息移除(确保当前地址可以在出现一些错误的地址时可以被删除) 
	removeFailedRegistered(url); 
	removeFailedUnregistered(url); 
	try {
		// 发送给第三方渠道进行注册操作 
		doRegister(url); 
	} catch (Exception e) { 
		Throwable t = e; 
		// 记录日志 
		boolean check = getUrl().getParameter(Constants.CHECK_KEY, true) && url.getParameter(Constants.CHECK_KEY, true) && !CONSUMER_PROTOCOL.equals(url.getProtocol()); 
		boolean skipFailback = t instanceof SkipFailbackWrapperException; 
		if (check || skipFailback) { 
			if (skipFailback) { 
				t = t.getCause(); 
			}
			throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t); 
		} else { 
			logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t); 
		}
		// 后台异步进行重试,也是Failback比较关键的代码 
		addFailedRegistered(url); 
	} 
}

(7)下面我们再来看看Zookeeper中 doRegister 方法的实现, 可以看到这里的实现也比较简单,关键在于 toUrlPath 方法的实现。关于 dynamic 的值,我们也在上面有看到,他的URL也是true的。

public void doRegister(URL url) { 
	try {
		// 进行创建地址 
		zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true)); 
	} catch (Throwable e) { 
		throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); 
	} 
}

(8)解读 toUrlPath 方法。可以看到这里的实现也是比较简单,也验证了我们之前的路径规则。

private String toUrlPath(URL url) { 
	// 分类地址 + url字符串 
	return toCategoryPath(url) + PATH_SEPARATOR + URL.encode(url.toFullString()); 
}
private String toCategoryPath(URL url) { 
	// 服务名称 + category(在当前的例子中是providers) 
	return toServicePath(url) + PATH_SEPARATOR + url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY); 
}
private String toServicePath(URL url) {
	// 接口地址 
	String name = url.getServiceInterface(); 
	if (ANY_VALUE.equals(name)) { 
		return toRootPath(); 
	}
	// 根节点 + 接口地址 
	return toRootDir() + URL.encode(name); 
}

3、URL规则详解

URL地址如下:
在这里插入图片描述
URL主要有以下几部分组成:

  • protocol: 协议,一般像我们的 provider 或者 consumer 在这里都是人为具体的协议
  • host: 当前 provider 或者其他协议所具体针对的地址,比较特殊的像 override 协议所指定的host就是 0.0.0.0 代表所有的机器都生效
  • port: 和上面相同,代表所处理的端口号
  • path: 服务路径,在 provider 或者 consumer 等其他中代表着我们真实的业务接口
  • key=value: 这些则代表具体的参数,这里我们可以理解为对这个地址的配置。比如我们 provider中需要具体机器的服务应用名,就可以是一个配置的方式设置上去。

注意:Dubbo中的URL与java中的URL是有一些区别的,如下:

  • 这里提供了针对于参数的 parameter 的增加和减少(支持动态更改)
  • 提供缓存功能,对一些基础的数据做缓存

4、服务本地缓存

Dubbo调用者需要通过注册中心(例如:ZK)注册信息,获取提供者,但是如果频繁往从ZK获取信息,肯定会存在单点故障问题,所以dubbo提供了将提供者信息缓存在本地的方法。
Dubbo在订阅注册中心的回调处理逻辑当中会保存服务提供者信息到本地缓存文件当中(同步/异步两种方式),以URL纬度进行全量保存。
Dubbo在服务引用过程中会创建registry对象并加载本地缓存文件,会优先订阅注册中心,订阅注册中心失败后会访问本地缓存文件内容获取服务提供信息。
(1)首先从构造方法讲起, 这里方法比较简单,主要用于确定需要保存的文件信息。并且从系统中读取已有的配置信息。

public AbstractRegistry(URL url) { 
	setUrl(url); 
	// Start file save timer 
	syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false); 
	// 默认保存路径(home/.dubbo/dubbo-registry-appName-address-port.cache) 
	String defaultFilename = System.getProperty("user.home") + "/.dubbo/dubbo- registry-" + url.getParameter(APPLICATION_KEY) + "-" + url.getAddress().replaceAll(":", "-") + ".cache"; 
	String filename = url.getParameter(FILE_KEY, defaultFilename); 
	// 创建文件 
	File file = null; 
	if (ConfigUtils.isNotEmpty(filename)) { 
		file = new File(filename); 
		if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) { 
			if (!file.getParentFile().mkdirs()) { 
				throw new IllegalArgumentException("Invalid registry cache file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!"); 
			} 
		} 
	}
	this.file = file; 
	// 加载已有的配置文件 
	loadProperties(); 
	notify(url.getBackupUrls()); 
}

(2)我们可以看到这个类中最为关键的一个属性为 properties ,我们可以通过寻找,得知这个属性的设置值只有在一个地方: saveProperties ,我们来看一下这个方法。这里也有一个我们值得关注的点,就是基于版本号的的更改。

private void saveProperties(URL url) { 
	if (file == null) { 
		return; 
	}
	try {
		StringBuilder buf = new StringBuilder(); 
		// 获取所有通知到的地址 
		Map<String, List<URL>> categoryNotified = notified.get(url); 
		if (categoryNotified != null) { 
			for (List<URL> us : categoryNotified.values()) { 
				for (URL u : us) { 
					// 多个地址进行拼接 
					if (buf.length() > 0) { 
						buf.append(URL_SEPARATOR); 
					}
					buf.append(u.toFullString()); 
				} 
			} 
		}
		// 保存数据 
		properties.setProperty(url.getServiceKey(), buf.toString());
		// 保存为一个新的版本号 
		// 通过这种机制可以保证后面保存的记录,在重试的时候,不会重试之前的版本 
		long version = lastCacheChanged.incrementAndGet(); 
		// 需要同步保存则进行保存 
		if (syncSaveFile) { 
			doSaveProperties(version); 
		} else { 
			// 否则则异步去进行处理 
			registryCacheExecutor.execute(new SaveProperties(version)); 
		} 
	} catch (Throwable t) { 
		logger.warn(t.getMessage(), t); 
	} 
}

(3)下面我们再来看看是如何进行保存文件的。这里的实现也比较简单,主要比较关键的代码在于利用文件级锁来保证同一时间只会有一个线程执行。

public void doSaveProperties(long version) { 
	if (version < lastCacheChanged.get()) { 
		return; 
	}if (file == null) { 
		return; 
	}
	// Save 
	try {
		// 使用文件级别所,来保证同一段时间只会有一个线程进行读取操作 
		File lockfile = new File(file.getAbsolutePath() + ".lock"); 
		if (!lockfile.exists()) { 
			lockfile.createNewFile(); 
		}
		try (RandomAccessFile raf = new RandomAccessFile(lockfile, "rw"); 
			FileChannel channel = raf.getChannel()) { 
			// 利用文件锁来保证并发的执行的情况下,只会有一个线程执行成功(原因在于可能是跨 VM的) 
			FileLock lock = channel.tryLock(); 
			if (lock == null) { 
				throw new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties"); 
			}
			// Save 
			try {
				if (!file.exists()) { 
					file.createNewFile(); 
				}
				// 将配置的文件信息保存到文件中 
				try (FileOutputStream outputFile = new FileOutputStream(file)) { 
					properties.store(outputFile, "Dubbo Registry Cache"); 
				} 
				} finally { 
					// 解开文件锁 
					lock.release(); 
				}
		} 
	} catch (Throwable e) { 
		// 执行出现错误时,则交给专门的线程去进行重试 
		savePropertiesRetryTimes.incrementAndGet(); 
		if (savePropertiesRetryTimes.get() >= MAX_RETRY_TIMES_SAVE_PROPERTIES) { 
			logger.warn("Failed to save registry cache file after retrying " + MAX_RETRY_TIMES_SAVE_PROPERTIES + " times, cause: " + e.getMessage(), e); 
			savePropertiesRetryTimes.set(0); 
			return; 
		}
		if (version < lastCacheChanged.get()) { 
			savePropertiesRetryTimes.set(0); 
			return; 
		} else { 
			registryCacheExecutor.execute(new SaveProperties(lastCacheChanged.incrementAndGet())); 
		}
		logger.warn("Failed to save registry cache file, will retry, cause: " + e.getMessage(), e); 
	} 
}

5、Dubbo 消费过程

服务消费流程
在这里插入图片描述
ReferenceConfig 类的 init 方法调用 createProxy() ,期间 使用 Protocol 调用 refer 方法生成 Invoker 实例(如上图中的红色部分),这是服务消费的关键。接下来使用ProxyFactory把 Invoker转换为客户端需要的接口(如:HelloService)

至此,结束


最后

在这个知识付费的时代,每一位热爱技术分享、奋笔直书的人,都值得我们尊敬!所以,请不要吝啬您手中的鼠标,按下左键,为小编点个赞吧。
更多内容,请关注微信公众号:架构视角

特别鸣谢

感谢程道老师风趣幽默的讲解,让我对所学知识点记忆深刻!
感谢木槿导师的认真和负责,每一次作业点评都是我前进的动力!
感谢班主任毕老师的负责和耐心,每次不厌其烦的上课通知都是我不忘初心,保持良好学习状态的精神支柱!
感谢拉勾教育平台,给我这次花少量的钱就能报名第一期拉钩训练营,就能学习到很多深层次的技术精华的机会。而且,在学习过程中还认识了很多技术大佬,可以请教他们一些问题,比如张大佬、卢大佬、雨生大佬等等。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值