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 技能