共计 7560 个字符,预计需要花费 19 分钟才能阅读完成。
Dubbo 国内影响力最大的开源框架之一,非常适合构建大规模微服务集群的,提供开发框架、高性能通信、丰盛服务治理等能力。同时 Dubbo 无缝反对 Spring、Spring Boot 模式的开发,这篇文章帮忙大家了解 Dubbo 是怎么和 Spring 做集成的,非常适合关怀原理是先的开发者。
感兴趣的敌人能够间接拜访官网体验 Spring+Dubbo 开发微服务 或搜寻关注官网微信公众号:Apache Dubbo
Spring Context Initialization
首先,咱们先来看一下 Spring context 初始化次要流程,如下图所示:
相干代码:org.springframework.context.support.AbstractApplicationContext#refresh()
简略形容一下每个步骤蕴含的内容:
- 创立 BeanFactory:读取加载 XML/ 注解定义的 BeanDefinition。
- prepareBeanFactory: 注册提前加载的各种内置 post-processor 以及环境变量等。
- invokeBeanFactoryPostProcessors: 加载 BeanDefinitionRegistryPostProcessor 和 BeanFactoryPostProcessor。留神这里的加载程序比较复杂,还波及到屡次加载,具体请查看代码。罕用于加载较早初始化的组件,如属性配置器 PropertyPlaceholderConfigurer 和 PropertySourcesPlaceholderConfigurer。还有一个比拟重要的 ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口,用于加载 @Configuration 类,解析 @Bean 定义并注册 BeanDefinition。这个阶段罕用于注册自定义 BeanDefinition。
- registerBeanPostProcessors: 加载并注册各种 BeanPostProcessor,罕用于批改或包装(代理)bean 实例,如 Seata 的 GlobalTransactionScanner。
- registerListeners: 加载并注册 ApplicationListener,解决 earlyApplicationEvents
- finishBeanFactoryInitialization: 注册 EmbeddedValueResolver,解冻配置
- preInstantiateSingletons: 遍历加载单例 bean,也就是加载一般的 bean,包含 @Controller, @Service, DAO 等。
FactoryBean
Spring 容器反对两种 bean:一般 bean 和工厂 bean(FactoryBean)。咱们常常写的 @Controller/@Service 这种被 Spring 间接初始化的 bean 就是一般 bean,而 FactoryBean 则是先由 Spring 先创立 FactoryBean 实例,而后由其再创立最终的 bean 实例。
[Spring BeanFactory] --create---> [XxxFactoryBean instance] --create--> [Final Bean Instance]
FactoryBean 接口如下:
public interface FactoryBean<T> {
/**
* Return an instance (possibly shared or independent) of the object managed by this factory.
*/
T getObject() throws Exception;
/**
* Return the type of object that this FactoryBean creates, or null if not known in advance.
* This allows one to check for specific types of beans without instantiating objects, for example on autowiring.
*/
Class<?> getObjectType();}
BeanDefinition
Spring bean 分为注册和创立实例两大阶段。将从 Spring XML/ 注解解析到的 bean 信息放到 BeanDefinition,而后将其注册到 BeanFactory,前面会依据 BeanDefinition 来初始化 bean 实例。不论是一般 bean 还是工厂 bean,都是先注册 bean definition,而后依照依赖程序进行初始化。
两者 BeanDefinition 的差别是:
- 广泛 bean 的 BeanDefinition 的 beanClassName 为最终 bean 的 class
- 工厂 bean 的 BeanDefinition 的 beanClassName 为工厂 bean 的 class
注册 Bean 次要有几种形式:
- 在 Spring XML 中定义 < bean />,由 Spring 解析生成并 BeanDefinition
- 在 Spring java config 中申明 @Bean 办法,由 Spring 解析生成并 BeanDefinition
- 调用 BeanDefinitionRegistry.registerBeanDefinition()办法手工注册 BeanDefinition
- 通过 SingletonBeanRegistry.registerSingleton()办法注册 bean 实例。
留神:注册 bean 实例与后面三种注册 BeanDefinition 有实质的区别。打个比方,注册 BeanDefinition 是新儿子,Spring 会治理 bean 的初始化及依赖注入及解决属性占位符,调用 BeanPostProcessor 进行解决等。而注册 bean 实例就是他人的儿子,Spring 将其视为曾经实现初始化的 bean,不会解决其依赖和属性占位符。前面会讲到 Dubbo 2.7/ 3 两个版本 Reference 注解注册 bean 的差别。
初始化 bean
创立 bean 大略有上面几个步骤:
- 创立实例 createBeanInstance
- 解决依赖 resolveDependency
- 解决属性占位符 applyPropertyValues
其中屡次调用 BeanPostProcessor 进行解决,如果某些 BeanPostProcessor 此时还没注册,则可能导致脱漏解决了以后的 bean。前面会讲到 dubbo 2.7 中提前加载 config bean 导致的一系列问题。
其中要害逻辑请参考代码:AbstractAutowireCapableBeanFactory#doCreateBean()
解决依赖
在 Spring 注解流行起来之后,通常是应用 @Autowire 注解来注入依赖的 bean。此种注入形式大略的流程如下:
- 查找匹配属性类型的 beanName 列表
- 依据 @Qualifier/@Primary/propertyName 等抉择适合的 bean 要害逻辑请参考代码:DefaultListableBeanFactory#doResolveDependency()。
其中第一步,查找匹配类型的 beanName 列表时会调用 ListableBeanFactory#getBeanNamesForType()来枚举查看所有的 beanDefinition。查看 bean type 的逻辑请查看 AbstractBeanFactory#isTypeMatch()。波及的逻辑比较复杂,这里只简略讲一下重要的分支:
- 如果是一般 bean,则查看 BeanDefinition 的 beanClass 是否匹配
- 如果是 FactoryBean,则通过多种形式来预测 bean type
FactoryBean 的类型预测次要包含上面几种:
- 如果有 DecoratedDefinition,则笼罩 BeanDefinition,查看合并后的 beanClass 是否匹配
- 通过 FactoryBean.OBJECT_TYPE_ATTRIBUTE 属性获取 beanType (since 5.2)
- 实例化这个 FactoryBean,调用 getObjectType()办法来获取 beanType 下面提到的第三种状况可能会呈现实例化失败(如解决属性占位符失败)而被屡次创立的问题,即每次预测 bean type 都会尝试实例化,而每次都失败,直到它所依赖的组件都就绪才胜利。
Dubbo ReferenceBean 自身也是一个 FactoryBean,在 2.7 中常常因为预测 bean type 导致被主动初始化,前面会具体讲这个问题。
解决属性
在 Spring 中个别是通过 PropertyPlaceholderConfigurer/PropertySourcesPlaceholderConfigurer 来解决 XML/@Value 中的属性占位符 ${…}。二者都实现了 BeanFactoryPostProcessor 接口,会在 invokeBeanFactoryPostProcessors 阶段被加载,而后遍历解决所有 BeanDefinition 中的属性占位符。
[解析注册 BeanDefinition] => [PropertyResourceConfigurer 解决属性占位符] => [加载 BeanPostProcessor] => [初始化单例 bean]
由此可知,如果在 PropertyPlaceholderConfigurer/PropertySourcesPlaceholderConfigurer 加载前去初始化某个 bean,则这个 bean 的属性占位符是不会被解决的。这个就是 Dubbo config bean 被过早加载导致无奈解决占位符的根因。
Dubbo Spring 的一些问题及解决办法
Dubbo spring 2.7 初始化过程
初始化入口是 ReferenceBean#prepareDubboConfigBeans(),即当第一个 ReferenceBean 初始化实现时,尝试加载其余 dubbo config bean。
@Override
public void afterPropertiesSet() throws Exception {
// Initializes Dubbo's Config Beans before @Reference bean autowiring
prepareDubboConfigBeans();
// lazy init by default.
if (init == null) {init = false;}
// eager init if necessary.
if (shouldInit()) {getObject();
}
}
private void prepareDubboConfigBeans() {beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class);
beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class);
beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class);
beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class);
beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class);
beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class);
beansOfTypeIncludingAncestors(applicationContext, ConsumerConfig.class);
beansOfTypeIncludingAncestors(applicationContext, ConfigCenterBean.class);
beansOfTypeIncludingAncestors(applicationContext, MetadataReportConfig.class);
beansOfTypeIncludingAncestors(applicationContext, MetricsConfig.class);
beansOfTypeIncludingAncestors(applicationContext, SslConfig.class);
}
存在的问题:
- 没有一个固定的初始化机会,而是与 ReferenceBean 初始化相干。如果 ReferenceBean 被过早初始化,经常出现 dubbo 配置失落、属性占位符未解决等谬误。
- 可能在 BeanPostProcessor 加载实现前初始化 ReferenceBean,将导致相似 Seata 这种通过 BeanPostProcessor 机制的组件拦挡失败。
Dubbo spring 3 的初始化过程
Dubbo 3 中进行大量重构,下面的痛点问题曾经被解决,初始化次要流程如下:
[Spring 解析 XML/@Configuration class 注册 BeanDefinition] => [加载 BeanFactoryPostProcessor(蕴含 PropertyResourceConfigurer)]
=> [1. 解析 @DubboReference/@DubboService 注解并注册 BeanDefinition]
=> [加载并注册 BeanPostProcessor]
=> [加载 ApplicationListener] => [2. 加载 DubboConfigBeanInitializer 初始化 config bean]
=> [初始化单例 bean] => [依赖注入 ReferenceBean]
=> [3. 监听 ContextRefreshedEvent 事件,启动 dubbo 框架]
次要蕴含 3 个阶段:
- 在 BeanFactoryPostProcessor 阶段解析 @DubboReference/@DubboService 注解并注册 BeanDefinition。因为此时还是 BeanDefinition 解决阶段,故注册的 ReferenceBean 能够被后续加载的业务 bean 应用 @Autowire 依赖注入。同时,也扩大反对在 @Configuration bean 办法应用 @DubboReference/@DubboService 注解。
- 在加载完所有 PropertyResourceConfigurer 和 BeanPostProcessor 之后才会执行 DubboConfigBeanInitializer 初始化 config bean,解决了属性 占位符未解决和 BeanPostProcessor 拦挡失败的问题。
- 监听在 Spring context 事件,在其加载结束时启动 dubbo 框架。
反对在 @Configuration bean 办法应用 @DubboReference/@DubboService 注解
参考 Dubbo spring 3 的初始化过程的第 1 阶段。
属性占位符解决失败
参考 Dubbo spring 3 的初始化过程的第 2 阶段。
ReferenceBean 被过早初始化问题
预测 ReferenceBean beanType 导致
Dubbo ReferenceBean 自身也是一个 FactoryBean,在 2.7 中常常因为预测 bean type 导致被主动初始化。例如用户自定义的某个 BeanFactoryPostProcessor bean 应用了 @Autowire 注解依赖注入某个业务 bean,而且这个自定义的 BeanFactoryPostProcessor bean 优先级比解决属性占位符的 PropertyResourceConfigurer 高,则此时呈现解决属性占位符失败。
Dubbo 3 中 ReferenceBean 通过上面两种形式解决预测 type 的问题:
FactoryBean 的类型预测次要包含上面几种:
如果有 DecoratedDefinition,则笼罩 BeanDefinition,查看合并后的 beanClass 是否匹配
通过 FactoryBean.OBJECT_TYPE_ATTRIBUTE 属性获取 beanType (since 5.2)
ReferenceBean 被间接依赖导致过早初始
如果在 Dubbo config bean 初始化前被依赖主动创立 ReferenceBean 实例,并创立一个 Lazy proxy 类注入到依赖的类中,不须要解决属性占位符,不会拉起 Dubbo 框架。其余的 config bean 则固定在 PropertyResourceConfigurer 和 BeanPostProcessor 加载实现后才会执行初始化,防止了上述问题。
Reference 注解可能呈现 @Autowire 注入失败的问题
在 Dubbo 2.7 中,在 BeanPostProcessor 中解析 @DubboReference/@Reference 注解,创立并注入 ReferenceBean 实例到 Spring 容器。这种形式有几个问题:
@DubboReference/@Reference 注解与 XML 定义的 < dubbo:reference /> 初始化形式不统一,前者是由 dubbo 初始化,后者是由 Spring 容器负责初始化。
执行机会导致的依赖注入失败问题。依照失常的在 invokeBeanFactoryPostProcessors 阶段注册结束所有 BeanDefinition,而 dubbo 2.7 的 ReferenceAnnotationBeanPostProcessor 是在 BeanPostProcessor 执行时才创立 ReferenceBean,可能呈现某些比它早初始化的 bean 应用 @Autowire 注入失败的状况。
在 Dubbo 3 中,改成在 BeanFactoryPostProcessor 解析 @DubboReference/@Reference 注解并注册 ReferenceBean 的 BeanDefinition,记录字段将要注入的 referenceBeanName。在 BeanPostProcessor 执行时通过 BeanFactory().getBean(referenceBeanName)获取到 ReferenceBean 实例。
搜寻关注官网微信公众号:Apache Dubbo,理解更多业界最新动静,把握大厂面试必备 Dubbo 技能