dubbo如何利用spring扩展点完成初始化
dubbo在初始化的时候,充分利用了spring的扩展点,进行初始化,这篇笔记主要记录dubbo是如何利用spring的扩展点来进行初始化的;
这篇笔记只记录dubbo启动的时候的源码,不记录启动过程中服务导出和服务引入的源码
dubbo关键类
@DubboComponentScan
DubboComponentScanRegistrar
ServiceAnnotationBeanPostProcessor
ReferenceAnnotationBeanPostProcessor
ServiceBean
ReferenceBean
@Service
@Reference
dubbo在进行服务导出和服务引入之前,需要将对应的bean放入到spring容器中,比如:当前在UserService中引入了一个TestService,同时对外提供了一个OrderService的类
那dubbo在启动的时候,会扫描将加了@Service的OrderService先注入到spring的容器中,在spring容器启动完成之后,再去zk进行服务注册
同理在初始化UserService的时候,会对@Reference修饰的变量 TestService进行注入,注入的时候,就会去zk拉取对应的服务提供者,然后生成代理对象,将代理对象注入到UserService中
结论
1.在@DubboComponentScan注解中会import一个DubboComponentScanRegistrar,这个DubboComponentScanRegistrar是ImportBeanDefinitionRegistry的实现类
2.在其registerBeanDefinitions方法中会注入两个bean:ReferenceAnnotationBeanPostProcessor和ServiceAnnotationBeanPostProcessor
3.ServiceAnnotationBeanPostProcessor是BeanDefinitionRegistryPostProcessor的实现类,所以在其postProcessorBeanDefinitionRegistry()方法中,会调用dubboClassPathBeanDefinitionScanner的scan方法,进行扫描,将加了@Service注解的bean扫描到beanDefinitionMap中
添加到beanDefinitionMap中的时候,有一个点需要特别注意的是:对于dubbo的@Service注解所修饰的bean,在spring的beanDefinitionMap中,会有两个会beanDefinition,一个是正常的beanDefinition,可以理解为和spring扫描的bean是无差别的,另外一个beanDefinition的beanClass被设置为ServiceBean,这个我觉得和Mybatis的接口在被注入到beanDefinitionMap中的时候有点类似
在初始化bean的时候,如果整个spring容器刷新完成了,serviceBean会监听对应的事件,在监听到之后,开始进行服务导出的处理,这时候,调用的就是dubbo中的代码,完成:在zk中注册服务、开启netty等
3.ReferenceAnnotationBeanPostProcessor是一个后置处理器,会对加了@Reference注解的bean进行处理,如果A这个类中通过@Reference注入了bean,在注入的时候,会被这个后置处理器处理
在一个bean初始化的过程中,会进行属性注入,调用其inject方法,然后在inject方法中,会对要注入的bean进行处理,将referenceClass,也就是要注入的bean,包装成referenceBean,然后调用referenceBean的getObject()方法进行服务引入,在referenceBean.getObject()方法中,返回的是一个代理对象,在其getObject()方法中,会开启完成服务引入,然后在属性注入的时候,注入的就是一个代理对象
源码
@DubboComponentScan
这个注解如果陌生的话,那@EnableDubbo这个注解应该不会陌生
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {}
我们就直接来看@DubboComponentScan这个注解了
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {}
DubboComponentScanRegistrar
可以看到,在其注解上,通过@Import注解,引入了这个类,这个类是ImportBeanDefinitionRegistrar的实现类,关于ImportBeanDefinitionRegistrar这个扩展点,在前面@Import原理
中有介绍过,这个也是spring的扩展点之一,在初始化的过程中,会调用ImportBeanDefinitionRegistrar实现类的registerBeanDefinitions()方法,
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);registerReferenceAnnotationBeanPostProcessor(registry);}
在这个方法中,这两行代码比较简单,就不贴代码了,主要是调用BeanDefinitionRegistry的registerBeanDefinition方法进行bean的注入,但是这里的注入,只是将bean转换成beanDefinition,然后存入到beanDefinitionMap中
这里分别添加到beanDefinitionMap中的两个bean是:ServiceAnnotationBeanPostProcessor和ReferenceAnnotationBeanPostProcessor
ServiceAnnotationBeanPostProcessor
可以看到,这个类实现了BeanDefinitionRegistryPostProcessor,所以在spring容器启动的时候,会调用该方法,具体是什么时候调用的,这就是spring处理的代码了,只需要按照spring的要求,实现相应的接口,然后将实现类放入到spring容器中即可,具体可以参考前面的spring源码笔记 beanDefinitionRegistryPostProcessor笔记
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {// 解析要扫描的包Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);// 下面if判断中的代码,就是去扫描@Service注解所标记的类if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {registerServiceBeans(resolvedPackagesToScan, registry);} else {if (logger.isWarnEnabled()) {logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");}}
}
下面这个方法中,会完成对beanDefinition的处理
private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {// 这里是dubbo自己继承spring的ClassPathBeanDefinitionScanner,自己实现了一个扫描类DubboClassPathBeanDefinitionScanner scanner =new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);scanner.setBeanNameGenerator(beanNameGenerator);// 这里是设置该扫描器要过滤的bean,过滤条件是添加了Service注解scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));// 对扫描的包进行遍历,有可能设置多个for (String packageToScan : packagesToScan) {// Registers @Service Bean first// 按照指定的路径扫描,并将class转换为beanDefinitionMapscanner.scan(packageToScan);// Finds all BeanDefinitionHolders of @Service whether @ComponentScan scans or not.// 这里会重新查找一遍,获取到所有的加了@Service注解的beanDefinitionSet<BeanDefinitionHolder> beanDefinitionHolders =findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {// 这个方法中,会对beanDefinition进行一些处理,简单而言,就是将beanName对应的beanDefinition中的beanClass属性设置为ServiceBeanregisterServiceBean(beanDefinitionHolder, registry, scanner);}if (logger.isInfoEnabled()) {logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " +beanDefinitionHolders +" } were scanned under package[" + packageToScan + "]");}} else {if (logger.isWarnEnabled()) {logger.warn("No Spring Bean annotating Dubbo's @Service was found under package["+ packageToScan + "]");}}}
}
1、scanner.scan(packageToScan);
在这个方法中,会对加了@Service注解的bean进行扫描,然后将bean注入到beanDefinitionMap中
2、Set beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
在这个方法中,找到所有加了@Service注解的bean,然后返回
3、在registerServiceBean(beanDefinitionHolder, registry, scanner);
在这个方法中,会对找到的所有加了@Service注解的beanDefinition,然后对这些beanDefinition信息进行一些处理;主要是对Service注解中指定的dubbo属性进行解析,解析之后,放到beanDefinition中,然后还会将beanDefinition的beanClass设置为ServiceBean
这个方法的代码就不贴了,要不然代码太多了
然后在将beanDefinition的属性调整了之后,会就把beanDefinition放入到beanDefinitionMap中,调用的也是org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
所以:对于@Service注解的bean,实际会注入到spring容器中两个bean,一个是正常的beanDefinition,beanName也是正常的;另外一个beanDefinition对应的beanName,就是serviceBean:com.xxx等这个格式的,并且其beanClass是serviceBean
ReferenceAnnotationBeanPostProcessor
这是ReferenceAnnotationBeanPostProcessor的类图,可以看到,这个类是一个后置处理器,在该后置处理器中,主要处理的就是@Reference注解,我们来看下这个类中的关键方法
com.alibaba.dubbo.config.spring.beans.factory.annotation.AnnotationInjectedBeanPostProcessor#postProcessMergedBeanDefinition
/*** 这是referenceAnnotationBeanPostProcessor的核心方法之一:在第三个后置处理器被调用的时候,会执行该逻辑* 找到当前bean中,添加了@Reference注解的属性信息,然后在后面第六个后置处理器执行的时候,会依次取出所有的待注入属性* 进行注入* @param beanDefinition* @param beanType* @param beanName*/
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {if (beanType != null) {InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);metadata.checkConfigMembers(beanDefinition);}
}
具体下面的逻辑我就不贴代码了,其实和@Autowired和@Resource注解解析的套路是一样的,在第三个后置处理器执行的时候,找到要注入的属性,然后在第六个后置处理器执行的时候,就直接从这里的缓存中拿到要注入的属性,然后进行属性注入,接下来我们来看第六个后置处理器的执行方法
com.alibaba.dubbo.config.spring.beans.factory.annotation.AnnotationInjectedBeanPostProcessor#postProcessPropertyValues
InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
try {metadata.inject(bean, beanName, pvs);
} catch (BeanCreationException ex) {throw ex;
}
这里的这个方法也是和AutowireAnnotationBeanPostProcessor得逻辑一样,从上面一行代码中,找到要注入的属性之后,在inject()方法中,进行属性注入
主要需要看的是inject()方法:
在inject方法中,dubbo2.6和dubbo2.9版本是不一样的,具体2.7和2.8版本采用的是哪种方式,我没注意看
不管哪个版本,这个inject方法要完成的操作是一样的,就是从注册中心拉去对应的服务提供者,然后生成一个代理对象注入到当前bean中,这里所谓的拉取服务提供者的过程,也就是服务引入的过程,具体的,可以看ReferenceConfig.get()方法
在2.6和2.9版本中,都是先根据当前要注入的类型,生成一个referenceBean对象,生成这个对象之后,在2.6版本中,会通过proxy动态代理的方式,在调用动态代理的时候,需要一个invocationHandler对象,在buildInvocationHandler对象的时候,去调用referenceConfig的get()方法,然后返回一个代理对象;
但是在2.9的版本中,返回了一个referenceBean对象,然后调用referenceBean的getObject()方法,在其getObject()方法中,会调用ReferenceConfig的get()方法,完成服务引入的过程
在服务引入完成之后,调用field.set()方法完成属性注入
所以:对于@Reference注解,其实就是会在属性注入的时候,生成一个referenceBean对象,将其ref属性设置为@Reference指定的bean;不管是哪个版本,核心的思想就是一样的,都是在属性注入的时候,去完成服务引入的,也就是通过ReferenceBean的getObject()方法完成的
总结而言,dubbo在完成初始化的过程中,用到了spring以下几个扩展点:
1.beanDefinitionRegistryPostProcessor --> ServiceAnnotationBeanPostProcessor
2.beanPostProcessor --> ReferenceAnnotationBeanPostProcessor
3.ImportBeanDefinitionRegistrar --> DubboComponentScanRegistrar
4.FactoryBean --> ReferenceBean
dubbo如何利用spring扩展点完成初始化相关推荐
- 利用Spring扩展点模拟MyBatis的注解编程「知识点多多」「扩展点实战系列」- 第448篇
历史文章(文章累计440+) <国内最全的Spring Boot系列之一> <国内最全的Spring Boot系列之二> <国内最全的Spring Boot系列之三> ...
- spring扩展点之二:spring中关于bean初始化、销毁等使用汇总,ApplicationContextAware将ApplicationContext注入...
<spring扩展点之二:spring中关于bean初始化.销毁等使用汇总,ApplicationContextAware将ApplicationContext注入> <spring ...
- 利用SPRING管理热加载的GROOVY对象!
作者:paleXu的技术博客 来源:https://palexu.github.io/posts/spring-dynamic-load-groovy-bean?hmsr=toutiao.io& ...
- Dubbo 正式支持 Spring 6 Spring Boot 3
作者:Dubbo 社区 Dubbo 简介 Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java.Golang 等多语言 SDK 实现. ...
- java观察者模式在spring中的应用_利用spring自己实现观察者模式
利用spring,自己实现的一个观察者模式,写着玩玩,目的是为了加深理解,下次用Spring自带的玩一玩. 首先我们定义一个侦听类接口 package com.hyenas.common.listen ...
- 利用Spring框架封装的JavaMail现实同步或异步邮件发送
利用Spring框架封装的JavaMail现实同步或异步邮件发送 作者:张纪豪 J2EE简单地讲是在JDK上扩展了各类应用的标准规范,邮件处理便是其中一个重要的应用.它既然是规范,那么我们就可以通过J ...
- 利用Spring MVC 上传图片文件
本文转自:http://amcucn.iteye.com/blog/264457.感谢作者 近日在工作当中,需要用到上传图片的功能,然而自己平时学习的时候只会使用struts的上传功能,但因为项目并没 ...
- 利用Spring的AbstractRoutingDataSource解决多数据源的读写分离问题
背景 最近项目中为了提高数据库读写速度,想要横向扩展Oracle数据库,一个Master,多个Slave.master可以读写数据,Slave只能读数据.这就是多数据源问题了.怎么利用Spring解决 ...
- spring boot java app_利用spring boot创建java app
利用spring boot创建java app 背景 在使用spring框架开发的过程中,随着功能以及业务逻辑的日益复杂,应用伴随着大量的XML配置和复杂的bean依赖关系,特别是在使用mvc的时候各 ...
- druid dubbo 生产者_dubbo项目扩展druid sql监控
最近在项目中采用dubbo来管理服务,但是数据库连接池这一块采用druid,网上配置druid sql监控的都是基于web.xml配置,需要servlet容器支持,但是dubbo项目服务端这边一般不 ...
最新文章
- Educational Codeforces Round 12 A. Buses Between Cities 水题
- 从零开始html css,HTML/CSS从零开始-常用属性(三)
- 服务器监控系统的介绍,客户服务系统服务器监控系统
- pythorch基本信息查询
- android用上传图片到服务器上,Android使用post方式上传图片到服务器的方法
- php jq表格,如何用jQuery操作表单和表格
- 基于TCP协议的网络摄像头的设计与实现
- python 用户登录判断
- beta分布_常用概率分布总结(2)
- github中的各种操作
- Python实现人工神经网络逼近股票价格
- Python推箱子小游戏源代码
- 【微信小程序】ColorUI——一个多彩漂亮的UI组件库
- 北京市居住证办理问题的整理
- 计算机开题报告答辩评语,开题报告评语4篇
- Linux的隐匿技巧【渗透测试】
- 解释部署大数据解决方案应遵循的步骤
- python中 s是什么意思_这里面的s.name是什么意思啊?
- 【JVM基础】垃圾回收算法详解(引用计数、标记、清除、压缩、复制)
- 李峋同款爱心代码-电视剧《点燃我温暖你》
热门文章
- 翻译: 4.2. 从零开始实现多层感知器MLP pytorch
- Mac大文件分包split与合并cat,加密压缩zip
- java为什么要连接Mysql_为什么要启动mysql workbech,java才能连接mysql数据库呢?
- SSD,单次多框检测器Single Shot Multibox Detector,超越YOLO和Fast-RCNN
- 535. TinyURL 的加密与解密
- Inception v1
- oracle 7302,无法创建链接服务器XXXXX的OLEDB访问接口OraOLEDBOracle的实例。(MicrosoftSQLServer,错误7302)...
- Error in loadNamespace(name) : there is no package called ‘yaml’
- java controller的生命周期_一张图搞懂Spring bean的完整生命周期
- .NET Core微服务之基于Steeltoe使用Zipkin实现分布式追踪