Spring與Dubbo整合原理
應用啓動類與配置
public class Application {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();
}
@Configuration
@EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.provider")
@PropertySource("classpath:/spring/dubbo-provider.properties")
static class ProviderConfiguration {
}
}
應用配置類爲ProviderConfiguration, 在配置上有兩個比較重要的註解
- @PropertySource表示將dubbo-provider.properties中的配置項添加到Spring容器中,可以通過@Value的方式獲取到配置項中的值
- @EnableDubbo(scanBasePackages = “org.apache.dubbo.demo.provider”)表示對指定包下的類進行掃描,掃描@Service與@Reference註解,並且進行處理
@EnableDubbo
在EnableDubbo註解上,有另外兩個註解,也是研究Dubbo最重要的兩個註解
- @EnableDubboConfig
- @DubboComponentScan
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {
boolean multiple() default true;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
注意兩個註解中對應的@Import註解所導入的類:
- DubboConfigConfigurationRegistrar
- DubboComponentScanRegistrar
Spring在啓動時會解析這兩個註解,並且執行對應的Registrar類中的registerBeanDefinitions方法(這是Spring中提供的擴展功能。)
DubboConfigConfigurationRegistrar
Spring啓動時,會調用DubboConfigConfigurationRegistrar的registerBeanDefinitions方法,會對Properties文件進行解析,主要完成的事情是根
據Properties文件的每個配置項的前綴、參數名、參數值生成對應的Bean。
比如前綴爲"dubbo.application"的配置項,會生成一個ApplicationConfig類型的BeanDefinition。
比如前綴爲"dubbo.protocol"的配置項,會生成一個ProtocolConfig類型的BeanDefinition。
其他前綴對應關係如下:
@EnableDubboConfigBindings({
@EnableDubboConfigBinding(prefix = "dubbo.application", type = ApplicationConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.module", type = ModuleConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.registry", type = RegistryConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.protocol", type = ProtocolConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.monitor", type = MonitorConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.provider", type = ProviderConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.consumer", type = ConsumerConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.config-center", type = ConfigCenterBean.class),
@EnableDubboConfigBinding(prefix = "dubbo.metadata-report", type = MetadataReportConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.metrics", type = MetricsConfig.class)
})
public static class Single {
}
默認情況下開啓了multiple模式,multiple模式表示開啓多配置模式,意思是這樣的:
如果沒有開啓multiple模式,那麼只支持配置一個dubbo.protocol,比如:
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.protocol.host=0.0.0.0
如果開啓了multiple模式,那麼可以支持多個dubbo.protocol,比如:
dubbo.protocols.p1.name=dubbo
dubbo.protocols.p1.port=20880
dubbo.protocols.p1.host=0.0.0.0
dubbo.protocols.p2.name=http
dubbo.protocols.p2.port=8082
dubbo.protocols.p2.host=0.0.0.0
DubboConfigConfigurationRegistrar的registerBeanDefinitions方法源碼流程:
- 根據DubboConfigConfiguration.Single.class的定義來註冊BeanDefinition,如果開啓了multiple模式,則根據DubboConfigConfiguration.Multiple.class的定義來註冊BeanDefinition
- 兩者都是調用的registerBeans(BeanDefinitionRegistry registry, Class<?>… annotatedClasses)方法
- 在registerBeans方法內,會利用Spring中的AnnotatedBeanDefinitionReader類來加載annotatedClasses參數所指定的類(上面所Single或Multiple類),Spring的AnnotatedBeanDefinitionReader
類會識別annotatedClasses上的註解,然後開啓解析annotatedClasses類上的註解 - 可以發現,不管是Single類,還是Multiple類,類上面定義的註解都是@EnableDubboConfigBindings,所以Spring會解析這個註解,在這個註解的定義上Import了一個DubboConfigBindingsRegistra
r類,所以這是Spring會去調用DubboConfigBindingsRegistrar類的registerBeanDefinitions方法 - 在DubboConfigBindingsRegistrar類的registerBeanDefinitions方法中,會去取EnableDubboConfigBindings註解的value屬性的值,該值是一個數組,數組中存的內容爲@EnableDubboConfigBinding注
解。此時DubboConfigBindingsRegistrar會去處理各個@EnableDubboConfigBinding註解,使用DubboConfigBindingRegistrar類的registerBeanDefinitions(AnnotationAttributes attributes, Bea
nDefinitionRegistry registry)去處理各個@EnableDubboConfigBinding註解 - attributes表示@EnableDubboConfigBinding註解中的參數對,比如prefix = “dubbo.application”, type = ApplicationConfig.class。
- 獲取出對應當前@EnableDubboConfigBinding註解的prefix和AbstractConfig類,ApplicationConfig、RegistryConfig、ProtocolConfig等等都是AbstractConfig類的子類
- 從environment.getPropertySources()中獲取對應的prefix的properties,相當於從Properties文件中獲取對應prefix的屬性,後面在生成了AplicationConfig類型的BeanDefinition之後,會把這些屬
性值賦值給對應的BeanDefinition,但是這裏獲取屬性只是爲了獲取beanName - 在生成Bean之前,需要確定Bean的名字,可以通過在Properties文件中配置相關的id屬性,那麼則對應的值就爲beanName,否則自動生成一個beanName
- 對於Multiple模式的配置,會存在多個bean以及多個beanName
- 得到beanName之後,向Spring中註冊一個空的BeanDefinition對象,並且向Spring中添加一個DubboConfigBindingBeanPostProcessor(Bean後置處理器),在DubboConfigBindingBeanPostProcessor中
有一個構造方法,需要傳入prefix和beanName。 - 總結一下:一個AbstractConfig的子類會對應一個bean(Multiple模式下會有多個),每個bean對應一個DubboConfigBindingBeanPostProcessor後置處理器。
- 至此,Spring掃描邏輯走完了。
- 接下來,Spring會根據生成的BeanDefinition生成一個對象,然後會經過DubboConfigBindingBeanPostProcessor後置處理器的處理。
- DubboConfigBindingBeanPostProcessor主要用來對其對應的bean進行屬性賦值
- 首先通過DubboConfigBinder的默認實現類DefaultDubboConfigBinder,來從Properties文件中獲取prefix對應的屬性值,然後把這些屬性值賦值給AbstractConfig對象中的屬性
- 然後看AbstractConfig類中是否存在setName()方法,如果存在則把beanName設置進去
- 這樣一個AbstractConfig類的bean就生成好了
- 總結一下:Spring在啓動時,會去生成ApplicationConfig、RegistryConfig、ProtocolConfig等等AbstractConfig子類的bean對象,然後從Properties文件中獲取屬性值並賦值到bean對象中去。
DubboConfigConfigurationRegistrar的邏輯整理完後,就開始整理DubboComponentScanRegistrar的邏輯。
DubboComponentScanRegistrar
DubboConfigConfigurationRegistrar的registerBeanDefinitions方法中:
- 首先獲得用戶指定的掃描包路徑
- 然後分別生成ServiceAnnotationBeanPostProcessor和ReferenceAnnotationBeanPostProcessor類的BeanDefinition,並註冊到Spring中,注意這兩個類看上去像,但完全不是一個層面的東西。
- ServiceAnnotationBeanPostProcessor是一個BeanDefinitionRegistryPostProcessor,是在Spring掃描過程中執行的。
- ReferenceAnnotationBeanPostProcessor的父類是AnnotationInjectedBeanPostProcessor,是一個InstantiationAwareBeanPostProcessorAdapter,是在Spring對容器中的bean進行依賴注入時使用的。
ServiceAnnotationBeanPostProcessor
在執行postProcessBeanDefinitionRegistry()方法時,會先生成一個DubboClassPathBeanDefinitionScanner,它負責掃描。接下來的流程:
- 從指定的包路徑下掃描@Service註解,掃描得到BeanDefinition
- 遍歷每個BeanDefinition
- 得到服務實現類、@Service註解信息、服務實現類實現的接口、服務實現類的beanName
- 生成一個ServiceBean對應的BeanDefinition,下面稱爲serviceBeanBeanDefinition
- 根據@Service註解上的信息對serviceBeanBeanDefinition中的屬性進行賦值
- 對ref屬性進行賦值(PropertyReference),賦值爲服務實現類的beanName
- 對interface屬性進行賦值(PropertyValue),賦值爲接口名
- 對parameters屬性進行賦值,把註解中的parameters屬性值轉化爲map進行賦值
- 對methods屬性進行賦值,把註解中的methods屬性值轉化爲List進行賦值
- 如果@Service註解中配置了provider屬性,則對provider屬性進行賦值(PropertyReference,表示一個beanName)
- monitor、application、module和provider類似
- 如果@Service註解中配置了registry屬性,會對registries屬性進行賦值(RuntimeBeanReference)
- 如果@Service註解中配置了protocol屬性,會對protocols屬性進行賦值(RuntimeBeanReference)
- 到此生成了一個ServiceBean的BeanDefinition
- 然後生成一個ServiceBeanName,並把對應的BeanDefinition註冊到Spring中去
- 總結一下:ServiceAnnotationBeanPostProcessor主要用來掃描指定包下面的@Service註解,把掃描到的@Service註解所標註的類都生成一個對應的BeanDefinition(會隨着Spring的生命週期生成一
個對應的Bean),然後遍歷掃描出來的BeanDefinition,根據@Service註解中的參數配置,會生成一個ServiceBean類型的BeanDefinition,並添加到Spring容器中去,所以相當於一個@Service註解會生成
兩個Bean,一個當前類的Bean,一個ServiceBean類型的Bean。需要注意的是ServiceBean實現了ApplicationListener接口,當Spring啓動完後,會發布ContextRefreshedEvent事件,ServiceBean會處
理該事件,調用ServiceBean中的export(),該方法就是服務導出的入口。
注意:關於RuntimeBeanReference參考https://www.yuque.com/renyong-jmovm/ufz328/gbwvk7。
ReferenceAnnotationBeanPostProcessor
ReferenceAnnotationBeanPostProcessor的父類是AnnotationInjectedBeanPostProcessor,是一個InstantiationAwareBeanPostProcessorAdapter,是在Spring對容器中的bean進行依賴注入時使用的。
當Spring根據BeanDefinition生成了實例對象後,就需要對對象中的屬性進行賦值,此時會:
- 調用AnnotationInjectedBeanPostProcessor類中的postProcessPropertyValues方法,查找注入點,查找到注入點後就會進行屬性注入,注入點分爲被@Reference註解了的屬性字段,被@Reference註解
了的方法 - 調用AnnotationInjectedBeanPostProcessor類中的findFieldAnnotationMetadata方法查找屬性注入點,返回類型爲List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement>
- 調用AnnotationInjectedBeanPostProcessor類中的findAnnotatedMethodMetadata方法查找方法注入點,返回類型爲List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement>
- 註解掉找到後,就調用InjectionMetadata的inject方法進行注入。
- 針對AnnotatedFieldElement,調用getInjectedObject方法得到注入對象injectedObject,然後通過反射field.set(bean, injectedObject);
- 針對AnnotatedMethodElement,調用getInjectedObject方法得到注入對象injectedObject,然後通過反射method.invoke(bean, injectedObject);
- 在getInjectedObject方法中,調用doGetInjectedBean方法得到注入對象,doGetInjectedBean方法在ReferenceAnnotationBeanPostProcessor類中提供了實現
- 根據@Reference註解中的參數信息與待注入的屬性類型,生成一個serviceBeanName,查看在本應用的Spring容器中是否存在這個名字的bean,如果存在,則表示現在引入的服務就在本地Spring容器中
- 根據@Reference註解中的參數信息與待注入的屬性類型,生成一個referenceBeanName
- 根據referenceBeanName、@Reference註解中的參數信息與待注入的屬性類型生成一個ReferenceBean對象(注意,這裏直接就是對象了,最終返回的就是這個對象的get方法的返回值)
- 把ReferenceBean對象通過beanFactory註冊到Spring中
- 那麼ReferenceBean對象是怎麼產生的呢?
- AnnotatedInterfaceConfigBeanBuilder類的build方法來生成這個ReferenceBean對象
- 先new ReferenceBean();得到一個ReferenceBean實例
- 然後調用configureBean(ReferenceBean實例);給ReferenceBean實例的屬性進行賦值
- 調用preConfigureBean(attributes, ReferenceBean實例);,把Reference註解的參數值賦值給ReferenceBean實例,除開"application", “module”, “consumer”, “monitor”, "registry"這幾個參數
- 調用configureRegistryConfigs對ReferenceBean實例的registries屬性進行賦值,通過@Reference註解中所配置的registry屬性獲得到值,然後根據該值從Spring容器中獲得到bean
- 同樣的,通過調用configureMonitorConfig、configureApplicationConfig、configureModuleConfig方法分別進行賦值
- 調用postConfigureBean方法對applicationContext、interfaceName、consumer、methods屬性進行賦值
- 最後調用ReferenceBean實例的afterPropertiesSet方法
- ReferenceBean生成後了之後,就會調用ReferenceBean的get方法得到一個接口的代理對象,最終會把這個代理對象注入到屬性中去
- 總結一下:ReferenceAnnotationBeanPostProcessor主要在Spring對容器中的Bean進行屬性注入時進行操作,當Spring對一個Bean進行屬性注入時,先查找@Reference的注入點,然後對注入點進行調
用,在調用過程中,會根據屬性類型,@Reference註解信息生成一個ReferenceBean,然後對ReferenceBean對象的屬性進行賦值,最後調用ReferenceBean的get方法得到一個代理對象,最終把這個代理
對象注入給屬性。需要注意的是ReferenceBean的get()方法就是服務引入流程的入口。