上篇我们粗略的过了一遍dubbo在
ServiceBean
的afterPropertiesSet()
方法中做了那些事。那么我们这篇就开始进入服务导出的代码了。让我们一起来分析,看看它是如何实现的。
前情回顾
其实上一篇文章写的不怎么好,跟心情有一定的原因,还有一些原因是因为该部分的代码都是差不多的,只要看懂了一块,那么剩下的也基本上都差不多了。按照惯例,还是得先回忆下上一篇中到了说了写啥。有如下内容:
- Dubbo是怎样从Spring容器中获取一个Bean的。
- 存储对应的配置到
ServiceBean
中。
上面两个就是上一篇的全部内容和涉及到的东西,注意哦,每碰到一个<dubbo:service>
标签,就会有一个ServiceBean
对象。
服务导出流程
首先声明,这里的服务导出流程,并不是Dubbo
的,而是我们猜想的。先跳出Dubbo,假设要我们做服务导出,我们应该怎么做?然后再对比Dubbo,这样看源码就不会太晕车。
回顾
还是回顾一下,在刚开始Dubbo
系列的时候,Dubbo
的第一篇就是如何简单的实现一个RPC框架(严格意义上,就是个demo),地址是这个Dubbo系列之RPC浅谈&简单实现一个RPC,当时我们说的流程是这样的:
新需求
假设如果要我们这个demo来集成Spring,并且需要引入注册中心,我们的流程会是怎样?如果基于上面的这个流程来的话,只考虑服务提供方的话,我们需要在第一步前插入如下流程:
- 解析配置文件,映射成配置实体,然后保存到Spring中。
- 在Spring配置文件解析完成后,在
InitializingBean.afterPropertiesSet()
中做文章
以上两点,就是前面两篇文章所说的内容。这样我们就继承了Spring,可以让Spring来帮我们管理一些bean。
然后接着看,如果要引入注册中心,需要动态的维护我们暴露出去的服务地址,这时候应该怎样做?基于上图,我们需要在哪里插入处理流程?
上面也说了,引入注册中心是为了动态的维护我们暴露的地址,所谓暴露地址就是需要告诉服务消费方应该连接哪个地址,那个端口来调服务提供方。
那这个暴露的地址是怎么来的?从哪来的?我们可以看上图的第一步,有个启动ServerSocket
的操作,ServerSocket
我们都知道是Java中用来网络通信的,这里ServerScoket
启动后就会阻塞在那里,等待消费端的连接。这时候我们服务的地址以及端口我们都已经知道了,所以我们可以在第一步操作完后,把我们的地址+端口注册到注册中心上。
那么最后的流程大致如下(只针对服务提供方):
- 解析配置文件,加入到Spring容器
- 启动一个
ServerSocket
,监听消费者的连接 - 把
ServerScoket
的地址、端口号保存到注册中心上
上面就是我们自己实现demo的流程,那Dubbo中是怎样的呢?其实Dubbo中抛去那些复杂的代码,以及对扩展的处理,总体的流程还是和上面的差不多,我们一起慢慢的看下去吧。
继续从ServiceBean
看起
上次我们说到了ServiceBean.afterPropertiesSet()
中的最后部分,也就是export()
方法。代码如下:
1 | if (!supportedApplicationListener) { |
后面这几篇文章,都是围绕这个export()
方法去阐述。我们今天只是说一部分,慢慢来~
ServiceBean.export()
我们跟进去,export()
代码如下:
1 |
|
方法里面就两行代码,先是调用super.export()
方法,然后在发布一个事件,我们先看下发布的是什么事件。为什么要先看事件呢?因为重头戏在super.export()
中,我们先看一些简单的。
ServiceBean.publishExportEvent()
1 | private void publishExportEvent() { |
可以很清楚的看到,他是通过Spring发布一个ServiceBeanExportEvent
事件。那么事件是从这发送出去了,那么是谁在监听呢?这里我们暂时不管。也就是说,这个方法就看到这里了。我们来重点关注super.exoprt()
方法。
super.export()
1 | //问题:这里为什么要加锁? |
这里我们先看一个问题,为什么方法上要加上synchronized
关键字?
- 这里加锁,是为了防止重复导出,因为
export()
方法并不只有ServiceBean
调用,还会有其他地方,而其他地方肯定是另外一个线程,这里加锁的目的就是为了防止并发情况下,多次导出。 - 既然说到这,那么他肯定有个字段标识是否已经导出,然后也会有个地方去判断这个字段的值,从而中断方法向下执行,该字段在后面会看到的。
问题回答完了,我们再一步一步的开始分析代码。checkAndUpdateSubConfigs()
方法,我们忽略掉不说。直接看他是怎么判断这个<dubbo:service>
标签配置的bean
是否需要导出的。
ServiceConfig.shouldExport()
1 | private boolean shouldExport() { |
经过上面的分析,可以得出在第一次调用shouldExport()
方法是,返回的是true
。所以!shouldExport()
是false
。
然后我们继续看一下shouldDelay()
方法。
ServiceConfig.shouldDelay()
1 | private boolean shouldDelay() { |
说白了,只有在我们配置了<dubbo:service>
的delay
属性,shouldDelay()
才会返回true
。
那我们看看如果配了delay
属性,也就是shouldDelay()
返回true
时是怎么玩的吧。同时也看看如果不配延时导出,是怎么玩的。
延迟 OR 不延迟
1 | //是否需要延迟导出 |
那么现在所有的关键都指向doExport()
方法,那咱们一起来look look。
ServiceConfig.doExport()
1 | //问题:这里为什么也要加锁?前面在export()方法那里不是已经加了么? |
经过上面一系列操作后,又调用了doExportUrls()
方法,一环套一环的来。
上面还留了个问题,这里为什么要加锁?明明调用该方法出已经加过锁了呀,这里再加一次,不就浪费了吗?原因如下:
- 还是调用的问题,因为我们可以在
export()
方法的最后看到,如果配置了延迟导出,会在一定时间后再调用该方法,如果没得配置延迟导出,那就会马山调用该方法。 - 而延迟导出,到了时间后是会新开一个线程去执行,而单位又是毫秒。假设如果
doExport()
方法没有加锁,我配置的是5000ms
,也就是5s
。然后我发现这个配置我配错了,我不需要延迟启动,所以我动态修改了delay
的值,然后刷新Spring容器。而Dubbo会在ServiceBean
中监听ContextRefreshedEvent
事件(这个会在后面说到),然后就会再次调用export()
方法。这时候因为delay
的值为空,所以不会走延迟导出,就会立马调用doExport()
方法。而好巧不巧,这时候延迟启动的时间到了,我也调用了doExport()
方法,试想一下,如果不加锁,这里就有可能会导出两次同一个服务。
可以看到,从开始到这里为止,逻辑都不是很复杂,可以谓之超级简单;也可以看到,这部分的代码无非就是判断下服务是否导出,是否需要延迟导出,既然判断都差不多了,那么接下来应该该入正题了,是的,正题部分我们下一篇再分析,该篇就到这里了。谢谢大佬们观看。
总结
-
没啥好说的,逻辑很简单,思路也很清晰。
-
这篇就不画图了哈,我们下一篇开始。
-
才疏学浅,难免会有错误之处,还望各位大佬不吝指教。谢谢~