Dubbo是国内最具影响力的开源框架之一。非常适合构建大规模的微服务集群,提供开发框架、高性能通信、丰富的服务治理能力。同时,Dubbo无缝支持Spring和SpringBoot模式的开发。本文帮助您了解Dubbo如何与Spring集成。非常适合首先关心原理的开发者。有兴趣的朋友可以直接访问官网体验Spring+Dubbo开发微服务或者搜索并关注官方微信公众号:ApacheDubboSpring上下文初始化首先我们来看一下Spring上下文初始化的主要流程,如图下图:相关代码:org.springframework.context.support.AbstractApplicationContext#refresh()简单描述一下每一步的内容:创建BeanFactory:读取并加载XML/注解定义的BeanDefinition。prepareBeanFactory:注册各种内置的后处理器和预先加载的环境变量。invokeBeanFactoryPostProcessors:加载BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor。注意这里的加载顺序比较复杂,涉及到多次加载,具体请查看代码。常用于加载较早初始化的组件,如PropertyPlaceholderConfigurer和PropertySourcesPlaceholderConfigurer。还有一个比较重要的ConfigurationClassPostProcessor,实现了BeanDefinitionRegistryPostProcessor接口,用于加载@Configuration类,解析@Bean定义,注册BeanDefinition。此阶段通常用于注册自定义BeanDefinition。registerBeanPostProcessors:加载并注册各种BeanPostProcessors,常用于修改或包装(代理)bean实例,如Seata的GlobalTransactionScanner。registerListeners:加载并注册ApplicationListener,处理earlyApplicationEventsfinishBeanFactoryInitialization:注册EmbeddedValueResolver,冻结配置preInstantiateSingletons:遍历并加载单例bean,即加载普通bean,包括@Controller、@Service、DAO等。FactoryBeanSpring容器支持两种bean:普通bean和工厂bean(FactoryBean)。我们经常写的@Controller/@Service是一个普通的bean,由Spring直接初始化,而FactoryBean是先由Spring创建一个FactoryBean实例,然后再创建最终的bean实例。[SpringBeanFactory]??--create--->[XxxFactoryBeaninstance]--create-->[FinalBeanInstance]FactoryBean接口如下:publicinterfaceFactoryBean{/***返回一个实例(可能共享或独立于此工厂管理的对象。*/TgetObject()抛出异常;/***返回此FactoryBean创建的对象类型,如果事先不知道,则返回null。*这允许在不实例化对象的情况下检查特定类型的bean,例如在自动装配时。*/ClassgetObjectType();}BeanDefinitionSpringbean分为注册和创建实例两个阶段。将SpringXML/annotation解析出来的bean信息放入BeanDefinition中,然后注册到BeanFactory中,再根据BeanDefinition初始化bean实例。不管是普通bean还是工厂bean,都是先注册bean定义,然后按照依赖的顺序进行初始化。两个BeanDefinition的区别在于:一般bean的BeanDefinition的beanClassName是最终bean的类。工厂bean的BeanDefinition的beanClassName就是工厂bean的类。注册bean有几种方式:在SpringXML中定义,由Spring解析在Springjavaconfig中生成并声明BeanDefinition@Bean方法,由Spring解析生成BeanDefinition调用BeanDefinitionRegistry.registerBeanDefinition()方法手动注册BeanDefinition并通过SingletonBeanRegistry.registerSingleton()方法注册bean实例。注意:注册bean实例和前面三种注册BeanDefinitions有本质区别。比如注册BeanDefinition是newson,Spring会管理bean初始化和依赖注入,解析属性占位符,调用BeanPostProcessor进行处理。注册的bean实例是别人的儿子,Spring将其视为已经初始化的bean,不会解析它的依赖和属性占位符。Dubbo2.7/3Reference注解注册bean两个版本的区别,后面会讲到。初始化bean和创建bean有以下步骤:创建实例createBeanInstance解决依赖resolveDependency解决propertyplaceholderapplyPropertyValues多次调用BeanPostProcessor进行处理,如果此时有些BeanPostProcessor还没有注册,可能会导致遗漏处理当前bean。dubbo2.7中提前加载configbean带来的一系列问题,后面会讲到。关键逻辑参考代码:AbstractAutowireCapableBeanFactory#doCreateBean()解决依赖。Spring注解流行之后,通常使用@Autowire注解来注入依赖bean。这种注入方式的大致流程如下:找到匹配的属性类型的beanName列表,根据@Qualifier/@Primary/propertyName选择合适的bean。关键逻辑可以参考代码:DefaultListableBeanFactory#doResolveDependency()。第一步是在查找匹配类型的beanName列表时调用ListableBeanFactory#getBeanNamesForType()来枚举和检查所有beanDefinition。有关检查bean类型的逻辑,请参阅AbstractBeanFactory#isTypeMatch()。涉及的逻辑比较复杂。这里简单说下重要的分支:如果是普通bean,检查BeanDefinition的beanClass是否匹配。如果是FactoryBean,则通过各种方式预测bean类型。FactoryBean的类型预测主要包括以下内容:如果有DecoratedDefinition,则重写BeanDefinition,检查合并后的beanClass是否匹配。通过FactoryBean.OBJECT_TYPE_ATTRIBUTE属性获取beanType(5.2起)实例化FactoryBean,调用getObjectType()方法获取beanType。上面提到的第三种情况,可能会出现实例化失败导致多次被创建的问题(比如propertyplaceholder解析失败),即每次预测到bean类型都会尝试实例化,并且它每次都会失败,直到它依赖的所有组件都准备就绪。DubboReferenceBean本身也是一个FactoryBean。在2.7中,由于bean类型的预测,经常会自动初始化。这个问题将在后面详细讨论。Spring中解析属性一般是通过PropertyPlaceholderConfigurer/PropertySourcesPlaceholderConfigurer来解析XML/@Value中的属性占位符${...}。两者都实现了BeanFactoryPostProcessor接口,会在invokeBeanFactoryPostProcessors阶段加载,然后遍历处理BeanDefinition中的所有属性占位符。[ResolveregisteredBeanDefinition]=>[PropertyResourceConfigurerresolvepropertyplaceholder]=>[LoadBeanPostProcessor]=>[Initializesingletonbean]可以看出,如果一个bean在PropertyPlaceholderConfigurer/PropertySourcesPlaceholderConfigurer加载之前初始化,则bean的Property占位符没有解析.这是无法解析占位符的根本原因,因为Dubboconfigbean被过早加载。DubboSpring的一些问题及解决方案Dubbospring2.7初始化过程初始化入口为ReferenceBean#prepareDubboConfigBeans(),即在初始化第一个ReferenceBean时,尝试加载其他dubboconfigbeans。@OverridepublicvoidafterPropertiesSet()throwsException{//在@ReferencebeanautowiringprepareDubboConfigBeans()之前初始化Dubbo的ConfigBeans;//默认情况下是惰性初始化。如果(init==null){init=false;}//如有必要,急于初始化。如果(shouldInit()){getObject();}}privatevoidprepareDubboConfigBeans(){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,这样会导致通过BeanPostProcessor机制拦截Seata等组件失败。在Dubbospring3的初始化过程中,Dubbo3做了很多重构,解决了以上痛点。初始化的主要流程如下:【Spring解析XML/@Configuration类并注册BeanDefinition】=>【加载BeanFactoryPostProcessor(包括PropertyResourceConfigurer)】=>【1.解析@DubboReference/@DubboService注解并注册BeanDefinition]=>[加载并注册BeanPostProcessor]=>[加载ApplicationListener]=>[2.加载DubboConfigBeanInitializer初始化configbean]=>[初始化单例bean]=>[依赖注入ReferenceBean]=>[3.监听ContextRefreshedEvent事件,启动dubbo框架】主要包括3个阶段:解析@DubboReference/@DubboService注解,在BeanFactoryPostProcessor阶段注册BeanDefinition。因为此时还在BeanDefinition处理阶段,注册的ReferenceBean可以被@Autowire依赖注入用于后续加载的业务bean。同时也支持在@Configurationbean方法中使用@DubboReference/@DubboService注解。在所有的PropertyResourceConfigurer和BeanPostProcessor加载完成后执行DubboConfigBeanInitializer初始化configbean,解决了属性占位符未解析和BeanPostProcessor拦截失败的问题。监听Spring上下文事件,加载完成后启动dubbo框架。支持在@Configurationbean方法中使用@DubboReference/@DubboService注解来引用Dubbospring3初始化过程的第一阶段。属性占位符解决失败是指Dubbospring3初始化过程的第二阶段。ReferenceBean过早初始化。预测的ReferenceBean的beanType导致DubboReferenceBean本身是一个FactoryBean。在2.7中,经常会因为预测的bean类型而自动初始化。比如用户自定义的BeanFactoryPostProcessorbean使用@Autowire注解向某个业务bean注入依赖,而这个自定义BeanFactoryPostProcessorbean的优先级高于解析属性占位符的PropertyResourceConfigurer,然后解析属性占位符失败。Dubbo3中的ReferenceBean通过以下两种方式解决类型预测问题:FactoryBean类型预测主要包括:如果有DecoratedDefinition,覆盖BeanDefinition,检查合并后的beanClass是否匹配通过FactoryBean.OBJECT_TYPE_ATTRIBUTE属性获取的beanType(自5.2起)ReferenceBean直接依赖,导致过早初始化。如果在Dubboconfigbean初始化之前自动创建了ReferenceBean实例,则会创建一个Lazy代理类并注入到依赖类中。不需要解析属性占位符,Dubbo框架不会被拉起。.其他配置bean固定在PropertyResourceConfigurer和BeanPostProcessor加载后初始化,避免了上述问题。引用注解可能存在@Autowire注入失败的问题。在Dubbo2.7中,解析BeanPostProcessor中的@DubboReference/@Reference注解,创建并注入ReferenceBean实例到Spring容器中。这种方式存在几个问题:@DubboReference/@Reference注解与XML定义的初始化方式不一致。前者由dubbo初始化,后者由Spring容器初始化。执行时机导致的依赖注入失败。通常情况下,所有的BeanDefinitions都是在invokeBeanFactoryPostProcessors阶段注册的,dubbo2.7的ReferenceAnnotationBeanPostProcessor只有在BeanPostProcessor执行时才会创建ReferenceBean。一些早于它初始化的bean可能无法使用@Autowire注入。在Dubbo3中,改为解析BeanFactoryPostProcessor中的@DubboReference/@Reference注解,注册ReferenceBean的BeanDefinition,并在字段中记录要注入的referenceBeanName。BeanPostProcessor执行时通过BeanFactory().getBean(referenceBeanName)获取ReferenceBean实例。搜索并关注官方微信公众号:ApacheDubbo,了解更多行业最新动态,掌握各大厂面试必备的Dubbo技能