原理 | dubbo [與 springboot 整合時服務導出的觸發(fā)]
@
§1 接入方式
dubbo 與 springboot 整合后的接入方式可以概括為 3 種
- Dubbo api 方式,通過 @Bean 的方式手動完成 dubbo 相關(guān)對象的聲明
- ApplicationConfig
- RegistryConfig
- ProtocolConfig
- ServiceBean
- xml,原始方式
- 注解,常用方式,使用下述注解,配合
- 原生 dubbo2:@Service/@Reference
- 原生 dubbo3:@DubboService/@DubboReference
不管使用哪種方式,整體思路應該是一致的
- 讓 spring 掃描到相關(guān)類,注冊
BeanDefinition(后簡稱為 BD)- Spring 容器
context.refresh()階段通過BeanDefinition生成SpringBean- 最后由這些
SpringBean完成 dubbo 的相關(guān)動作單說服務導出這個事,其核心類是 ServiceBean,dubbo 相關(guān)動作即導出,即
export()方法
§2 原生注解方式接入
入口 @EnableDubbo
dubbo2 原生注解生效的入口其實是 @EnableDubbo
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig
@DubboComponentScan //1
public @interface EnableDubbo {}
其上注解 @DubboComponentScan 導入了 DubboComponentScanRegistrar
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class) //1
public @interface DubboComponentScan {}
掃描 BeanDefinition 階段
DubboComponentScanRegistrar 用于負責 BeanDefinition 級的注冊,與本帖討論話題相關(guān)的代碼如下
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
//1
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
registerCommonBeans(registry);
}
private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
//2
BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);
builder.addConstructorArgValue(packagesToScan);
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
}
DubboComponentScanRegistrar 注冊了一個 ServiceAnnotationBeanPostProcessor,可以看到它是 BeanFactoryPostProcessor 的實現(xiàn)
其關(guān)鍵方法 registerServiceBeans 就是字面意思的功能,負責掃描并注冊 ServiceBean 的 BD
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {}
public class ServiceAnnotationBeanPostProcessor
implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registerBeans(registry, DubboBootstrapApplicationListener.class);
Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
//1
registerServiceBeans(resolvedPackagesToScan, registry);
} else {
if (logger.isWarnEnabled()) {
logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
}
}
}
}
registerServiceBeans 進行了如下 3 步
- //1:聲明對
@Service注解的掃描 - //2:執(zhí)行掃描,這里已經(jīng)獲取了相關(guān)的
BeanDefinition,但是這是原始類的BeanDefinition - //3:生成并注冊
ServiceBean的BeanDefinition
private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
//1
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));
for (String packageToScan : packagesToScan) {
scanner.scan(packageToScan);
//2
Set<BeanDefinitionHolder> beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
//3
registerServiceBean(beanDefinitionHolder, registry, scanner);
}
// ......
}
}
//2 處,會掃描指定包,按注解過濾其下所有 /**/*.class 文件,包裝成 BeanDefinition 進而包裝成 BeanDefinitionHolder 集合,如下
private Set<BeanDefinitionHolder> findServiceBeanDefinitionHolders(
ClassPathBeanDefinitionScanner scanner, String packageToScan, BeanDefinitionRegistry registry,
BeanNameGenerator beanNameGenerator) {
//2.1
Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packageToScan);
Set<BeanDefinitionHolder> beanDefinitionHolders = new LinkedHashSet<>(beanDefinitions.size());
for (BeanDefinition beanDefinition : beanDefinitions) {
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);
BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
beanDefinitionHolders.add(beanDefinitionHolder);
}
return beanDefinitionHolders;
}
//2.1 處,就已經(jīng)掃描出了所有 BD,其核心邏輯如下
即遍歷 registerServiceBeans //1 處聲明的注解過濾器,逐個匹配 //2 處指定包下的所有類型
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
//...
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return isConditionMatch(metadataReader);
}
}
return false;
}
//3 處,registerServiceBean 方法負責注冊 ServiceBean 的 BD
為什么會有兩種BD ?
因為這倆是針對不同類的 BD。
假設有個類叫 A,它被標注了@Service注解。則,前者是 A 的實例定義信息,而后者是對應 A 的ServiceBean的實例定義信息
BD 是對 spring 里的 bean 的描述,其抽象實現(xiàn)
AbstractBeanDefinition中,有一個成員Object beanClass;用于描述被定義的 Bean 的類型
這兩個 BD 之間的邏輯說白了就是通過輸入流讀取指定包下 class 文件,然后篩選出帶有對應注解的,包裝成第一種 BD
這些 BD,都需要讓 Spring 聲明為 dubbo 的提供者,于是用原 BD 聲明出 dubbo 使用的 ServiceBean 的 BD(第二種),并注冊到 Spring 容器
最后在 Spring 的 refresh 環(huán)節(jié)完成 BD 到 SpringBean 的轉(zhuǎn)換
private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
DubboClassPathBeanDefinitionScanner scanner) {
Class<?> beanClass = resolveClass(beanDefinitionHolder);
Annotation service = findServiceAnnotation(beanClass);
AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false);
Class<?> interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass);
String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
//3.1
AbstractBeanDefinition serviceBeanDefinition =
buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);
String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);
if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { // check duplicated candidate bean
//3.2
registry.registerBeanDefinition(beanName, serviceBeanDefinition);
//...
}
}
//3.1 組裝 ServiceBean 的 BD,組裝用的信息從原 BD 及其注解上獲取
//3.2 將新的 BD 注冊到 Spring 容器,默認由 DefaultListableBeanFactory 實現(xiàn),核心代碼就是下面兩句
會同時加入兩個集合:beanDefinitionMap / beanDefinitionNames
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
//...
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName); // 可以視為 beanDefinitionMap 的索引
//...
}
}
生成 ServiceBean
生成 ServiceBean 是在 applicationContext.refresh() 中實現(xiàn)的,具體流程可以參考下面的脈絡
- 入口是 Springboot 啟動類
SpringApplication.run(XxxApplication.class, args); - 觸發(fā)容器刷新的方法也在
SpringApplication中refreshContext(context);applicationContext.refresh();
- 容器刷新的功能默認由
AbstractApplicationContext實現(xiàn)finishBeanFactoryInitialization(beanFactory);beanFactory.preInstantiateSingletons();
preInstantiateSingletons的實現(xiàn)在DefaultListableBeanFactory中
這個類剛剛提到過,最終 ServiceBean 的 BD 就在這個類的成員集合中
//1 處,就是上文提到的集合之一, preInstantiateSingletons 會遍歷此集合
//2 處,beanDefinitionNames 中記錄的每個 BD 都會通過此方法創(chuàng)建對象,本文討論的話題中,最終生成的就是 ServiceBean
public void preInstantiateSingletons() throws BeansException {
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); //1
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) { //...
} else {
getBean(beanName); //2
}
}
}
// smart 初始化邏輯,忽略
}
getBean 中相關(guān)的核心邏輯如下
// 從前文所屬集合獲取 BD,此方法最終是從 beanDefinitionMap 獲取 BD
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// ...
//處理依賴(遞歸 getBean)
String[] dependsOn = mbd.getDependsOn();
// ...
// 真正創(chuàng)建實例
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
//在這里實際創(chuàng)建,會使用前面生成的 beanDefinition
return createBean(beanName, mbd, args);
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
服務導出
服務導出最終由 ServiceConfig.export() 實現(xiàn)(ServiceBean 是 ServiceConfig 的子類),這就好辦了,打個斷點,可見如下

