目录
在前面的文章中我主要说明了以下几个问题:
- Spring启动的时候如何扫描到DubboService注解,并加入容器中的。
主要是通过
ServiceClassPostProcessor
这个核心类来实现的
- Spring如何将容器中的对象注入DubboReference注解的字段中的。
主要是通过这个核心类
ReferenceAnnotationBeanPostProcessor
类来实现的. 实现机制和Spring的Autowired注解的机制一样的
- dubbo的启动过程 会做哪些操作
可以看下我的 Dubbo的启动过程源码分析 – 初始化过程(4) , 这篇值是单独的介绍Dubbo的启动过程, 并没有说明和Spring结合是如何启动的。
在这篇博客中我会说明和Spring整合后,dubbo是如何启动的,如何暴露服务的和如何注册服务的。
首先要弄清楚两个概念:
服务暴露: 是指Dubbo开启端口监听, 并把这个服务的调用暴露到这个监听中,这样就可以远程调用这个服务了
服务注册: 是指把服务的基本信息,比如ip地址,服务接口名称, 端口号, 有哪些方法等等信息注册到注册中心上, 这样消费者就可以通过注册中心找到这个服务提供者了
总结
因为后面会不可避免的进入到代码追溯的深渊中, 所以这里我先把总结放上去,这样查看下面的源码更容易弄明白。
- Dubbo先使用Spring的能力加载扫描自己的业务类
- 通过监听Spring的启动事件, 在自己的事件监听器中启动DubboBootstrap.start()
- 首先遍历所有的ServiceConfig类, 每个服务提供类都一个对应的ServiceConfig。 在调用这个类的export方法暴露和注册服务
- 在export方法的内部会使用SPI的Adaptive机制. 会先导出到inJvm本地服务, 因为本地的服务调用不用去远程。
- 然后通过RegistryProtocol实现类的export方法来注册服务, 在注册远程服务的过程中会调用DubboProtocol来暴露服务, 暴露成功后才会注册
- 在RegistryProtocol内部会先调用DubboProtocol实现类来暴露服务, 并且RegistryProtocol在暴露完成后会继续自己的注册操作
- 注册完成, 这个服务的暴露和注册才全部完成。
我是边学习边写博客的方式, 有很多的问题我也不清楚, 比如文章结尾的为何Dubbo的SPI会选择ProtocolListenerWrapper实现类来调用的, 希望有知道的大佬能指点下迷津。
Dubbo监听Spring启动完成事件
在spring的框架启动完成后会发出一个事件, 而Dubbo就注册了这个事件的监听器DubboBootstrapApplicationListener
, 这个是在ServiceClassPostProcessor类的postProcessBeanDefinitionRegistry方法中第一行注册的。
DubboBootstrapApplicationListener中处理事件的方式就是调用DubboBootstrap的start方法
而这个start方法我在上篇博客Dubbo的启动过程源码分析 – 初始化过程中有初步的讲解, 但是这篇博客我主要还是来探究服务是如何注册到注册中心的。
我在org.apache.dubbo.config.bootstrap.DubboBootstrap#start
加上断点后启动
可以看到调用栈信息, 在finishRefresh方法中会发送事件。 在DubboBootstrapApplicationListener类的onApplicationContextEvent方法中会接受到事件, 并调用了dubboBootstrap.start方法。
ps: 在事件其实经过了OneTimeExecutionApplicationContextEventListener过滤了一下, 他会检查事件是否是框架发送的,如果不是会直接拦截下来的。
开始暴露服务
调用了start方法后,会先初始化, 初始化其实就会加载很多的配置信息等。
然后调用exportServices方法暴露服务, 调用exportMetadataService 暴露元信息, 并注册本地服务。
exportServices 方法
就是遍历之前扫描加载的ServiceConfigBase类, 并逐个调用sc.export();
来暴露服务。 很遗憾还要继续往下追溯。
export 暴露服务方法
可以看到前面就是校验参数, 并构建serviceMetadata对象, doExport才是真正的方法。
doExport和doExportUrls方法
图中的红色框中的registerProvider 我其实也不太清楚,感觉是先把服务的名称,序列化方式,调用的接口和真实的服务类放入注册中的本地缓存中。
后面for循环是获取这个服务提供者支持的协议, 并且把每个协议差异化的设置放入循环中,并注册到远程的服务中心。
注册到远程服务中心使用的是doExportUrlsFor1Protocol方法。 我们看下
doExportUrlsFor1Protocol
这个方法有点多,我会分两次截图
图中的红框部分就是把服务的基本信息, 比如协议,进程号, 应用名称, 服务名称等等信息放入到map中。
图中的这一坨在我debug的时候没有进入, 但是看源码 大概就是反射获取ServiceConfig中的方法和方法参数的信息并把这些信息组装好后放入到Map中。
但是为何debug没有进入呢?
通常注册方法信息到注册中心,都是默认注册DubboService类的父接口的所有方法的。 如果我们想要注册一个方法不在父接口中,这个功能的实现就是在这一坨代码中的。
此处我还有疑惑,希望有人能告诉我
接着往下走
图中就是简单的反射获取接口的所有方法, 并放入到map中。
前面放置了这么多的信息最后还是还放入到serviceMetadata的attachments字段中, 并且还会根据注册中心的host和port等洗信息构建一个URL
此URL是dubbo自己实现的URL类, 这个URL类包含了服务提供者的地址等等信息
我们看下URL的信息。
继续往下debug看看
图中是暴露服务到本地的jvm中的, 因为会出现服务消费者调用本地的服务提供者的情况,这个时候没有必要再去远端调用服务提供者了, 通过本地暴露的服务就可以调用本地的提供者了。 内部细节没有我没有截图, 直接通过了。
上面代码根据 url 中的 scope 参数决定服务导出方式,分别如下:
scope = none,不导出服务
scope != remote,导出到本地
scope != local,导出到远程
不管是导出到本地,还是远程。进行服务导出之前,均需要先创建 Invoker,这是一个很重要的步骤。因此下面先来分析 Invoker 的创建过程。
继续往下debug
先获取Invoker是代理调用器, 十分重要的一个类, 我们看下是如何实现的。
PROXY_FACTORY.getInvoker
PROXY_FACTORY 就是SPI机制中获取的ProxyFactory类的AdaptiveExtension扩展的。 我们看下
默认使用的是javassist的实现方式, 源码在org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory
类中
核心逻辑在org.apache.dubbo.common.bytecode.Wrapper#makeWrapper
方法中.
使用javassist 来操作class字节码来生成org.apache.dubbo.common.bytecode.Wrapper
类的子类. wrapper的子类会代理调用传入的被代理类, 在debug中被传入的被代理类实际上就是服务提供者类, 生成了子类后会反射生成子类的实例并返回。
所以Dubbo默认获取的Invoker实际上都是通过javassist操作字节码生成的静态代理的类。
PROTOCOL.export(wrapperInvoker)
这里才是真正的注册到远程注册中心的方法。 其中Protocol也是使用的SPI机制获取的扩展实现类。 实现的类是org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
。 可以看下这个类的export方法。
看到打开链接的方法openServer
, 其内部就是使用netty来开启socket服务,监听端口(默认20880)开始接收请求了
是怎么调用DubboProtocol的export方法的呢?
这个问题我也不太清楚, 但是我可以尝试的解释下
- 是通过spi机制获取了所有Protocol的AdaptiveExtension扩展类。 其中DubboProtocol就是这个扩展类之一
- 调用PROTOCOL.export方法其实就是SPI的选择机制选择出一个实现类来调用。
如何选择的呢, 使用的是SPI的Adaptive机制来做出选择的, 具体的可以看https://www.jianshu.com/p/dc616814ce98 这篇文章, 里面对于SPI是如何选择合适的实现类说的十分清楚。 我总结下SPI的选择的优先级:- 在实现类上加上@Adaptive注解, 那么Dubbo会直接使用这个实现类
- 在方法的URL参数中指明了调用的实现类, 这个时候会使用指定的实现类, 这种方式十分灵活.
- 在SPI接口上的@SPI注解中指定value值,这个value就是要调用的实现类。 这个只是一个兜底的指定, 如果上面两步都无法确定选择哪个实现类,才会使用这个实现类
PROTOCOL.export的调用链十分复杂, 我也弄不通它的SPI的实现类的选择逻辑。
上面就是整个PROTOCOL.export方法的调用时序图, 可以看到ProtocolListenerWrapper
和ProtocolFilterWrapper
只是两次中转, 真正的调用逻辑是在RegistryProtocol
和DubboProtocol
两个实现类中, 并且RegistryProtocol
是在内部调用了DubboProtocol
来暴露服务,暴露完成后才进行继续进行注册服务和订阅自己暴露的服务和监控自己注册上去的URL信息是否有变化。
监控自己注册上去的URL信息是否有变化, 正因为监控变化, 我们才能在admin管理中心来方便的堆服务进行降权,路由等操作
问题:
虽然整个调用链路清晰了, 但是我还是明白为何DUBBO的SPI会最先调用ProtocolListenerWrapper
这个实现类?
希望知道的大佬能指点下。
是因为Dubbo中wrapper 类可以实现类似于SpringAOP的功能,而ProtocolListenerWrapper就是一个典型的Wrapper 类。 wrapper的原理在我的后面文章中会说道