荐 【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析
文章目录
1 开篇
想要在一个JVM里调用另一个JVM里的的方法,或许你会想到如下的姿势:
- HttpClient
- RestTemplate
- WebService
- ServerSocket/Socket
- RMI — 可以参看我的上篇文章《【dubbo源码解析~番外篇】— JDK和Spring的RMI使用姿势简介》
但是在真正进行方法调用时,你不仅要考虑传什么参数、调用哪个方法,还得考虑对方服务的暴露地址等。
而用过dubbo的肯定都知道,它可以做到:在一个JVM(消费端)里调用另一个JVM(服务端)的方法,就如同你在另一个JVM(服务端)直接调自己的方法一样 —> 这究竟是如何做到的呢,本篇文章将主要来探索一下这个问题。
2 dubbo服务暴露过程底层原理探秘
2.1 spring环境下dubbo服务暴露过程的前置知识
2.1.1【spring解析要暴露服务的bean —> 进行服务暴露】 整体过程概览
无论是使用注解,还是xml配置的方式,大家必须要有的前置知识是,dubbo中一个要向外暴露的服务(service),会先被spring解析并包装为一个ServiceBean,然后再拿着该ServiceBean进行真正的服务暴露。在dubbo中真正进行服务暴露的源码如下:
其实在该段代码中还涉及到了几个重要的概念:
- (1)Invoker
- (2)ProxyFactory — 代理工厂,主要有两个作用:
- ① 生成Invoker
-
注意1:在provider端生成的Invoker(实际为AbstractProxyInvoker)里包含了
【具体要暴露的服务(即ref)】
、【服务的接口】
和【要暴露服务的url】
等 -
注意2:在consumer端生成的Invoker(实际为AbstractInvoker)里至少包含了
【服务的接口】
和【要暴露服务的url】
-
注意1:在provider端生成的Invoker(实际为AbstractProxyInvoker)里包含了
- ②生成服务的代理类
- 如果代理工厂选择的为JavassistProxyFactory,则为服务生成静态代理类
- 如果代理工厂选择的为JdkProxyFactory,则为服务生成动态代理类
- ① 生成Invoker
- (3)Protocol — 协议,也主要有两个作用:
- ① 拿着Invoker中的【url】 、【服务的接口】、和【根据该Invoker 用ProxyFactory生成的代理类】进行服务暴露
- ② 接收到消费者的请求后,直接将请求参数信息交给代理类 —> 然后代理类会将请求信息转给(2)中生成的Invoker,并在Invoker里调用具体的服务 —> 这里后面会继续深化!!!
2.1.2 ProxyFactory和Protocol接口简介
同时还需要注意 ProxyFactory 和 Protocol 都是dubbo的SPI扩展点
, 在dubbo源码中获取PROTOCOL和PROXY_FACTORY的姿势如下:
看过我《【dubbo源码解析】 — dubbo spi 机制(@SPI、@Adaptive)详解》这篇文章的你,肯定会想翻一下 ProxyFactory 和 Protocol 的源码,这里我们简单看一下:
- Protocol接口的源码
@SPI("dubbo") //dubbo默认使用的协议为dubbo协议
public interface Protocol {
int getDefaultPort();
@Adaptive //在方法上标注该注解,说明会静态代理Protocol 实现类的该方法 ---> 服务暴露的方法
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive//在方法上标注该注解,说明会静态代理Protocol 实现类的该方法 ---> 根据class类型和URL获取Invoker【消费端的逻辑】
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
default List<ProtocolServer> getServers() {
return Collections.emptyList();
}
}
- ProxyFactory接口的源码
@SPI("javassist")//dubbo默认使用的代理工厂为静态代理工厂
public interface ProxyFactory {
//PROXY_KEY其实指代的就是字符串“proxy”
@Adaptive({PROXY_KEY}) //在方法上标注该注解,说明会静态代理ProxyFactory 实现类的该方法 ---> 获取要暴露的服务的代理类
<T> T getProxy(Invoker<T> invoker) throws RpcException;
@Adaptive({PROXY_KEY})//在方法上标注该注解,说明会静态代理ProxyFactory 实现类的该方法 ---> 获取服务的代理类
<T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;
@Adaptive({PROXY_KEY})//在方法上标注该注解,说明会静态代理ProxyFactory 实现类的该方法 ---> 其实这里是生成Invoker
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
通过上面的源码分析,你会想到什么呢??? ---》 这里请大家自己先开脑洞。。。
2.2 实现一个自己的dubbo服务端
有了上面的知识后,其实我们就可以仿照dubbo的源码,自己实现一个服务端了。当然本文仅仅是探索RPC远程调用的原理,所以这里的Serive,我们大可以直接new一个出来。
仿照dubbo源码自己写的dubbo服务端:
ExtensionLoader<ProxyFactory> proxyLoader = ExtensionLoader.getExtensionLoader(ProxyFactory.class);
//支持的协议:dubbo、http、hessian、rmi等
ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
//URL protocol_url = URL.valueOf("dubbo://127.0.0.1:9300/" + DemoService.class.getName());//静态代理
URL protocol_url = URL.valueOf("dubbo://127.0.0.1:9300/" + DemoService.class.getName() + "?proxy=jdk"); //动态代理
@Test
public void serverRpc() throws IOException {
DemoService service = new DemoServiceImpl();
//生成代理工厂
// --- 由URL确定到底是动态代理工厂(JdkProxyFactory)还是静态代理工厂(JavassistProxyFactory)
// --- 默认情况下为静态代理工厂
ProxyFactory proxy = proxyLoader.getAdaptiveExtension();
//由代理工厂生成Invoker对象
// --- Invoker对象里包含了具体的服务(service,在dubbo源码里服务端称为ref)、服务的接口和要暴露服务的url
// --- 但值得注意的是这里返回的Invoker对象,是用Invoker接口进行接收的,也就是说通过下面的serviceInvoker,只能获取到service的接口
Invoker<DemoService> serviceInvoker = proxy.getInvoker(service, DemoService.class, protocol_url);
//获取具体的协议
// ---由URL确定到底使用哪个协议,默认情况下使用dubbo协议
Protocol protocol = protocolLoader.getAdaptiveExtension();
//利用protocol暴露服务
// --- 暴露服务的具体流程为
// --- (1) 拿到服务的【动态代理类或者静态代理类】、【接口】、以及【url】
// --- (2) 拿着(1)中获取到的三个内容进行真正的服务暴露
Exporter<DemoService> exporter = protocol.export(serviceInvoker);
System.out.println("server 启动协议:" + protocol_url.getProtocol());
// 保证服务一直开着
System.in.read();
exporter.unexport();
}
这里简单提一下dubbo的jar包的引入问题。
如果你想了解dubbo具体的协议,可以单独引入各个协议的jar包,比如说上面的代码如果想支持dubbo、http、hessian、rmi四种协议,需要引入的jar包如下:
<!--dubbo协议,该包里同时含有dubbo的Common等包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-rpc-dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--dubbo协议底层用的netty,注意这里要用netty4-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-remoting-netty4</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--dubbo协议底层序列化用的hessian2-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--http协议 该包里同时含有dubbo的Common等包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-rpc-http</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--rmi协议 该包里同时含有dubbo的Common等包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-rpc-rmi</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--hessian协议 该包里同时含有dubbo的Common等包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-rpc-hessian</artifactId>
<version>${dubbo.version}</version>
</dependency>
2.3 dubbo服务暴露过程主要流程梳理
3 dubbo服务消费过程底层原理探秘
3.1 spring环境下dubbo服务消费过程的前置知识
以注解配置为例,大家必须要有的前置知识是:当dubbo项目启动时,遇到@Reference(新版本的dubbo为@DubboReference)注解,会创建一个 ReferenceBean实例。该实例实现了InitializingBean 接口, 在其调用的afterPropertiesSet 方法中, 会为服务调用方 — 也就是@Reference标注的那个bean,创建一个远程代理对象。
创建过程中比较重要的两步源代码如下:
【1】获取可以调用服务端的服务,并将其和url、interface一起封装成一个Invoker
dubbo源码(1):消费端直连服务端的情况(在@Reference注解里可以配置直连的url)
或
dubbo源码(2):从注册中心获取服务端URL的情况
这里需要注意的是
:其实这一步已经获取到可以调用服务端的服务了,也就是说这个Invoker里封装了【可以调用服务端的服务】、【服务端的url】和【interface】。
获取【可以调用服务端的服务】的大致流程如下:
跟踪源码可以发现,无论是走http协议、rmi协议还是hessian协议【其他的协议我没具体研究】,它最终都会调用一个doRefer方法。我们这里简单看一下RMI协议的doRefer方法的源码:
对比一下我的上篇文章《【dubbo源码解析~番外篇】— JDK和Spring的RMI使用姿势简介》,你就会豁然开朗了,原来这就是通过【服务端url】和【interface】查找到服务端暴露的服务啊。
当然这里我还是保留上篇文章第4部分的观点:
消费端咋就找到了服务端的服务,并且为啥在消费端一调用,就会直接调用到服务端的方法这一过程,无需过度探索。
【2】代理工厂拿着协议对象创建的Invoker,创建实际的代理类,该代理类最终就会成为@Reference注解标注的那个实例bean
3.2 实现一个自己的dubbo消费端
有了上面的知识后,其实我们也可以仿照dubbo的源码,自己实现一个消费端了。当然还是那句话本文仅仅是探索RPC远程调用的原理,所以一切从简。
仿照dubbo源码自己写的dubbo消费端:
@Test
public void clientRpc() {
//获取具体的协议
// ---由URL确定到底使用哪个协议,默认情况下使用dubbo协议
Protocol protocol = protocolLoader.getAdaptiveExtension();
//由代理工厂生成Invoker对象
// --- Invoker对象里包含了服务的接口和要暴露服务的url
// --- 但值得注意的是这里返回的Invoker对象,是用Invoker接口进行接收的,也就是说通过下面的serviceInvoker,只能获取到service的接口
Invoker<DemoService> referInvoker = protocol.refer(DemoService.class, protocol_url);
//生成代理工厂
// --- 由URL确定到底是动态代理工厂(JdkProxyFactory)还是静态代理工厂(JavassistProxyFactory)
// --- 默认情况下为静态代理工厂
ProxyFactory proxy = proxyLoader.getAdaptiveExtension();
//生成DemoService的代理类
DemoService service = proxy.getProxy(referInvoker);
Map<String, String> info = new HashMap();
info.put("target", "orderService");
info.put("methodName", "getOrderInfo");
info.put("arg", "1");
Map<String, String> result = service.getHelloInfo(info);
System.out.println(result);
}
3.3 dubbo消费端调用服务的主要流程梳理
4 简单测试我们自己写的服务端和消费端
启用服务端【我提供的代码可以测试的协议有http、rmi、dubbo、hessian】
启用消费端调用服务端的服务
- (1)可以看到已经获取到了服务端返回的结果
- (2)同时可以看到确实调用到了服务端的服务
5 我心中Dubbo里最核心的概念: SPI/Invoker
- (1)SPI
通过本文的分析,相信你已经看到:【dubbo若想切换底层通讯协议】,【若想切换到底使用动态代理工厂还是静态代理工厂】是多么的easy!!! —> 只需要修改一个URL的参数,就搞定了,是不是很爽!!!
这里需要再提一下的是:
看完本文2.1.2的源码分析后,你会不会想到,其实dubbo使用注册中心的玩法,就是扩展了一个Protocol—>RegistryProtocol,然后只要你修改一下URL的参数【表现在dubbo实际使用中,就是修改一下配置文件+引入一下注册中心的jar】,就可以指向那个Protocol了 —》由此dubbo的核心源码还是本文提到的这些,但却可以将注册中心引入进来了—> 代码的扩展性多么好!!!
- (2)Invoker
其实Dubbo中的Invoker 概念, 作用不仅仅于此, 它统一了 dubbo 中各组件间相互交流的规范, 大家统一都用 invoker 进行粘合(书同文、 车同轴) 。 后续应该会继续展示 Invoker 更高一层的作用。。。
2020-07-15 夜
本文地址:https://blog.csdn.net/nrsc272420199/article/details/107269928
上一篇: 看加多宝等如何修炼成品牌中的「妖神」
下一篇: 荐 java学习(接口与继承)