原生注解的服務導出由 DubboBootstrapApplicationListener 監(jiān)聽(實現(xiàn)了 ApplicationListener),由 Spring 的 ContextRefreshedEvent 事件觸發(fā)
public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener implements Ordered {
@Override
public void onApplicationContextEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
// 事件觸發(fā)
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
}
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
dubboBootstrap.start(); // dubbo 啟動
}
}
比較關(guān)鍵的代碼摘要如下
public DubboBootstrap start() {
if (started.compareAndSet(false, true)) {
initialize();
if (logger.isInfoEnabled()) {
logger.info(NAME + " is starting...");
}
exportServices(); //從這里導出服務
//...
}
return this;
}
里面的邏輯很好找重點,從 configManager 遍歷,挨個執(zhí)行 ServiceConfig.export()
private void exportServices() {
//1 遍歷
configManager.getServices().forEach(sc -> {
ServiceConfig serviceConfig = (ServiceConfig) sc;
serviceConfig.setBootstrap(this);
if (exportAsync) {
//...挨個異步導出
} else {
//挨個同步導出
sc.export();
exportedServices.add(sc);
}
});
}
唯一的問題是 configManager 里面的東西是怎么來的。
答案是在 ServiceConfig/ServiceBean 的父類 AbstractConfig 上定義的初始化方法(繼承關(guān)系有點復雜,如下圖)
ServiceBean 初始化完成后會通過此方法把自己加入 ConfigManager,至于為什么這里叫做 ConfigManager,這是因為 dubbo 的各種對象都叫 xxxConfig
@PostConstruct
public void addIntoConfigManager() {
ApplicationModel.getConfigManager().addConfig(this);
}

