6 Dubbo 启动过程探析
6.1 Spring 简介 BeanDefinition
在 Java 中,一切皆对象。在 JDK 中使用 java.lang.Class 来描述类这个对象。
而在 Spring 中,bean 对象是操作核心。那么 Spring 也需要一个东西来描述bean 这个对象,它就是 BeanDefinition。
有兴趣的同学,可以详细追一下 spring 的基础流程,其整体类图如下:
详细的 spring 原理不属于本课的内容,这里只回顾下 BeanDefinition 的基础用法:
RootBeanDefinition beanDef = new RootBeanDefinition();
beanDef.setBeanClass(DemoServiceImpl.class);
beanDef.setBeanClassName(DemoServiceImpl.class.getName());
beanDef.setScope(BeanDefinition.SCOPE_SINGLETON);//单例
//设置属性
MutablePropertyValues propertyValues = beanDef.getPropertyValues();
propertyValues.addPropertyValue("type","runtime");
//注册 bean
applicationContext.registerBeanDefinition("demoService", beanDef);
可以看到,最后的 spring 动作 applicationContext.registerBeanDefinition 会在 IOC容器内创建描述的 bean 对象。具体验证可参见 demo 代码后续 Dubbo 的所有对象创建,皆以此形式委托给 Spring 来创建。
6.2Dubbo 的配置解析过程
dubbo 的配置解析,不论是 xml 方式的配置,还是注解的配置,目标都是把配置的属性值提取出来,变成 dubbo 的组件 bean(先由BeanDefinition 描述,然后委托 spring 生成组件 bean)。
下面以 xml 的标签为例,列出每种配置对应要解析成为的目标组件 bean:
6.2.1 xml 配置的解析过程
首先,dubbo 自定义了 spring 标签描述 dubbo.xsd。在 dubbo-config-spring模块下的 src/main/resouce/META-INF 下。
其次,在 spring.handlers、spring.schemas 中指定解析类,将标签引入 spring 中管理。
DubboBeanDefinitionParser 继承了 spring 的BeanDefinitionParser 接口,spring 会调用 parse 方法来读取每个标签配置,将属性值装入对应的 BeanDefinition 定义中,后续 spring会根据此 BeanDefinition 定义生成 dubbo 的组件 bean。
6.2.2 注解的解析过程
Dubbo 注解的目标,与 xml 一致,都是将配置信息变为 BeanDefinition定义,交由 spring 生成组件 bean。
注解的源头是@EnableDubbo 用于启用 dubbo 配置。而此注解又引入EnableDubboConfig 和 DubboComponentScan 注解。
其中,@EnableDubboConfig 主要用于处理 dubbo 中全局性的组件配置,一般在.properties 文件中的配置项,如Application/Registry/Protocol/Provider/consumer ,而@DubboComponentScan 负责扫描项目源代码,处理业务类上的Reference/Service 注解。
6.2.2.1@EnableDubboConfig 的过程
@EnableDubboConfig 主要用来解析属性文件中的配置,一般在springboot 项目中比较常用,过程如下:
通过 DubboConfigConfigurationSelector 类,连接到DubboConfigConfiguration 类,此处配置了它支持解析的所有注解组件,如下:
此处为每个 dubbo 组件绑定了属性文件的前缀值。
具体的处理过程在:@EnableDubboConfigBinding 中,最终引入到DubboConfigBindingRegistrar 类来完成组件 bean 注册。
6.2.2.2@DubboComponentScan 的过程
DubboComponentScan 用来解析,标注在业务 Service 实现上的注解,主要是暴露业务服务的@Service 和引入服务的@Reference。总入口在DubboComponentScanRegistrar 上。可以看到,两个注解的处理是分开处理的。
6.2.2.2.1 service 注解
service 注解的处理,最终由 serviceAnnotationBeanPostProcessor 来处理。整个过程比较简单,dubbo 先调用 spring 扫描包处理:
此处,先由 spring 将暴露的业务服务实例化。然后再创建 dubbo 的组件对象。
servicebean 有很多父级组件信息引入,这些组件已经在属性文件处理部分完成。servicebean 中将 ref 指向业务 bean。
6.2.2.2.2 reference 注解
reference 注解的处理,最终由 ReferenceAnnotationBeanPostProcessor 来处理。
ReferenceAnnotationBeanPostProcessor 继承于 AnnotationInjectedBeanPostProcessor 实现了 MergedBeanDefinitionPostProcessor 接口,方法 postProcessMergedBeanDefinition在创建 bean 实例前会被调用(用来找出 bean 中含有@Reference 注解的 Field 和 Method)。
然后使用这一句 metadata.inject(bean, beanName, pvs)对字段和方案进行反射绑定。
当 Spring 完成 bean 的创建后会调用 AbstractAutowireCapableBeanFactory#populateBean 方法完成属性的填充。
6.3Dubbo 的服务暴露过程
前面已经看到,dubbo 的组件中,servicebean 和 referencebean 是比较特殊的。这两个组件,将完成 dubbo 服务的远程 rpc 过程。
servicebean 作为服务端,会在 bean 创建成功后,发起服务暴露流程。其过程如下:
在实现的 InitializingBean 接口中,spring 调用afterPropertiesSet 方法,发起服务的暴露动作。
父类中最终执行此动作:
先将本地服务 ref 包装成 invoker,然后由 protocol 网络协议将invoker 连通到网络上。其核心,即是一旦有 protocol 网络传输过来的请求,则拉起 invoker 动作,并将动作传递到 ref 服务上进行响应。
在这个整个过程中,dubbo 不希望每一个具体的协议 ptotocol 去关心目标服务是谁(耦合性太强),于是中间插入了一个 invoker 概念实体来解耦双方的绑定关系(重点)。
为了详细说明白这个过程,我们细细探究看一下 invoker 的机理:
首先,明确目标,invoker 的本义,是 invoke 方法会转嫁对象到目标 ref 上(具体怎么转嫁,后续会演示),接口定义如下:
协议使用方,希望如此调用 invoker:
意思是,希望 dubbo 中的所有协议都按此模式,将网络请求转给 invoker对象即可,剩下的 protocol 协议不要去关心。
Invoker 如何转请求到 target 目标服务呢?
交给 Invoker 的实现类去具体实现,下面是个例子,invoker 中本身持有目标 target 服务:
后续的调用不言自明,肯定是通过 java 反射将方法转给 target 服务。
总结上述过程:
1、protocol 组件收到网络请求到来时,它需要将请求发向 target 目标服务(如果当前环境中有此服务就好了)。
2、因为当前环境中没有 target 对象,于是它创建了一个 target 的代理对象 proxy,将请求转给了此代理 proxy 对象,而此 proxy 对象只会干一件事,将调用甩锅给了 invoker 对象的 invoke 方法。
3、invoker 对象发现自己内部有 target 对象,调用 so easy,于是它使用 java 反射,将请求发向了 target 服务。
PS:我们可以想到,如果让 protocol 中持有 target 服务,直接转向请求到 target 要简单得多,但这样一来,每一个 ptotocol 服务要对接千千万万的业务 service 接口,耦合性太强。于是,dubbo 专门设计了invoker 实体来解开两者间的直接耦合(工作中可否借鉴?)