dubbo既可以单独使用不整合spring,也可以借助spring;单独代码如下:
服务提供者
服务消费者
可以看出并没有使用注解以及properties配置文件,也没有使用spring的容器去管理bean,而是用的时候直接new,并且配置信息直接写进实例中;
而dubbo整合spring,会用到properties配置文件集中保存配置信息,用注解将实例交给spring容器管理;
服务提供者
服务消费者
通常dubbo都会整合spring使用,既方便修改配置信息,也实现了bean的方便使用,降低了层与层,模块与模块之间调用的耦合;
dubbo中有两个重要注解@DubboService和@DubboReference;具体参考链接如下
https://www.cnblogs.com/ShakeTian/articles/16143086.html
@DubboService用在类上,具有spring的@Service的作用;
每个服务对应一个服务管理类org/apache/dubbo/config/spring/ServiceBean.java,该类管理者每一个dubbo服务,一方面负责管理properties文件中的配置,如其父类含有一个list,管理着某个服务配置文件中的protocol属性即协议,一个dubbo可以支持多个协议如http,dubbo等;
org/apache/dubbo/config/AbstractInterfaceConfig.java类也是其父类,也管理着applicationConfig,List,ConfigCenterConfig等;
另一方面负责接收@DubboService注解中的值;
1)@EnableDubbo注解
Spring会加载properties文件的内容到environment实例中,dubbo只负责将environment实例中的值根据prefix分类,载入到不同的xxxConfig中;
而properties文件中又分为单,复两种情况,@EnableDoubleConfig注解有一个multiple字段来标示这种情况,为false表示单,为true表示复;
1.1)@EnableDubboConfig注解
先分析@EnableDubboConfig注解;
1.1.1)DubboConfigConfigurationRegistrar#registerBeanDefinitions方法
@EnableDubboConfig注解的属性 只是一个布尔类型的multiple,但该注解核心包含@Import(DubboConfigConfigurationRegistrar.class),像这些加了@Import 的注解,在什么时候会被扫描解析呢?这里主要涉及到spring知识;
a)AbstractApplicationContext#refresh,这个在spring启动时就会执行;
b)AbstractApplicationContext#invokeBeanFactoryPostProcessors;
c)PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors;
d)其中会执行BeanDefinitionRegistryPostProcessor实现类的相关方法,这里主要分析ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法;进入到ConfigurationClassPostProcessor#processConfigBeanDefinitions方法;该方法一共有约100行,是处理@Import注解的核心方法,其中又会执行如下方法
e)checkConfigurationClassCandidate方法,检查是否是配置类,如果是的,则首先会从容器中取出所有的配置类(@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean),因为这些类上面可能会有@Import注解,将这些注解信息缓存起来;
f)ConfigurationClassParser#parse方法中会执行doProcessConfigurationClass方法,其中又会执行processImports(configClass, sourceClass, getImports(sourceClass), true)方法;getImports方法中会执行collectImports方法;获取类上面的@Import注解;
processImports方法中,会对@Import注解使用方式有三种进行判断,判断到底是哪一种;
情况一,@Import(Xxx.class);
情况二,@Import(XxxImportSelector.class),XxxImportSelector是ImportSelector接口的实现类,重写的selectImports方法的返回值就是我们要导入到IOC容器中的bean;
情况三,@Import(XxxImportBeanDefinitionRegistrar.class),XxxImportBeanDefinitionRegistrar是ImportBeanDefinitionRegistrar接口的实现类,重写的 registerBeanDefinitions方法负责注册一个自定义的beanDefinition;
情况二和三会import进来的实现类进行实例化,缓存起来,后边调用相应重写的方法;
由于dubbo的@EnableDubboConfig注解上边import进来的是一个ImportBeanDefinitionRegistrar接口的实现类,所以会在后边调用registerBeanDefinitions方法;
g)执行完parse方法后,会执行loadBeanDefinitions方法,其中会执行loadBeanDefinitionsFromRegistrars方法,该方法会调用ImportBeanDefinitionRegistrar接口实现类DubboConfigConfigurationRegistrar的registerBeanDefinitions()方法;
DubboConfigConfigurationRegistrar#registerBeanDefinitions方法中会执行如下两个方法;
1.1.1.1)getAnnotationAttributes方法
拿到@EnableDubboConfig注解中的值multiple,该值为false和true分别表示properties文件中的单,复情况,如下所示;该值默认为true;
1.1.1.2)AnnotatedBeanDefinitionReader#register方法
该方法是利用spring中的AnnotatedBeanDefinitionReader来解析DubboConfigConfiguration.Multiple类或者DubboConfigConfiguration.Single类上的注解,然后进行处理;以DubboConfigConfiguration.Single类为例,该类上包含@EnableDubboConfigBindings注解,该注解核心由@Import(DubboConfigBindingsRegistrar.class)组成,而DubboConfigBindingsRegistrar又实现了ImportBeanDefinitionRegistrar, 和EnvironmentAware接口,registerBeanDefinitions方法在某个时候被调用时,会执行DubboConfigBindingRegistrar# registerBeanDefinitions方法,该方法会逐个解析@EnableDubboConfigBinding注解,比如@EnableDubboConfigBinding(prefix = “dubbo.application”, type = ApplicationConfig.class);
type代表要生成的bean,每个bean包含三个核心属性,分别是beanName,beanDefinition,DubboConfigBindingBeanPostProcessor;DubboConfigBindingBeanPostProcessor用于对bean中属性进行赋值的;
第一种情况是当prefix为dubbo.application时,properties文件中如:
dubbo.application.name=dubbo-demo-provider1-application
dubbo.application.logger=log4j
前缀为"dubbo.application"的配置项,会生成一个ApplicationConfig类型的BeanDefinition,并且name和logger属性为对应的值。 第二种情况是当prefix为dubbo.protocols时,properties文件中如:
dubbo.protocols.p1.name=dubbo
dubbo.protocols.p1.port=20880
dubbo.protocols.p1.host=0.0.0.0
dubbo.protocols.p2.name=dubbo
dubbo.protocols.p2.port=20881
dubbo.protocols.p2.host=0.0.0.0
前缀为"dubbo.protocols"的配置项,会⽣成两个ProtocolConfig类型的BeanDefinition,两个
BeanDefinition的beanName分别为p1和p2。
并且还会针对生成的每个BeanDefinition生成一个和它一对一绑定的BeanPostProcessor,类型为DubboConfigBindingBeanPostProcessor.class。如ApplicationConfig类型的bean对应一个DubboConfigBindingBeanPostProcessor,一个ProtocolConfig也会对应一个DubboConfigBindingBeanPostProcessor;不过在dubbo新版本中,并没有为每一个bean绑定一个DubboConfigBindingBeanPostProcessor,而是设置一个DubboConfigBindingBeanPostProcessor,处理所有相关bean;
对于multiple为true的情况
后记:
@EnableDoubleConfig注解中最终是调用@import注解导进来一个类DubboConfigBindingsRegistrar,该类实现了spring中的ImportBeanDefinitionRegistrar和EnvironmentAware接口;
ImportBeanDefinitionRegistrar接口不是直接注册Bean到IOC容器,它的执行时机比较早,准确的说更像是注册Bean的定义信息以便后面的Bean的创建
这个接口提供了如下一个方法:
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
这个方法可以拿到@Import的这个class的Annotation Metadata,以及此时的BeanDefinitionRegistry对象,通过BeanDefinitionRegistry就可以拿到目前所有注册的BeanDefinition,然后可以对这些BeanDefinition进行额外的修改或增强。properties文件中的配置信息最开始被加载进environment实例中,接着会通过registerBeanDefinitions的处理逻辑会将信息进一步加载进beanDefinition中;
这里可以举一反三,其实其他一些组件源码也是基于相同原理将配置信息转为bean注入到ioc容器中,如SpringCloud框架的Feign客户端中FeignClientsRegistrar类也实现了ImportBeanDefinitionRegistrar接口并完成看FeignClients相关Bean定义信息的解析和注册;
在开源框架分布式配置中心 Apollo中也使用到了ImportBeanDefinitionRegistrar。Apollo是和Spring集成的,在Spring或者SpringBoot中使用Apollo来做配置中心是比较方便的,在应用这边相当于一个Apollo客户端,使用的时候也是通过一个注解来开启Apollo客户端的功能,该注解是@EnableApolloConfig;
在启动类上使用了该注解即开启了Apollo客户端的功能,我们看到使用该注解实际上也是Import了一个类ApolloConfigRegistrar,他也是实现了ImportBeanDefinitionRegistrar接口;
在mybatis的源码中,一般在springboot启动类上会标记@MapperScan,以表示开启此功能并且标记扫描的路径,该注解里边包含@Import({MapperScannerRegistrar.class}),该类也实现了ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法;具体参考如下;
https://blog.csdn.net/my_momo_csdn/article/details/95044917
spring关键类之BeanDefinitionRegistryPostProcessor
Spring源码中BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor、BeanPostProcessor,ImportBeanDefinitionRegistrar四者的区别
前三个的区别见:
https://blog.csdn.net/weixin_37862824/article/details/123531719
BeanDefinitionRegistryPostProcessor与ImportBeanDefinitionRegistrar的区别是:
ImportBeanDefinitionRegistrar在ConfigurationClassPostProcessor处理Configuration类期间被调用,用来生成该Configuration类所需要的BeanDefinition;
1.2)@DubboComponentScan注解
接着分析@DubboComponentScan注解;这个注解上边也是有一个@Import(DubboComponentScanRegistrar.class);DubboComponentScanRegistrar类继承了ImportBeanDefinitionRegistrar,重写了registerBeanDefinitions方法,该方法主要解析dubbo自定义的两个注解@Service和@Reference;@Service用在类上,表示当前是dubbo服务端,提供dubbo服务,@Reference用在方法上,表示当前是dubbo消费端,引用指定dubbo服务;
DubboComponentScanRegistrar#registerBeanDefinitions方法中有三个重要的方法;如下:
1.2.1)getPackagesToScan方法
拿到DubboComponentScan注解所定义的包路径,扫描该package下的类,识别这些类上注解;;
1.2.2) registerServiceAnnotationBeanPostProcessor方法
该方法中会注册ServiceAnnotationBeanPostProcessor一个Bean;
该bean实现了BeanDefinitionRegistryPostProcessor接口,所以在Spring启动时会调用postProcessBeanDefinitionRegistry方法;该方法会进行扫描,扫描@Service注解了的类,然后生成BeanDefinition(会生成两个,一个普通的bean,一个ServiceBean),后续的Spring周期中会生成Bean;
在ServiceBean中会监听ContextRefreshedEvent事件,一旦Spring启动完后,就会进行服务导出;
在postProcessBeanDefinitionRegistry方法中会执行registerServiceBeans
方法,其中会实例化dubbo自定义的类DubboClassPathBeanDefinitionScanner,DubboClassPathBeanDefinitionScanner
类继承了spring的ClassPathBeanDefinitionScanner,主要是设置了useDefaultFilters参数为false,并且往过滤器中添加了自定义注解Service.class,这样就可以基于spring的扫描逻辑只对自定义注解进行扫描了。原先useDefaultFilters默认为true,会往过滤器中添加@Component注解,表示只有被@Component修饰的类,才会被扫描到。
调用scan方法完成对@Service注解的扫描后,并且会将被@Service修饰的类封装成beanDefinition放进容器中,实际上被@Service修饰的类就是dubbo服务的实现类;接着遍历每个被@Service注解修饰的实现类的beanDefinition,将其封装成ServiceBean实例,即执行ServiceAnnotationBeanPostProcessor#registerServiceBean方法;该方法执行逻辑相对复杂;目的为了创建serviceBean;在该方法中依次是获取服务实现类,获取@Service注解上的信息,获取服务实现类对应的接口,获取服务实现类对应的bean的名字,
这四个信息作为入参,执行buildServiceBeanDefinition方法;该方法有约100行,主要是为了生成一个ServiceBean,并且给ServiceBean这个实例的各个属性赋值,简单属性直接赋值,复杂的属性需要封装成一个对象再赋值,如ref属性引用的是被@Service注解修饰的服务实现类的bean;
另外ServiceBean这个dubbo中的自定义类实现了spring的ApplicationListener接口,重写了onApplicationEvent方法,所以当spring启动完成后,会调用该方法,该方法中会执行export方法,完成了服务的导出;
而且每生成一个serviceBean,都会以冒号作为分隔符,按照一定格式,将serviceBean,group等等信息连接在一起存进缓存,即将本地应用服务以字符串形式全部存进缓存,方便后边引用服务时判断当前需要引用的服务是否是本地服务;
在spring源码AnnotatedBeanDefinitionReader类的构造方法中会调用AnnotationConfigUtils#registerAnnotationConfigProcessors方法,注册了许多postProcessor,用于bean的生命周期中的各种事件处理;
1.2.3) registerReferenceAnnotationBeanPostProcessor方法
注册ReferenceAnnotationBeanPostProcessor,该类继承了dubbo自定义的AnnotationInjectedBeanPostProcessor抽象类,而该抽象类又实现了InstantiationAwareBeanPostProcessorAdapter接口,所以Spring在启动时,在对属性进行注入时会调用AnnotationInjectedBeanPostProcessor接口中的postProcessPropertyValues方法,在这个过程中会按照@Reference注解的信息去生成一个RefrenceBean对象;
AnnotationInjectedBeanPostProcessor#postProcessPropertyValues方法中首先寻找被@Reference注解修饰的字段和方法,接着调用InjectionMetadata#inject方法对字段和方法进行依赖注入;在这里InjectionMetadata#inject方法中具体实现是会遍历所有被@Reference注解修饰的字段或方法,对字段或者方法的注入又分别调用不同的方法,这里运用了策略模式,如下所示;
这里以字段注入为例,
进入getInjectedObject方法中,其中会执行ReferenceAnnotationBeanPostProcessor#doGetInjectedBean方法,该方法中会生成动态代理对象注入到字段中;该方法中首先会判断被@Reference修饰的类是否是本地的dubbo服务,什么意思呢,就是本地应用的服务,即无需远程rpc调用就可以调用到的服务,如果是本地服务,其实简化流程只要带上配置,无需走rpc调用就可以,如果是rpc调用,则需要生成一个动态代理对象,进行方法调用,增强的内容包括调用方式,调用配置等等;
判断被@Reference修饰的字段所引用的服务是否是本地服务的依据如下,先构造出如下格式的字符串,再在spring容器中判断若存在如下格式的serviceBean,则表示是本地的dubbo服务;因为如果是其他应用程序的服务,首先别人的代码在本地服务器不存在,所以spring容器中也就不存在这种格式的字符串,所以可以判断出是别人的应用;
a)buildReferencedBeanName方法
该方法根据被@Reference修饰的字段,找到对应的serviceBean,按一定规则生成一个字符串referencedBeanName,规则为ServiceBean:interfaceClassName:version:group;
b)getReferenceBeanName方法
根据@Reference注解中的值,拼接成字符串referenceBeanName,作为缓存的key,value为ReferenceBean实例,该实例中包含了动态代理对象,以及@Reference注解中的信息;
c)buildReferenceBeanIfAbsent方法
若在map的key中不存在当前referenceBeanName,则往map的value中添加ReferenceBean实例,是为了缓存已经使用过的服务;
d)registerReferenceBean方法
把referenceBean添加到spring容器中去;这样在使用了@Reference注解的类中,再使用@Autowired注解,也和@Reference注解有一样的效果;
e) getOrCreateProxy方法
其中会调用referenceBean.get()方法生成一个动态代理对象;