接上一篇博客《Dubbo源码深度解析(六)》,上篇博客主要从服务消费方开始讲起,主要讲:如果类中的属性或者方法,如果被@DubboReference注解所修饰,Dubbo是怎么处理的,处理逻辑类似Spring框架提供的@Autowired注解。核心就是ReferenceAnnotationBeanPostProcessor类,最终会生成一个对应的ReferenceBean对象,并放入Spring容器,当然最终给属性进行依赖注入的并非ReferenceBean对象,而是它的 ref属性,如果该属性为空,就需要调用Protocol#refer()方法(Dubbo的SPI机制找到Protocol接口的实现类,调用实现类的ref()方法)创建Invoker,最终生成代理对象还没讲到,这篇博客会讲到。然后就是主要讲如何创建Invoker对象,也涉及到从注册中心拉取服务提供方的地址,以及Netty Client是如何跟服务提供方建立连接的逻辑等。
本篇博客是Dubbo源码讲解的最后一篇,在本篇博客中,会接着上篇的内容继续讲,上篇最后讲到了服务消费方跟服务提供方建立连接,其实讲到这里,Invoker对象的创建也基本讲完了。有一个核心的方法没有讲到,即RegistryProtocol#doCreateInvoker()方法中指向的这一行代码:
前面也讲到了,这里的cluster对象实际上是MockClusterWrapper对象,而MockClusterWrapper对象的cluster是FailoverCluster对象,看看MockClusterWrapper#join()方法,代码如下:
先看看FailoverCluster#doJoin()方法,代码如下:
再看看AbstractCluster#buildClusterInterceptors()方法,代码如下:
先看看ClusterInterceptor接口,代码如下:
org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor文件的内容为:
实现类的代码分别是:
讲ExtensionLoader#getActivateExtension()方法的时候,有一点没有讲到,也就是过滤条件的这个地方:
因此最终获取到的只有ConsumerContextClusterInterceptor对象,最终创建的InterceptorInvokerNode对象的interceptor属性为ConsumerContextClusterInterceptor对象,next属性为FailoverCluster对象。断点验证,结果如下:
到这里为止,生成Invoker对象的逻辑讲完了。总结一下,最终在ReferenceConfig得到的Invoker是什么样的?或者说是经过了多少层包装?
通过上面这些截图可以知道,最终生成的Invoker对象为:MigrationInvoker对象,而MigrationInvoker对象的invoker属性为MockClusterInvoker对象;而MockClusterInvoker的对象的invoker属性为InterceptorInvokerNode对象;而InterceptorInvokerNode对象interceptor属性为ConsumerContextClusterInterceptor对象,next为FailoverClusterInvoker对象;FailoverClusterInvoker对象里面有个urlInvokerMap属性,key为URL,value为InvokerDelegate对象,而InvokerDelegate的invoker属性为FilterNode对象;而FilterNode对象的filter属性为ConsumerContextFilter属性,它的next属性仍是FilterNode对象;而这个FilterNode对象的filter属性为FutureFilter对象,它的next属性仍是FilterNode对象;这个FilterNode对象的filter属性为MonitorFilter对象,next属性为ListenerInvokerWrapper对象;而ListenerInvokerWrapper对象的invoker属性为AsncToSyncInvoker对象;AsncToSyncInvoker对象的invoker属性为DubboInvoker对象,其中DubboInvoker的clients存放的对象,才是真正跟服务提供方建立连接的客户端(有朋友可能好奇,整理清楚这层层的包装有啥用?用处大了,理顺了才能知道调用链路,也能知道每一层包装的作用是啥,可能其中某一层就做了服务的负债均衡)。断点验证,结果为:
回到ReferenceConfig#createProxy()方法,最终得到的Invoker就是那个上面我讲的,位置为:
这里的PROXY_FACTORY属性即为ProxyFactory$Adaptive,即调用ProxyFactory$Adaptive#getProxy()方法,代码如下:
很明显这里的InvokerInvocationHandler实现了InvocationHandler接口,因此在真正执行的是时候,调用的是InvokerInvocationHandler#invoke()方法,断点看看依赖注入的结果:
接下来就是发起远程调用的,即调用HelloService#sayHello()方法,最终调用的是InvokerInvocationHandler#invoke()方法,代码如下:
有前面讲的可知,InvokerInvocationHandler对象的invoker属性为MigrationInvoker对象,因此直接看MigrationInvoker#invoke()方法,代码如下:
看看MigrationInvoker#checkInvokerAvailable()方法,代码如下:
由于是取反,因此最终调用invoker#invoke()方法,代码如下:
有前面可知,构建的InterceptorInvokerNode链只有一个,而这里的interceptor属性是ConsumerContextClusterInterceptor对象,因此可以看看ConsumerContextClusterInterceptor的before()方法和after()方法,代码如下:
重点看看ClusterInterceptor#intercept()方法,代码如下:
先看看是如何获取Invoker的集合的,看看AbstractClusterInvoker#list()方法,代码如下:
看看RouterChain#route()方法,代码如下:
上一篇博客讲到过,此时routers属性中有四个Router对象,顺序分别是:MockInvokersSelector、TagRouter、AppRouter和ServiceRouter。常用的有TagRouter和ConditionRouter,但默认是不加载ConditionRouter的,需要指定。可以通过环境变量来指定,核心方法为:AbstractConfig#refresh()方法,ReferenceBean是其类,看看该方法,代码如下:
再看看CompositeConfiguration#getString()方法,传入的当然是解析后的Setter方法的名字,如setRouter() => "router",代码如下:
实际上就是在AbstractConfig#refresh()方法中创建的CompositeConfiguration对象,回去看看,代码如下:
因此可以知道 ,prefix的值为:dubbo.reference.接口全限定名,因此可以这样设置,代码如下:
断点验证,结果为:
当然,还可以用Dubbo的配置文件,即dubbo.properties,如下:
当然也可以通过@DubboReference注解指定,由于注解中没有直接提供 router属性,因此需要这样指定:
处理@DubboReference注解的parameters属性的代码在这里:
回到之前说的,主要看TagRouter和ConditionRouter的使用方法。先说TagRouter,这是给服务消费方和服务提供方发打上标签,只有服务消费方的tag和服务提供方的tag一直才能调用,前置准备,先是服务提供方配置:
启动服务提供方服务,再在做如下修改:
再次启动服务提供方的服务,再看看Nacos,结果如下:
再配置服务消费方,配置如下:
启动服务消费方服务,直接看RegistryDirectory#doList()方法,并打断点,结果如下:
接着说ConditionRouter的使用方法。它的配置很灵活,参考官方文档:条件路由使用规则
按照官方文档的说法,"=>"之前是针对服务消费方,"=>"之后针对服务提供方,后面的参数名支持所有很多,包括URL中的,比如我举的例子,=> port = 20881,意思是服务消费方调用的应该是port为20881的服务提供方节点。配置如下:
同样的服务提供方有两个节点,port分别是20880和20881,Nacos上的服务信息如下:
再看看ConditionRouter#的代码,如下:
解析的代码较复杂就不一点点讲了,直接看ConditionRouter#route()方法,代码如下:
断点看最终的结果:
剩下的谷规则,有兴趣的可以自己看,或者参看官方文档,我就不继续讲了。回到AbstractClusterInvoker#invoke()方法,Invoker的集合经过了一系列规则之后,如果还有多个,那就需要进行负载均衡算法,选择一个最合适的Invoker对象,发起远程调用了,代码如下:
回到FailoverClusterInvoker#doInvoke()方法,根据随机的负载均衡策略,得到了一个Invoker对象,接着就是调用Invoker#invoke()方法了,由前面讲的可知这里的Invoker对象是InvokerDelegate,即调用InvokerDelegate#invoke()方法,代码如下:
重点看DubboInvoker#invoke()方法(方法在父类AbstractInvoker中),代码如下:
此时得到的isOneWay为false,因此走else的逻辑,重点看ExchangeClient#request()方法,代码如下:
看看这个channel是什么对象,代码如下:
还要再看看client是什么对象,代码如下:
再看看Transporters#connect()方法,代码如下:
因此这里的client属性为NettyClient对,回到HeaderExchangeClient#request()方法,代码如下:
看看NettyClient#getChannel()方法,获取的Channel对象是什么,代码如下:
NettyClient#getOrAddChannel方法入参 ch即为下面赋值的结果:
回到AbstractClient#send()方法,因此channel#send()方法的结果如下:
这里的channel对象就是前面截图中跟服务提供方建连的newChannel对象。由Netty的源码可知(参看《Netty源码深度解析》),如果消息向外写出,会经过一系列的ChannelOutboundHandler对象,看看之前往ChannelPipeline中添加了哪些ChannelOutboundHandler对象,看NettyClient#doOpen()方法,代码如下:
其中IdleStateHandler和NettyClientHandler,即实现了ChannelInboundHandler接口,也实现了ChannelOutboundHandler接口,也就是说读取数据以及写数据都要讲过这两个ChannelHandler对象。我这里当然是重点讲NettyClientHandler。由于是向服务提供方写数据,因此顺序是NettyClientHandler->IdleStateHandler->InternalEncoder(从tail到head)。因此,先看NettyClientHandler#write()方法,代码如下:
看看NettyClientHandler父类的write()方法,代码如下:
获取下一个AbstractChannelHandlerContext对象,准确地讲是DefaultChannelHandlerContext,它的handler属性即为ChannelOutboundHandler对象,代码如下:
顺便聊一下IdleStateHandler是如何实现心跳机制的,回到NettyClient#doOpen()方法,代码如下:
如果没有设置心跳间隔,默认是一分钟,看看IdleStateHandler的有参构造,代码如下:
看看IdleStateHandler#initialize()方法,代码如下:
既然是定时任务,核心当然是看IdleStateHandler.ReaderIdleTimeoutTask#run()方法了,代码如下:
看看IdleStateHandler#channelIdle()方法,代码如下:
其实还有一个问题,lastReadTime只是在IdleStateHandler#initialize()方法中赋值了,这个属性应该还在其他的地方放赋值才对,比如IdleStateHandler#channelRead(),实际上是在IdleStateHandler#channelReadComplete()方法中进行赋值的,代码如下:
回到IdleStateHandler#write()方法,最终会调到InternalEncoder#write()方法,代码如下: