本文对应源码地址:/nieandsun/dubbo-study
文章目录
1 开篇2 dubbo服务暴露过程底层原理探秘2.1 spring环境下dubbo服务暴露过程的前置知识2.1.1【spring解析要暴露服务的bean ---> 进行服务暴露】 整体过程概览2.1.2 ProxyFactory和Protocol接口简介2.2 实现一个自己的dubbo服务端2.3 dubbo服务暴露过程主要流程梳理3 dubbo服务消费过程底层原理探秘3.1 spring环境下dubbo服务消费过程的前置知识3.2 实现一个自己的dubbo消费端3.3 dubbo消费端调用服务的主要流程梳理4 简单测试我们自己写的服务端和消费端5 我心中Dubbo里最核心的概念: SPI/Invoker1 开篇
想要在一个JVM里调用另一个JVM里的的方法,或许你会想到如下的姿势:
HttpClientRestTemplateWebServiceServerSocket/SocketRMI — 可以参看我的上篇文章《【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】
②生成服务的代理类 如果代理工厂选择的为JavassistProxyFactory,则为服务生成静态代理类如果代理工厂选择的为JdkProxyFactory,则为服务生成动态代理类 (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"); //动态代理@Testpublic 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消费端:
@Testpublic 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)Invoker看完本文2.1.2的源码分析后,你会不会想到,其实dubbo使用注册中心的玩法,就是扩展了一个Protocol—>RegistryProtocol,然后只要你修改一下URL的参数【表现在dubbo实际使用中,就是修改一下配置文件+引入一下注册中心的jar】,就可以指向那个Protocol了 —》由此dubbo的核心源码还是本文提到的这些,但却可以将注册中心引入进来了—> 代码的扩展性多么好!!!
其实Dubbo中的Invoker 概念, 作用不仅仅于此, 它统一了 dubbo 中各组件间相互交流的规范, 大家统一都用 invoker 进行粘合(书同文、 车同轴) 。 后续应该会继续展示 Invoker 更高一层的作用。。。
-07-15 夜