Dubbo3和Spring?Boot整合過程源碼解析
前言
Dubbo3 已經(jīng)從一開始的 RPC 框架改頭換面,現(xiàn)在的定位是微服務框架,除了提供基本的 RPC 功能外,它還提供了一整套的服務治理方案。Dubbo 有自身的一套設計體系,不過通常很少單獨使用,更多的是和 Spring 整合在一起,本文分析下 Dubbo3 整合 Spring Boot 的源碼,版本是 3.1.10,Github 地址:https://github.com/apache/dubbo/tree/dubbo-3.1.10。
Dubbo3 專門提供了一個模塊來和 Spring Boot 做整合,模塊名是 dubbo-spring-boot
,下面有4個子模塊:
dubbo-spring-boot -> dubbo-spring-boot-actuator -> dubbo-spring-boot-autoconfigure -> dubbo-spring-boot-compatible -> dubbo-spring-boot-starter
一般我們直接引入 dubbo-spring-boot-starter
即可開啟 Dubbo 的自動裝配,在 Spring Boot 項目里使用 Dubbo。
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>3.1.10</version> </dependency>
所以我們以這個模塊為切入點,看看 Dubbo 做了什么。
入口模塊
dubbo-spring-boot-starter
模塊沒有代碼,只有一個 pom.xml
引入了幾個必要的依賴:
<dependencies> <!-- Spring Boot 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <optional>true</optional> </dependency> <!-- 日志 依賴 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>${log4j2_version}</version> <optional>true</optional> </dependency> <!-- Dubbo 自動裝配 --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-autoconfigure</artifactId> <version>${project.version}</version> </dependency> </dependencies>
我們直接看 dubbo-spring-boot-autoconfigure
模塊:
src/main -> java/org/apache/dubbo/spring/boot/autoconfigure ---> BinderDubboConfigBinder.java ---> DubboRelaxedBinding2AutoConfiguration.java -> resources/META-INF ---> spring.factories
這里用到了 Spring Boot 的自動裝配功能,顯然就兩個類是無法實現(xiàn)如此復雜的整合的,再看 pom.xml
發(fā)現(xiàn)該模塊又依賴了 dubbo-spring-boot-autoconfigure-compatible
模塊:
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-autoconfigure-compatible</artifactId> <version>${project.version}</version> </dependency>
該模塊的 spring.factories
配置了一堆組件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration,\ org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBindingAutoConfiguration,\ org.apache.dubbo.spring.boot.autoconfigure.DubboListenerAutoConfiguration org.springframework.context.ApplicationListener=\ org.apache.dubbo.spring.boot.context.event.WelcomeLogoApplicationListener org.springframework.boot.env.EnvironmentPostProcessor=\ org.apache.dubbo.spring.boot.env.DubboDefaultPropertiesEnvironmentPostProcessor org.springframework.context.ApplicationContextInitializer=\ org.apache.dubbo.spring.boot.context.DubboApplicationContextInitializer
OK,到這里看源碼的思路基本就有了,Dubbo 利用 Spring Boot 自動裝配的功能,提供了兩個自動裝配的模塊,配置了一堆自動裝配的組件,通過這些組件來和 Spring Boot 做整合。
服務發(fā)布
Dubbo3 整合 Spring 后,如何將加了 @DubboService
的服務發(fā)布出去?
服務發(fā)布的關鍵節(jié)點:
DubboAutoConfiguration#serviceAnnotationBeanProcessor ServiceBean后置處理器 ServiceAnnotationPostProcessor#postProcessBeanDefinitionRegistry ServiceAnnotationPostProcessor#scanServiceBeans 掃描ServiceBean DubboClassPathBeanDefinitionScanner#scan ServiceAnnotationPostProcessor#processScannedBeanDefinition 處理BeanDefinition ServiceAnnotationPostProcessor#buildServiceBeanDefinition 構(gòu)建ServiceBeanDefinition BeanDefinitionRegistry#registerBeanDefinition 注冊BeanDefinition ServiceBean#afterPropertiesSet ModuleConfigManager#addService 交給ModuleConfigManager管理 此時還沒啟動服務 DubboDeployApplicationListener#onApplicationEvent Spring事件監(jiān)聽 DubboDeployApplicationListener#onContextRefreshedEvent ContextRefreshed事件 DefaultModuleDeployer#start 模塊部署啟動 DefaultModuleDeployer#exportServices 啟動服務 暴露服務 DefaultModuleDeployer#referServices 引用服務
AutoConfiguration
先看 dubbo-spring-boot-autoconfigure
模塊自動裝配的配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBinding2AutoConfiguration
DubboRelaxedBinding2AutoConfiguration 是 Dubbo 提供的針對 Spring Boot 2.0 的自動裝配類,它先于 DubboRelaxedBindingAutoConfiguration 執(zhí)行,如果你用的是 Spring Boot 1.x 版本,則會觸發(fā)后者。核心:讀取 dubbo.scan.basePackages
配置,獲取要掃描的包路徑。
@ConditionalOnMissingBean(name = BASE_PACKAGES_BEAN_NAME) @Bean(name = BASE_PACKAGES_BEAN_NAME) public Set<String> dubboBasePackages(ConfigurableEnvironment environment) { // 讀取 dubbo.scan.basePackages 掃描包路徑 注冊Set到Spring容器 PropertyResolver propertyResolver = dubboScanBasePackagesPropertyResolver(environment); return propertyResolver.getProperty(BASE_PACKAGES_PROPERTY_NAME, Set.class, emptySet()); } @ConditionalOnMissingBean(name = RELAXED_DUBBO_CONFIG_BINDER_BEAN_NAME, value = ConfigurationBeanBinder.class) @Bean(RELAXED_DUBBO_CONFIG_BINDER_BEAN_NAME) @Scope(scopeName = SCOPE_PROTOTYPE) public ConfigurationBeanBinder relaxedDubboConfigBinder() { return new BinderDubboConfigBinder(); }
再看 DubboAutoConfiguration,它主要做的事:
- 注入 ServiceAnnotationPostProcessor,處理 ServiceBean
- DubboSpringInitializer 的初始化,注冊一些核心bean
@ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true) @Configuration @AutoConfigureAfter(DubboRelaxedBindingAutoConfiguration.class) @EnableConfigurationProperties(DubboConfigurationProperties.class) @EnableDubboConfig// 引入 DubboConfigConfigurationRegistrar public class DubboAutoConfiguration { @ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME) @ConditionalOnBean(name = BASE_PACKAGES_BEAN_NAME) @Bean public ServiceAnnotationPostProcessor serviceAnnotationBeanProcessor(@Qualifier(BASE_PACKAGES_BEAN_NAME) Set<String> packagesToScan) { // 獲取掃描的包路徑 // 注入 ServiceBean 后置處理器 return new ServiceAnnotationPostProcessor(packagesToScan); } }
ServiceAnnotationPostProcessor
Dubbo 對外提供的服務,在 Spring 里面被封裝為 org.apache.dubbo.config.spring.ServiceBean
。
它繼承自 ServiceConfig,所以它是一個標準的 Dubbo Service,在整合 Spring 方面做了擴展。ServiceAnnotationPostProcessor 是 Spring BeanFactory 的后置處理器,它的職責是:掃描 DubboService Bean,注冊到 Spring 容器。拿到需要掃描的包路徑,然后掃描 DubboService Bean:
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { this.registry = registry; // 掃描 DubboService Bean scanServiceBeans(resolvedPackagesToScan, registry); }
Dubbo 會在類路徑下掃描,所以會 new 一個 DubboClassPathBeanDefinitionScanner 掃描器,它依賴于 Spring 的 ClassPathBeanDefinitionScanner,將類路徑下加了 @DubboService
、 @Service
的類封裝成 BeanDefinition,然后注冊到容器:
private void scanServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) { // 實例化一個ClassPath掃描器 DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader); // 掃描bean的包含規(guī)則 有@DubboService @Service注解 for (Class<? extends Annotation> annotationType : serviceAnnotationTypes) { scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType)); } // 排除規(guī)則 ScanExcludeFilter scanExcludeFilter = new ScanExcludeFilter(); scanner.addExcludeFilter(scanExcludeFilter); for (String packageToScan : packagesToScan) { // 掃描包路徑 scanner.scan(packageToScan); Set<BeanDefinitionHolder> beanDefinitionHolders = findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator); for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) { // 處理掃描到的BeanDefinition -> 注冊 processScannedBeanDefinition(beanDefinitionHolder); servicePackagesHolder.addScannedClass(beanDefinitionHolder.getBeanDefinition().getBeanClassName()); } servicePackagesHolder.addScannedPackage(packageToScan); } }
ServiceBean 注冊到容器了,Spring 會正常實例化 bean。又因為 ServiceBean 實現(xiàn)了 InitializingBean 接口,所以會觸發(fā)它的 afterPropertiesSet()
,ServiceBean 會把自己交給 ModuleConfigManager 管理。
Tips:服務現(xiàn)在還沒啟動,目前只是先收集 ServiceBean。
public void afterPropertiesSet() throws Exception { if (StringUtils.isEmpty(getPath())) { if (StringUtils.isNotEmpty(getInterface())) { setPath(getInterface()); } } //register service bean ModuleModel moduleModel = DubboBeanUtils.getModuleModel(applicationContext); // 交給 ModuleConfigManager 管理 moduleModel.getConfigManager().addService(this); moduleModel.getDeployer().setPending(); }
DubboDeployApplicationListener
DubboConfigConfigurationRegistrar 會初始化 DubboSpringInitializer,初始化的時候會向 Spring 容器注冊一堆 bean,其中就包含 DubboDeployApplicationListener。它是 Spring 應用程序的事件監(jiān)聽器,它監(jiān)聽到 ContextRefreshedEvent 事件會啟動 Dubbo 服務;監(jiān)聽到 ContextClosedEvent 事件關閉 Dubbo 服務。
public void onApplicationEvent(ApplicationContextEvent event) { if (nullSafeEquals(applicationContext, event.getSource())) { if (event instanceof ContextRefreshedEvent) { onContextRefreshedEvent((ContextRefreshedEvent) event); } else if (event instanceof ContextClosedEvent) { onContextClosedEvent((ContextClosedEvent) event); } } }
啟動服務其實就是觸發(fā) ModuleDeployer#start()
,Dubbo 會啟動相關組件,然后暴露服務和應用服務。
private void onContextRefreshedEvent(ContextRefreshedEvent event) { ModuleDeployer deployer = moduleModel.getDeployer(); Assert.notNull(deployer, "Module deployer is null"); // 啟動模塊部署 Future future = deployer.start(); if (!deployer.isBackground()) { try { // 等待啟動完成 future.get(); } catch (InterruptedException e) { logger.warn(CONFIG_FAILED_START_MODEL, "", "", "Interrupted while waiting for dubbo module start: " + e.getMessage()); } catch (Exception e) { logger.warn(CONFIG_FAILED_START_MODEL, "", "", "An error occurred while waiting for dubbo module start: " + e.getMessage(), e); } } }
至此,服務啟動完畢。
服務引用
Dubbo3 整合 Spring 后,如何注入加了 @DubboReference
的屬性/方法,完成服務引用?
服務引用的關鍵節(jié)點:
DubboConfigConfigurationRegistrar#registerBeanDefinitions DubboSpringInitializer#initialize Dubbo引用初始化 DubboBeanUtils#registerCommonBeans 注冊公共bean ReferenceAnnotationBeanPostProcessor#postProcessBeanFactory 遍歷BeanDefinition AbstractAnnotationBeanPostProcessor#findInjectionMetadata 查找注入點 ReferenceAnnotationBeanPostProcessor#prepareInjection 準備注入 ReferenceAnnotationBeanPostProcessor#registerReferenceBean 注冊ReferenceBean ReferenceAnnotationBeanPostProcessor#postProcessPropertyValues bean屬性值后置處理 AnnotatedInjectElement#inject 注入 ReferenceBean#getObject 獲取注入對象 ReferenceBean#createLazyProxy 創(chuàng)建延遲代理 DubboReferenceLazyInitTargetSource#createObject 調(diào)用bean時才創(chuàng)建真正的Proxy ReferenceConfig#get 引用服務 創(chuàng)建Proxy
ReferenceAnnotationBeanPostProcessor
DubboSpringInitializer 初始化的時候會注冊 ReferenceAnnotationBeanPostProcessor 類,它的職責是完成 @DubboReference
依賴的注入。
它是 BeanFactoryPostProcessor 的子類,是 Spring BeanFactory 的后置處理器,Spring 啟動時會觸發(fā) postProcessBeanFactory()
。首先是遍歷容器中所有的 BeanDefinition,解析 BeanClass 上的屬性或方法是否有加相關注解,也就是尋找注入點。
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 遍歷所有已注冊的BeanDefinition > 查找注入點 > 準備注入 String[] beanNames = beanFactory.getBeanDefinitionNames(); for (String beanName : beanNames) { Class<?> beanType; if (beanFactory.isFactoryBean(beanName)) { BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); if (beanType != null) { // 查找注入點 AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null); // 準備注入 prepareInjection(metadata); } } }
AnnotatedInjectionMetadata 元數(shù)據(jù)解析完以后,Spring 會把注入點封裝成 ReferenceBean,同時注冊到 Spring 容器,AnnotatedFieldElement 只是記錄一下引用的 beanName,后續(xù)依賴注入時就可以直接從 Spring 容器獲取對應的 bean 實例了。
protected void prepareInjection(AnnotatedInjectionMetadata metadata) throws BeansException { try { // 遍歷注入點 注冊 ReferenceBean 此時記錄的只是一個referenceBeanName 還沒注入 for (AnnotatedFieldElement fieldElement : metadata.getFieldElements()) { if (fieldElement.injectedObject != null) { continue; } Class<?> injectedType = fieldElement.field.getType(); AnnotationAttributes attributes = fieldElement.attributes; String referenceBeanName = registerReferenceBean(fieldElement.getPropertyName(), injectedType, attributes, fieldElement.field); fieldElement.injectedObject = referenceBeanName; // 緩存起來 injectedFieldReferenceBeanCache.put(fieldElement, referenceBeanName); } } }
postProcessPropertyValues()
才是真正的依賴注入:
public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { try { // 已經(jīng)解析過了,這里直接從緩存獲取 AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs); prepareInjection(metadata); // 依賴注入 metadata.inject(bean, beanName, pvs); } return pvs; }
AnnotatedInjectionMetadata 會把收集到的注入點 挨個進行注入,最終調(diào)用 AnnotatedInjectElement#inject()
。
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { // ReferenceBean#getObject() 拿到一個延遲代理對象 Object injectedObject = getInjectedObject(attributes, bean, beanName, getInjectedType(), this); if (member instanceof Field) {// 屬性注入 Field field = (Field) member; ReflectionUtils.makeAccessible(field); field.set(bean, injectedObject); } else if (member instanceof Method) {// 方法注入 Method method = (Method) member; ReflectionUtils.makeAccessible(method); method.invoke(bean, injectedObject); } }
ReferenceBean
依賴注入的對象被封裝為 ReferenceBean,它是一個 FactoryBean,所以在注入的時候,其實會調(diào)用 ReferenceBean#getObject()
獲取 bean 實例。
public T getObject() { if (lazyProxy == null) { createLazyProxy(); } return (T) lazyProxy; }
Dubbo 這里并沒有直接去引用遠程服務,而是先創(chuàng)建了一個 LazyProxy,Dubbo 希望在真正發(fā)起 RPC 調(diào)用時才去引用服務,為什么這么做呢?LazyProxy 的創(chuàng)建過程,利用 JDK 動態(tài)代理,創(chuàng)建了一個空殼的代理對象:
private void createLazyProxy() { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTargetSource(new DubboReferenceLazyInitTargetSource()); proxyFactory.addInterface(interfaceClass); Class<?>[] internalInterfaces = AbstractProxyFactory.getInternalInterfaces(); for (Class<?> anInterface : internalInterfaces) { proxyFactory.addInterface(anInterface); } if (!StringUtils.isEquals(interfaceClass.getName(), interfaceName)) { try { Class<?> serviceInterface = ClassUtils.forName(interfaceName, beanClassLoader); proxyFactory.addInterface(serviceInterface); } catch (ClassNotFoundException e) { } } this.lazyProxy = proxyFactory.getProxy(this.beanClassLoader); }
調(diào)用 LazyProxy 的方法會觸發(fā) JdkDynamicAopProxy#invoke()
,它會在第一次調(diào)用非 Object 方法時創(chuàng)建實際的對象。創(chuàng)建實際的對象由 DubboReferenceLazyInitTargetSource 完成:
private class DubboReferenceLazyInitTargetSource extends AbstractLazyCreationTargetSource { @Override protected Object createObject() throws Exception { // 首次調(diào)用 ReferenceBean 方法才會創(chuàng)建 return getCallProxy(); } @Override public synchronized Class<?> getTargetClass() { return getInterfaceClass(); } }
getCallProxy()
其實就是調(diào)用 ReferenceConfig#get()
引用服務。
private Object getCallProxy() throws Exception { if (referenceConfig == null) { throw new IllegalStateException("ReferenceBean is not ready yet, please make sure to call reference interface method after dubbo is started."); } synchronized (((DefaultSingletonBeanRegistry)getBeanFactory()).getSingletonMutex()) { return referenceConfig.get(); } }
尾巴
Dubbo3 和 Spring Boot 整合的過程可謂是一波三折,過程還是挺繞的,除了要了解 Dubbo 的底層原理,還要對 Spring Boot 的原理、各種后置處理器很熟悉才行。Dubbo 首先是提供了一個單獨的模塊來和 Spring Boot 做整合,利用 Spring Boot 自動裝配的功能,配置了一堆自動裝配的組件。首先是讀取到要掃描的包路徑,然后掃描 DubboService Bean,并把它注冊到 Spring 容器,而后統(tǒng)一交給 ModuleConfigManager 管理;再通過監(jiān)聽 ContextRefreshedEvent 事件來完成服務的啟動和發(fā)布。針對 DubboReference 的注入,則依賴 ReferenceAnnotationBeanPostProcessor 后置處理器,它會遍歷 Spring 容器內(nèi)所有的 BeanDefinition,然后查找注入點,把注入點封裝成 ReferenceBean 并注冊到 Spring 容器,依賴注入時再通過容器獲取對應的 bean 實例。Dubbo 采用的是延遲注入的方式,默認會注入一個空殼的 LazyProxy 對象,在首次調(diào)用 RPC 方法時再去調(diào)用底層的服務引用方法。
到此這篇關于Dubbo3和Spring Boot整合過程的文章就介紹到這了,更多相關Dubbo3和Spring Boot整合內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Elasticsearch查詢Range Query語法示例
這篇文章主要為大家介紹了Elasticsearch查詢Range Query語法示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04Java Mybatis框架多表操作與注解開發(fā)詳解分析
MyBatis 是一款優(yōu)秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數(shù)和獲取結(jié)果集的工作。MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO為數(shù)據(jù)庫中的記錄2021-10-10SpringBoot實現(xiàn)elasticsearch 查詢操作(RestHighLevelClient 
這篇文章主要給大家介紹了SpringBoot如何實現(xiàn)elasticsearch 查詢操作,文中有詳細的代碼示例和操作流程,具有一定的參考價值,需要的朋友可以參考下2023-07-07基于Java SSM框架開發(fā)圖書借閱系統(tǒng)源代碼
本文給大家介紹了基于Java SSM框架開發(fā)圖書借閱系統(tǒng),開發(fā)環(huán)境基于idea2020+mysql數(shù)據(jù)庫,前端框架使用bootstrap4框架,完美了實現(xiàn)圖書借閱系統(tǒng),喜歡的朋友快來體驗吧2021-05-05