关于后端:源码解析Dubbo3-的-Spring-适配原理与初始化流程

48次阅读

共计 7560 个字符,预计需要花费 19 分钟才能阅读完成。

Dubbo 国内影响力最大的开源框架之一,非常适合构建大规模微服务集群的,提供开发框架、高性能通信、丰盛服务治理等能力。同时 Dubbo 无缝反对 Spring、Spring Boot 模式的开发,这篇文章帮忙大家了解 Dubbo 是怎么和 Spring 做集成的,非常适合关怀原理是先的开发者。

感兴趣的敌人能够间接拜访官网体验 Spring+Dubbo 开发微服务 或搜寻关注官网微信公众号:Apache Dubbo

Spring Context Initialization

首先,咱们先来看一下 Spring context 初始化次要流程,如下图所示:

相干代码:org.springframework.context.support.AbstractApplicationContext#refresh()

简略形容一下每个步骤蕴含的内容:

  1. 创立 BeanFactory:读取加载 XML/ 注解定义的 BeanDefinition。
  2. prepareBeanFactory: 注册提前加载的各种内置 post-processor 以及环境变量等。
  3. invokeBeanFactoryPostProcessors: 加载 BeanDefinitionRegistryPostProcessor 和 BeanFactoryPostProcessor。留神这里的加载程序比较复杂,还波及到屡次加载,具体请查看代码。罕用于加载较早初始化的组件,如属性配置器 PropertyPlaceholderConfigurer 和 PropertySourcesPlaceholderConfigurer。还有一个比拟重要的 ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口,用于加载 @Configuration 类,解析 @Bean 定义并注册 BeanDefinition。这个阶段罕用于注册自定义 BeanDefinition。
  4. registerBeanPostProcessors: 加载并注册各种 BeanPostProcessor,罕用于批改或包装(代理)bean 实例,如 Seata 的 GlobalTransactionScanner。
  5. registerListeners: 加载并注册 ApplicationListener,解决 earlyApplicationEvents
  6. finishBeanFactoryInitialization: 注册 EmbeddedValueResolver,解冻配置
  7. 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 次要有几种形式:

  1. 在 Spring XML 中定义 < bean />,由 Spring 解析生成并 BeanDefinition
  2. 在 Spring java config 中申明 @Bean 办法,由 Spring 解析生成并 BeanDefinition
  3. 调用 BeanDefinitionRegistry.registerBeanDefinition()办法手工注册 BeanDefinition
  4. 通过 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 的类型预测次要包含上面几种:

  1. 如果有 DecoratedDefinition,则笼罩 BeanDefinition,查看合并后的 beanClass 是否匹配
  2. 通过 FactoryBean.OBJECT_TYPE_ATTRIBUTE 属性获取 beanType (since 5.2)
  3. 实例化这个 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);
    }

存在的问题:

  1. 没有一个固定的初始化机会,而是与 ReferenceBean 初始化相干。如果 ReferenceBean 被过早初始化,经常出现 dubbo 配置失落、属性占位符未解决等谬误。
  2. 可能在 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 个阶段:

  1. 在 BeanFactoryPostProcessor 阶段解析 @DubboReference/@DubboService 注解并注册 BeanDefinition。因为此时还是 BeanDefinition 解决阶段,故注册的 ReferenceBean 能够被后续加载的业务 bean 应用 @Autowire 依赖注入。同时,也扩大反对在 @Configuration bean 办法应用 @DubboReference/@DubboService 注解。
  2. 在加载完所有 PropertyResourceConfigurer 和 BeanPostProcessor 之后才会执行 DubboConfigBeanInitializer 初始化 config bean,解决了属性 占位符未解决和 BeanPostProcessor 拦挡失败的问题。
  3. 监听在 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 技能

正文完
 0