Dubbo3的Spring适配原理与初始化流程源码解析

目录
  • 引言
  • Spring Context Initialization
  • FactoryBean
  • BeanDefinition
  • 初始化bean
    • 解决依赖
    • 解决属性
  • Dubbo Spring的一些问题及解决办法
    • Dubbo spring 2.7 初始化过程
    • Dubbo spring 3的初始化过程
    • 属性占位符解决失败
    • ReferenceBean被过早初始化问题
    • Reference注解可能出现@Autowire注入失败的问题

引言

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

感兴趣的朋友可以直接访问官网体验 Spring+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被直接依赖导致过早初始</strong> 如果在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实例。

以上就是Dubbo3的Spring适配原理与初始化流程源码解析的详细内容,更多关于Dubbo3 Spring适配初始化流程的资料请关注我们其它相关文章!

(0)

相关推荐

  • Spring与Dubbo搭建一个简单的分布式详情

    目录 一.zookeeper 环境安装搭建 二.实现服务接口 dubbo-interface 1. dubbo-interface 项目创建 2. 创建接口类 3. 将项目打成 jar 包供其他项目使用 三.实现服务提供者 dubbo-provider 1. dubbo-provider 项目创建 2. pom 文件引入相关依赖 3. 在 application.properties 配置文件中配置 dubbo 相关信息 4. 实现接口 5. 服务提供者启动类编写 四.实现服务消费者 dubbo

  • springboot 整合dubbo3开发rest应用的场景分析

    目录 一.前言 二.dubbo适用场景 1.内部单体应用微服务化 2.应用服务更多面向内部服务间调用 3.对服务管理趋于精细化 三.dubbo微服务治理过程中的一个难题 四.与springboot的整合使用 1.公共pom依赖 2.common-service 模块 3.provider-demo 模块 pom依赖 核心配置文件 服务实现类 启动类 4.consumer-demo 模块 4.接口测试 4.1 启动zookeeper服务 4.2 启动provider服务 4.3 启动consume

  • SpringBoot搭建Dubbo项目实现斐波那契第n项详解

    目录 step1 新建项目 step2 新建需要的包和接口以及实现类 step3 在两个项目的resource下新建配置文件 step4 代码编写 导入依赖 provider consumer 端口冲突更改 step5 运行 step1 新建项目 方法1:直接在IDEA里新建如图: 方法2:在start.spring.io新建 可能有的小朋友已经发现了,第一种方式的Server URL就是第二个的网站,都是一样的 要新建两个项目,第一个项目如上图所示,第二个项目只需要将provider改为con

  • Java Spring Dubbo三种SPI机制的区别

    目录 前言 SPI 有什么用?​ JDK SPI​ Dubbo SPI Spring SPI​ 对比​ 前言 SPI 全称为 Service Provider Interface,是一种服务发现机制.SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类.这样可以在运行时,动态为接口替换实现类.正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能. 本文主要是特性 & 用法介绍,不涉及源码解析(源码都很简单,相信你一定一看就懂) SPI 有什

  • springboot 整合 dubbo 的实现组聚合详情

    目录 消费者 自定义聚合策略 如何自定义 dubbo 聚合策略? 提供者 接口及其实现 表结构及数据 消费者 yml 文件配置: dubbo: application: name: dubbo-gateway registry: address: zookeeper://127.0.0.1:2181 server: true provider: timeout: 3000 protocol: name: dubbo port: 20881 controller 类: @RestControlle

  • Dubbo3的Spring适配原理与初始化流程源码解析

    目录 引言 Spring Context Initialization FactoryBean BeanDefinition 初始化bean 解决依赖 解决属性 Dubbo Spring的一些问题及解决办法 Dubbo spring 2.7 初始化过程 Dubbo spring 3的初始化过程 属性占位符解决失败 ReferenceBean被过早初始化问题 Reference注解可能出现@Autowire注入失败的问题 引言 Dubbo 国内影响力最大的开源框架之一,非常适合构建大规模微服务集群

  • Spring Transaction事务实现流程源码解析

    目录 一.基于xml形式开启Transaction 1. 创建数据库user 2. 创建一个maven 项目 3. 通过xml形式配置事务 1) 创建Spring命名空间 2) 开启事务配置 3) 创建UserService类 4. 测试事务 1) 抛出RuntimeException 2) 注释掉RuntimeException 二.事务开启入口TxNamespaceHandler AnnotationDrivenBeanDefinitionParser 三.AOP驱动事务 Transacti

  • Kubernetes kubectl中Pod创建流程源码解析

    目录 确立目标 先写一个Pod的Yaml 部署Pod 查询Pod kubectl create 的调用逻辑 Main Match Command Create RunCreate Summary 确立目标 从创建pod的全流程入手,了解各组件的工作内容,组件主要包括以下 kubectl kube-apiserver kube-scheduler kube-controller kubelet 理解各个组件之间的相互协作,目前是kubectl 先写一个Pod的Yaml apiVersion: v1

  • SpringMVC请求流程源码解析

    目录 一.SpringMVC使用 1.工程创建 2.工程配置 3.启动工程 二.SpringMVC启动过程 1.父容器启动过程 2.子容器启动过程(SpringMvc容器) 3.九大组件的初始化 1.处理器映射器的初始化 2.处理器适配器的初始化 4.拦截器的初始化 三.SpringMVC请求过程 1.请求流程图 2.业务描述 一.SpringMVC使用 1.工程创建 创建maven工程. 添加java.resources目录. 引入Spring-webmvc 依赖. <dependency>

  • SpringBoot应用启动流程源码解析

    前言 Springboot应用在启动的时候分为两步:首先生成 SpringApplication 对象 ,运行 SpringApplication 的 run 方法,下面一一看一下每一步具体都干了什么 public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);

  • 浅析Spring Security登录验证流程源码

    一.登录认证基于过滤器链 Spring Security的登录验证流程核心就是过滤器链.当一个请求到达时按照过滤器链的顺序依次进行处理,通过所有过滤器链的验证,就可以访问API接口了. SpringSecurity提供了多种登录认证的方式,由多种Filter过滤器来实现,比如: BasicAuthenticationFilter实现的是HttpBasic模式的登录认证 UsernamePasswordAuthenticationFilter实现用户名密码的登录认证 RememberMeAuthe

  • Spring Security 实现用户名密码登录流程源码详解

    目录 引言 探究 登录流程 校验 用户信息保存 引言 你在服务端的安全管理使用了 Spring Security,用户登录成功之后,Spring Security 帮你把用户信息保存在 Session 里,但是具体保存在哪里,要是不深究你可能就不知道, 这带来了一个问题,如果用户在前端操作修改了当前用户信息,在不重新登录的情况下,如何获取到最新的用户信息? 探究 无处不在的 Authentication 玩过 Spring Security 的小伙伴都知道,在 Spring Security 中

  • SpringSecurity 默认表单登录页展示流程源码

    SpringSecurity 默认表单登录页展示流程源码 本篇主要讲解 SpringSecurity提供的默认表单登录页 它是如何展示的的流程, 涉及 1.FilterSecurityInterceptor, 2.ExceptionTranslationFilc,xmccmc,ter , 3.DefaultLoginPageGeneratingFilter 过滤器, 并且简单介绍了 AccessDecisionManager 投票机制  1.准备工作(体验SpringSecurity默认表单认证

  • RocketMQ之NameServer架构设计及启动关闭流程源码分析

    目录 NameServer 1.架构设计 2.核心类与配置 NamesrvController NamesrvConfig NettyServerConfig RouteInfoManager 3.启动与关闭流程 3.1.步骤一 3.2.步骤二 3.3.步骤三 NameServer 1.架构设计 消息中间件的设计思路一般都是基于主题订阅与发布的机制,RocketMQ也不例外.RocketMQ中,消息生产者(Producer)发送某主题的消息到消息服务器,消息服务器对消息进行持久化存储,而消息消费

  • 详解Android布局加载流程源码

    一.首先看布局层次 看这么几张图 我们会发现DecorView里面包裹的内容可能会随着不同的情况而变化,但是在Decor之前的层次关系都是固定的.即Activity包裹PhoneWindow,PhoneWindow包裹DecorView.接下来我们首先看一下三者分别是如何创建的. 二.Activity是如何创建的 首先看到入口类ActivityThread的performLaunchActivity方法: private Activity performLaunchActivity(Activi

随机推荐