§3 原生 xml 方式接入
xml 方式接入時,可以推測其整個流程的最大不同在 BeanDefinition 階段
原生注解接入時,dubbo 的服務是由注解標注生效,服務也是由注解標注聲明,因此我們從注解開始找入口(如果找不到應該結(jié)合 Spring 的機制找相關(guān)的類)
xml 方式接入時,服務是從 xml 里聲明的,入口也應該從 xml 開始找,尤其是其對應的解析器
入口 xmlns:dubbo
xml 方式入口位置很容易忽略,其實是 xml 父標簽的 xmlns。
xmlns 用于聲明 xml 的 namespace,用于定義其中的標簽,以及其解析器,如下
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" (here)
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
</beans>
http://dubbo.apache.org/schema/dubbo 直接訪問是訪問不了的,它實際上定義在 dubbo-config-spring 項目下的 spring.handlers 中
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandle
可見對應的是 DubboNamespaceHandler,作為 Dubbo 標簽的解析器,解析器中明顯可見如下代碼
其初始化方法中注冊了 BD 解析器,針對每種標簽注冊了一種,以 registry 為例,就對應 <dubbo:registry /> 標簽
從下面代碼可見,xml 依賴的解析器基本都是 DubboBeanDefinitionParser
從這里也可以看到,dubbo 解析后的類,基本全叫 xxxConfig
public class DubboNamespaceHandler extends NamespaceHandlerSupport implements ConfigurableSourceBeanMetadataElement {
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
//1
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}
掃描 BeanDefinition 階段
DubboBeanDefinitionParser 見名知意,是用于將標簽解析為 dubbo 的 BD 的解析器,既然是解析器其核心方法必然是 parse,入口斷點調(diào)試可見

解析由 spring 觸發(fā) XmlBeanDefinitionReader, 在 DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions() 中執(zhí)行
此方法的有效邏輯最終由 DubboBeanDefinitionParser.parse() 全權(quán)實現(xiàn):解析標簽為 BD 并完成注冊
本帖相關(guān)核心代碼如下
//1:這里的 beanClass 直接使用的成員變量,即 DubboBeanDefinitionParser 實例化時傳來的 ServiceBean.class
//2:直接定義結(jié)果 BD,parse 方法的作用就是將標簽 <dubbo:service/> 解析為 ServiceBean 的 BD,并完成 BD 的注冊
//3:按 Dubbo 的規(guī)則生成 id,這個 id 會作為 ServiceBean 的 BD 的名字注冊
//4:注冊 ServiceBean 的 BD,parserContext.getRegistry() 實際返回的是 DefaultListableBeanFactory,與注解方式接入時一致
//5/6:與注解方式接入的一個區(qū)別在于此,xml 方式是現(xiàn)有 ServiceBean 的 BD,在注冊時再去生成實際服務的 BD,而注解方式是相反的
public BeanDefinition parse(Element element, ParserContext parserContext) {
registerDubboConfigAliasPostProcessor(parserContext.getRegistry());
//1
return parse(element, parserContext, beanClass, required);
}
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
//2
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
String id = element.getAttribute("id");
//3
if (StringUtils.isEmpty(id) && required) {//...}
if (StringUtils.isNotEmpty(id)) {
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " + id);
}
//4
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
if (ProtocolConfig.class.equals(beanClass)) {
} else if (ServiceBean.class.equals(beanClass)) {
String className = element.getAttribute("class");
if (StringUtils.isNotEmpty(className)) {
//5
RootBeanDefinition classDefinition = new RootBeanDefinition();
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
parseProperties(element.getChildNodes(), classDefinition);
//6
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
//...
}
//...
}
生成 ServiceBean
與原生注解方式一致
服務導出
與原生注解方式一致

浙公網(wǎng)安備 33010602011771號