> 文章列表 > spring注解方式整合Dubbo源码解析

spring注解方式整合Dubbo源码解析

spring注解方式整合Dubbo源码解析

系列文章目录


前言

本节我们的Dubbo源码版本基于2.6.x

在前一章我们的整合案例中,我们有几个比较关键的步骤:

  1. 在启动类上标注了@EnableDubbo注解
  2. 在provider类上面标注了@Service注解来提供dubbo服务
  3. 在消费的时候通过@Reference注解引入dubbo服务
  4. 在配置文件中配置应用名,协议,暴露端口,注册中心地址等。
  5. 在配置文件中配置应用名,协议,暴露端口,注册中心地址等。

一、EnableDubbo注解

  1. 这里可以看到EnableDubbo是一个复合注解,集成了@EnableDubboConfig@DubboComponentScan注解
    2. EnableDubbo注解的属性,其中scanBasePackagesscanBasePackageClasses默认都是空,这里是可以指定包扫描的路径或者基类的,然后multipleConfig默认是true

spring注解方式整合Dubbo源码解析

1.1、EnableDubboConfig注解

  1. 这里使用import注解导入了DubboConfigConfigurationRegistrar
  2. 这里EnableDubboConfig注解中的multiple属性默认为true

spring注解方式整合Dubbo源码解析

1.1.1、DubboConfigConfigurationRegistrar

  1. 首先获取EnableDubboConfig注解中的属性列表
  2. 这里会根据注解中的multiple属性判断是否是单一绑定配置还是多个实例绑定配置,这里multiple默认为true

spring注解方式整合Dubbo源码解析

这里单一实例和多实例的区别,如下所示,多实例就是协议我可能支持多种,这样会根据我们是否支持多实例来给每个协议都生成一个配置.

spring注解方式整合Dubbo源码解析

1.1.2、AnnotatedBeanDefinitionRegistryUtils#registerBeans

  1. 上面会根据单实例还是多实例的区别来判断传入的注解类是DubboConfigConfiguration.Single还是 DubboConfigConfiguration.Multiple.class,最终都会调用到registerBeans方法。
  2. 这里会构造一个AnnotatedBeanDefinitionReader类,然后会扫描上面传入的注解类,然后注入到spring容器中。

spring注解方式整合Dubbo源码解析

1.1.3、DubboConfigConfiguration

  1. 我们上面把Single or Multiple 注解到spring容器中,然后发现对应类上面也标注了@EnableDubboConfigBindings注解,二者的区别就是Multiple多了一个注解属性,multipletrue
  2. @EnableDubboConfigBindings注解中是一个@EnableDubboConfigBinding注解的数组,他的作用就是把配置文件中的配置信息和具体的类进行绑定。

spring注解方式整合Dubbo源码解析

1.1.4、EnableDubboConfigBindings注解

  1. 这里我们发现他也是通过import导入了一个DubboConfigBindingsRegistrar类。

spring注解方式整合Dubbo源码解析

1.1.5、DubboConfigBindingsRegistrar#registerBeanDefinitions

  1. 上面通过import导入了DubboConfigBindingsRegistrar,他是实现ImportBeanDefinitionRegistrar,spring就会在其生命周期中回调对应的registerBeanDefinitions`方法。
  2. 这里首先会获取EnableDubboConfigBindings注解中的属性列表
  3. 然后获取value属性,这里对应就是EnableDubboConfigBinding[]注解列表
  4. 然后创建了一个DubboConfigBindingRegistrar注册器
  5. spring的环境也就是ConfigurableEnvironment设置到注册器中,从environment中可以获取到配置文件中的配置信息
  6. 遍历EnableDubboConfigBinding[]注解列表,注册对应的BeanDefinition

spring注解方式整合Dubbo源码解析

1.1.6、DubboConfigBindingRegistrar#registerBeanDefinitions

例:@EnableDubboConfigBinding(prefix = “dubbo.applications”, type = ApplicationConfig.class, multiple = true),

  1. 这里首先会从EnableDubboConfigBinding中解析出来prefix属性的value,得到dubbo.applications
  2. 解析出来type属性,得到ApplicationConfig.class
  3. 解析出来multiple属性,得到true
  4. 把解析出来的参数全部传入调用registerDubboConfigBeans注册Dubbo的配置Bean

spring注解方式整合Dubbo源码解析

1.1.7、DubboConfigBindingRegistrar#registerDubboConfigBeans

  1. 这里调用getSubProperties方法从environment中,也就是我们配置文件中的配置里面解析出来指定前缀的配置
  2. 如果没有指定前缀的配置则不需要注册BeanDefinition
  3. 根据multiple属性,这里一般为true,这样我们如果配置了多种协议,那么就会生成多个beanName
  4. 给每个beanName都注册一个空的BeanDefinition,然后给每个Bean都注册一个DubboCOnfigBindingBeanPostProcessorbean的后置处理器
  5. 最后调用registerDubboConfigBeanCustomizers注册了一个NamePropertyDefaultValueDubboConfigBeanCustomizerbean

spring注解方式整合Dubbo源码解析

1.1.7.1、PropertySourcesUtils#getSubProperties

  1. propertySources转成MutablePropertySources
  2. 调用重载方法getSubProperties

spring注解方式整合Dubbo源码解析

  1. 这里就是遍历PropertySource,解析出来指定前缀的配置,然后缓存起来返回。

spring注解方式整合Dubbo源码解析

1.1.7.2、DubboConfigBindingRegistrar#resolveMultipleBeanNames

  1. 这里就是从配置中解析出来beanName。
    比如配置是如下:
    dubbo.protocols.p1.name=dubbo
    dubbo.protocols.p1.port=20880
    dubbo.protocols.p1.host=0.0.0.0
    这里properties传进来的就是
    p1.name=dubbo
    p1.port=20880

    那么解析出来的beanName就是p1

spring注解方式整合Dubbo源码解析

1.1.7.3、DubboConfigBindingRegistrar#registerDubboConfigBean

这里就是通过BeanDefinitionBuilder生成一个指定类型的空的beanDefinition并注册到spring容器中。

spring注解方式整合Dubbo源码解析

1.1.7.4、DubboConfigBindingRegistrar#registerDubboConfigBindingBeanPostProcessor

  1. 这里为我们刚刚每个注册的配置bean都同样注册一个DubboCOnfigBindingBeanPostProcessor类型的的bean后置处理器
  2. 这里首先生成一个DubboConfigBindingBeanPostProcessor类型的BeanDefinitionBuilder
  3. 然后这里会设置构造器参数值,把前缀和beanName都传进去,这里的前缀如:dubbo.registries.r2beanName如:r2,这里设置构造器参数的value,那么spring帮我们创建bean的时候就会调用对应的构造器把参数值传进去
  4. 通过BeanDefinitionBuilder生成DubboConfigBindingBeanPostProcessorBeanDefinition并注册到spring容器中。

spring注解方式整合Dubbo源码解析

1.1.7.5、DubboConfigBindingRegistrar#registerDubboConfigBeanCustomizers

这里会注册namePropertyDefaultValueDubboConfigBeanCustomizer的bean到spring容器中,这里后续DubboConfigBindingBeanPostProcessor处理逻辑中会用到。

spring注解方式整合Dubbo源码解析

1.1.7.6、DubboConfigBindingBeanPostProcessor#postProcessBeforeInitialization

  1. 我们在上面会给每个配置bean都生成一个后置处理器,那么在spring容器的生命周期中就会调用到后置处理器的postProcessBeforeInitialization方法,我们来看下DubboConfigBindingBeanPostProcessorpostProcessBeforeInitialization方法
  2. 这里会判断beanName是否一致,只处理关联的那个bean
  3. 这里会调用bind方法从properties中获取值并设置到DubboConfig对象中
  4. 设置dubboConfig对象的name属性,设置为beanName

spring注解方式整合Dubbo源码解析

1.1.7.7、DubboConfigBindingBeanPostProcessor#bind

  1. 这里会调用dubboConfigBinder的bind方法,我们看下dubboConfigBinder是啥时候构造出来的,他的bind方法是如何实现数据绑定的。

spring注解方式整合Dubbo源码解析

我们看到在DubboConfigBindingBeanPostProcessor#afterPropertiesSet方法会进行初始化操作,这个方法也是spring提供的回调方法,会在属性设置前调用到这个方法

spring注解方式整合Dubbo源码解析

这里initDubboConfigBinder方法首先会从spring容器中获取DubboConfigBinder类型的bean,如果获取不到就创建默认的也就是DefaultDubboConfigBinder。

spring注解方式整合Dubbo源码解析spring注解方式整合Dubbo源码解析

  1. 这里initConfigBeanCustomizers方法会从spring容器中获取DubboConfigBeanCustomizer类型的所有bean实现,这里最少会有一个,之前在DubboConfigBindingRegistrar中注册的namePropertyDefaultValueDubboConfigBeanCustomizer
  2. 然后设置到configBeanCustomizers属性中,并且会按照order进行排序.

spring注解方式整合Dubbo源码解析

1.1.7.8、DefaultDubboConfigBinder#bind

  1. 从上面我们知道如果我们没有注入DubboCOnfigBinder,则默认是DefaultDubboConfigBinder
  2. 这里会创建DataBinder,这个是spring提供的,用来实现数据绑定
  3. 这里设置忽略无用的属性和不能解析的属性
  4. 从properties中获取指定前缀的属性
  5. 把perperties转成MutablePropertyValues,调用dataBinder#bind方法执行绑定操作,这里会把MutablePropertyValues中的配置绑定到dubboConfig中。

spring注解方式整合Dubbo源码解析

1.1.7.9、DubboConfigBindingBeanPostProcessor#customize

  1. 这里会遍历从spring容器中获取的所有configBeanCustomizers,并调用其customize方法
  2. 这里如果我们没有配置,默认只有一个NamePropertyDefaultValueDubboConfigBeanCustomizer

spring注解方式整合Dubbo源码解析

这里其实就是判断如果DubboConfig中有name属性,则判断如果name属性如果没有设置值,则反射调用其set方法把beanName设置到name属性中。

spring注解方式整合Dubbo源码解析

1.2、DubboComponentScan注解

这里通过Import导入了DubboComponentScanRegistrar

spring注解方式整合Dubbo源码解析

1.2.1、DubboComponentScanRegistrar#registerBeanDefinitions

  1. 获取包扫描路径
  2. 注册处理@Service注解的后置处理器
  3. 注册处理@Reference注解的后置处理器

spring注解方式整合Dubbo源码解析

1.2.2、DubboComponentScanRegistrar#getPackagesToScan

  1. 获取DubboComponentScan注解中配置的属性
  2. 获取value属性,并把包路径数组转成set,可以对重复的包路径去重
  3. 如果指定了basePackageClasses,那么说明需要对这些类所在包路径设置到set中
  4. 如果都没指定包路径,则使用当前标注了@DubboComponentScan注解所在类的包路径作为基础包扫描路径。

spring注解方式整合Dubbo源码解析

1.2.3、DubboComponentScanRegistrar#registerServiceAnnotationBeanPostProcessor

  1. 注册ServiceAnnotationBeanPostProcessor后置处理器到spring容器中,并制定构造器的参数值为包扫描路径,这里spring会在生成bean的时候会调用构造方法设置进去

spring注解方式整合Dubbo源码解析

1.2.4、DubboComponentScanRegistrar#registerReferenceAnnotationBeanPostProcessor

  1. 注册registerReferenceAnnotationBeanPostProcessor后置处理器到spring容器中

spring注解方式整合Dubbo源码解析

1.2.5、ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry

  1. ServiceAnnotationBeanPostProcessor是一个BeanDefinitionRegistryPostProcessor,spring会回调他的postProcessBeanDefinitionRegistry方法来让用户修改BeanDefinition
  2. 这里会获取用户注解配置的包扫描
  3. 调用registerServiceBeans注册ServiceBean
    spring注解方式整合Dubbo源码解析

1.2.5.1、ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry

这里就是做了下占位符的处理,因为有可能包路径中有占位符例:xxx.${xx}…xxx。

spring注解方式整合Dubbo源码解析

1.2.5.2、ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry

  1. 这里首先创建DubboClassPathBeanDefinitionScanner,用来执行包扫描
  2. 从registry中解析出BeanNameGenerator
  3. 设置的DubboClassPathBeanDefinitionScanner的beanNameGenerator为刚刚解析出来的BeanName生成器
  4. 给scanner设置IncludeFilter,表示只扫描标注了Dubbo中的@Service注解的类
  5. 遍历包路径,执行扫描操作,把扫描出来的BeanDifinition转换成BeanDefinitionHolder
  6. 遍历BeanDefinitionHolders,注册ServiceBean

spring注解方式整合Dubbo源码解析

1.2.5.3、ServiceAnnotationBeanPostProcessor#resolveBeanNameGenerator

  1. 判断如果BeanDefinitionRegistry是SingletonBeanRegistry类型,则获取internalConfigurationBeanNameGenerator返回
  2. 如果不是SingletonBeanRegistry或者从容器中没有获取到internalConfigurationBeanNameGenerator则使用AnnotationBeanNameGenerator

spring注解方式整合Dubbo源码解析

1.2.5.4、ServiceAnnotationBeanPostProcessor#findServiceBeanDefinitionHolders

  1. 获取出来packageToScan路径下标注了@Service的BeanDefinition
  2. 把扫描出来的BeanDefinition转换成BeanDefinitionHolder,Holder中包含BeanDefinition和BeanName

spring注解方式整合Dubbo源码解析

1.2.5.5、ServiceAnnotationBeanPostProcessor#registerServiceBean

  1. 从beanDefinitionHolde中解析出来对应的beanClass
  2. 从BeanClass上获取标注的@Service注解信息
  3. 从beanClass和@Service注解信息中解析出来对应实现的接口的class
  4. 从从beanDefinitionHolder中获取BeanName,构造ServiceBean的Definition,这个ServiceBean用来存储这个服务提供者的元数据信息
  5. 生成ServiceBean的BeanName
  6. 如果没有冲突,则会添加到spring容器中

spring注解方式整合Dubbo源码解析

1.2.5.5、ServiceAnnotationBeanPostProcessor#resolveClass

  1. 这里从BeanDefinition获取到对应的BeanClassName,然后通过BeanClassName解析出来对应的class对象

spring注解方式整合Dubbo源码解析

1.2.5.6、ServiceAnnotationBeanPostProcessor#resolveServiceInterfaceClass

  1. 首先拿@service注解中配置的interfaceClass属性,如果没有指定,在判断用户有没有在注解中指定interfaceName,如果指定了,则尝试解析出interfaceName对应的class返回
  2. 如果注解中没有配置,则获取到当前BeanClass实现的所有接口类,然后获取第一个

spring注解方式整合Dubbo源码解析

1.2.5.6、ServiceAnnotationBeanPostProcessor#buildServiceBeanDefinition

  1. 首先通过BeanDefinitionBuilder生成一个空的ServiceBean的BeanDefinition
  2. 这里获取BeanDefinition的propertyValues,然后设置忽略的属性名,因为这些都是对象类型,是我们之前解析配置文件生成的配置类,已经放到spring容器中,需要通过注入的方式特殊赋值
  3. 给BeanDefinition的propertyValues添加一个PropertyValues是AnnotationPropertyValuesAdapter,这个可以从environment获取对应的配置,后续spring在进行属性注入的时候会自动填充
  4. 下面就是调用addPropertyReference,这里会从spring容器中找到对应name的bean,然后把对应的bean设置到其属性中

spring注解方式整合Dubbo源码解析
spring注解方式整合Dubbo源码解析

1.2.5.7、ServiceAnnotationBeanPostProcessor#generateServiceBeanName

这里serviceBeanName的生成规则就是拼接了接口名+分组名+版本号生成一个beanName

spring注解方式整合Dubbo源码解析
spring注解方式整合Dubbo源码解析

1.2.5.8、ServiceBean#onApplicationEvent

  1. 在上面我们已经把ServiceBean注册到Spring容器中,那么他是什么时候进行服务暴露的,注册到注册中上,供外部服务调用,从下图我们可以看到他是继承了很多接口,其中一个就是ApplicationListener,并且监听的是容器刷新完成的时间。
  2. 那么Spring容器在刷新完成的时候就会回调监听器的onApplicationEvent方法,在这里会进行dubbo服务的导出操作,这个后续我们再进行分析。

spring注解方式整合Dubbo源码解析
spring注解方式整合Dubbo源码解析

1.2.6、ReferenceAnnotationBeanPostProcessor

  1. 这里他继承了AnnotationInjectedBeanPostProcessor,并且指定了要进行自动注入的注解是@Reference注解,这个AnnotationInjectedBeanPostProcessor的回调时机是在Bean属性填充完毕后,这里可以类比@Autowried注解,也是会有一个xxxProcessor来进行自动注入的逻辑

spring注解方式整合Dubbo源码解析

1.2.6.1 、AnnotationInjectedBeanPostProcessor#postProcessPropertyValues

  1. 这里首先会找到Bean所有标注了@Reference注解的字段和方法
  2. 然后对字段、方法进行反射绑定

spring注解方式整合Dubbo源码解析

1.2.6.2 、AnnotationInjectedBeanPostProcessor#findInjectionMetadata

  1. 这里首先从缓存中获取当前bean的注入元数据信息
  2. 如果缓存中没有,则会调用buildAnnotatedMetadata方法构造注解元数据

spring注解方式整合Dubbo源码解析

1.2.6.3 、AnnotationInjectedBeanPostProcessor#buildAnnotatedMetadata

  1. 这里首先查找这个类标注了@Reference注解的属性元素
  2. 再查找这个类标注了@Reference注解的方法元素
  3. 组装成AnnotatedInjectionMetadata返回

spring注解方式整合Dubbo源码解析

1.2.6.4 、AnnotationInjectedBeanPostProcessor#buildAnnotatedMetadata

  1. 这里就是遍历这个类对应的所有属性,判断上面有没有标注@Reference注解
  2. 判断如果有标注注解,如果修饰符是static,那么也不支持
  3. 如果满足条件则加入到元素列表中返回

spring注解方式整合Dubbo源码解析

1.2.6.5 、AnnotationInjectedBeanPostProcessor#findAnnotatedMethodMetadata

  1. 这里遍历类中的所有方法,这里有可能方法是个泛型方法,因为泛型在编译阶段会进行类型擦除,Java编译器会将类型参数替换为其上界(类型参数中extends子句的类型),如果上界没有定义,则默认为Object,当一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法,这样是为了避免子父类方法签名不一致,就在子类自动生成一个与父类的方法签名一致的桥接方法
  2. 所以这里会判断如果是桥接方法就找到被桥接的方法,然后判断被桥接的方法是否可见,方法上面有没有标注@Reference注解
  3. 如果有注解,则判断是不是静态,不支持静态方法,也不支持无参方法,因为这样没法进行注入了,不知道注入啥类型的
  4. 把符合条件的方法收集返回

spring注解方式整合Dubbo源码解析

1.2.6.5 、InjectionMetadata#inject

  1. 在上面已经查询到了并组装成InjectionMetadata,这里会调用inject方法执行注入操作
  2. 这里是spring的源码,会遍历每个element,并调用其inject方法

spring注解方式整合Dubbo源码解析

1.2.6.6 、InjectionMetadata#inject

在上面调用每个element的inject方法就会调用到element具体的实现,是方法的还是属性的

spring注解方式整合Dubbo源码解析

1.2.6.6.1 、AnnotatedFieldElement#inject
  1. 获取属性类型
  2. 获取注入的对象
  3. 通过反射设置到属性中

spring注解方式整合Dubbo源码解析

1.2.6.6.2、AnnotatedFieldElement#inject
  1. 获取属性类型
  2. 获取注入的对象
  3. 通过反射设置

spring注解方式整合Dubbo源码解析

1.2.6.7 、AnnotationInjectedBeanPostProcessor#getInjectedObject

  1. 首先构造缓存key,从缓存中获取
  2. 如果缓存中不存在,则调用doGetInjectedBean获取注入的对象

spring注解方式整合Dubbo源码解析

1.2.6.8 、ReferenceAnnotationBeanPostProcessor#doGetInjectedBean

  1. 首先构造serviceBean的名字,通过接口+分组+版本构造
  2. 调用buildReferenceBeanIfAbsent方法,生成ReferenceBean对象
  3. 放入缓存中
  4. 生成代理对象

spring注解方式整合Dubbo源码解析

1.2.6.9 、ReferenceAnnotationBeanPostProcessor#buildReferenceBeanIfAbsent

  1. 先从缓存中获取referenceBean
  2. 如果缓存中没有,则通过ReferenceBeanBuilder生成一个referenceBean

spring注解方式整合Dubbo源码解析

1.2.6.9.1 、ReferenceBeanBuilder#build
  1. 这里checkDependencies是个空方法
  2. 调用doBuild创建一个ReferenceBean
  3. 调用configureBean配置ReferenceBean的相关属性

spring注解方式整合Dubbo源码解析

1.2.6.9.2 、ReferenceBeanBuilder#doBuild
  1. 这里直接创建一个ReferenceBean返回

spring注解方式整合Dubbo源码解析

1.2.6.9.3、AbstractAnnotationConfigBeanBuilder#doBuild
  1. ConfigureBean的前置操作
  2. 然后设置注册中心配置,监控中心配置,应用配置,模块配置等
  3. ConfigureBean的后置操作

spring注解方式整合Dubbo源码解析

1.2.6.9.4、ReferenceBeanBuilder#preConfigureBean
  1. 这里又使用到了DataBinder来给referenceBean来做数据绑定
  2. 这里给filter和listener属性注册了StringTrimmerEditor,他会帮助格式化String,去除前后空格
  3. 处理parameters属性,通过自定义的PropertyEditorSupport,把String处理成map
  4. 调用dataBinder#bind方法,这里会自动给referenceBean绑定配置文件中的相关配置

spring注解方式整合Dubbo源码解析

1.2.6.9.5、AbstractAnnotationConfigBeanBuilder#configureRegistryConfigs

这里设置配置的逻辑一致,都是从@Reference注解中获取对应的配置名,然后从spring容器中找到对应的配置Bean,然后设置到属性中。

spring注解方式整合Dubbo源码解析

1.2.6.9.6、ReferenceBeanBuilder#postConfigureBean
  1. 这里还是设置一些属性到ReferenceBean中,其中具体就不细看了,和上面类似

spring注解方式整合Dubbo源码解析

1.2.6.10 、ReferenceAnnotationBeanPostProcessor#cacheInjectedReferenceBean

这里就是把对应的注入点和对应的注入值放入缓存中

spring注解方式整合Dubbo源码解析

1.2.6.11 、ReferenceAnnotationBeanPostProcessor#buildProxy

  1. 调用buildInvocationHandler方法构造一个InvocationHandler
  2. 创建一个proxy对象返回,这里需要代理对象是因为Dubbo在调用的时候就算是本地服务也有负载均衡,注册中心等一系列其他的逻辑,因此不能直接返回服务实现类,而是返回代理对象,公共的操作让代理对象去执行

spring注解方式整合Dubbo源码解析

1.2.6.12 、ReferenceAnnotationBeanPostProcessor#buildInvocationHandler

.1 先从缓存中获取,如果缓存中没有就创建一个
2. 判断当前spring容器中有没有包含着这个引用的bean,如果有说明是本地调用,则缓存起来
3. 如果是远程调用,则会调用ReferenceBeanInvocationHandler#init方法

spring注解方式整合Dubbo源码解析

1.2.6.12 、ReferenceBeanInvocationHandler#init

  1. 这里会调用referenceBean#get方法,会返回一个代理对象
  2. 这里#referenceBean#get方法是服务引入的核心逻辑,我们后续章节剖析

spring注解方式整合Dubbo源码解析
最后这里返回的代理对象会被设置到对应标注了@Reference的属性中,然后调用其方法都会走到代理对象这里,Dubbo会在这里面做一系列远程调用,服务容错,服务逻辑的逻辑。