INDEX
- §1 接入方式
- §2 原生注解方式接入
- 入口 `@EnableDubbo`
- 扫描 `BeanDefinition` 阶段
- 生成 `ServiceBean`
- 服务导出
- §3 原生 xml 方式接入
- 入口 `xmlns:dubbo`
- 扫描 BeanDefinition 阶段
- 生成 ServiceBean
- 服务导出
§1 接入方式
dubbo 与 springboot 整合后的接入方式可以概括为 3 种
- Dubbo api 方式,通过 @Bean 的方式手动完成 dubbo 相关对象的声明
- ApplicationConfig
- RegistryConfig
- ProtocolConfig
- ServiceBean
- xml,原始方式
- 注解,常用方式,使用下述注解,配合
- 原生 dubbo2:@Service/@Reference
- 原生 dubbo3:@DubboService/@DubboReference
不管使用哪种方式,整体思路应该是一致的
- 让 spring 扫描到相关类,注册
BeanDefinition
(后简称为 BD)- Spring 容器
context.refresh()
阶段通过BeanDefinition
生成SpringBean
- 最后由这些
SpringBean
完成 dubbo 的相关动作单说服务导出这个事,其核心类是 ServiceBean,dubbo 相关动作即导出,即
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
级的注册,与本帖讨论话题相关的代码如下
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);//1registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);registerCommonBeans(registry);}private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {//2BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);builder.addConstructorArgValue(packagesToScan);builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);}
DubboComponentScanRegistrar
注册了一个 ServiceAnnotationBeanPostProcessor
,可以看到它是 BeanFactoryPostProcessor
的实现
其关键方法 registerServiceBeans
就是字面意思的功能,负责扫描并注册 ServiceBean
的 BD
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {}public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {registerBeans(registry, DubboBootstrapApplicationListener.class); Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {//1registerServiceBeans(resolvedPackagesToScan, registry);} else {if (logger.isWarnEnabled()) {logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");}}}
}
registerServiceBeans
进行了如下 3 步
- //1:声明对
@Service
注解的扫描 - //2:执行扫描,这里已经获取了相关的
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);//1scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));for (String packageToScan : packagesToScan) {scanner.scan(packageToScan);//2Set<BeanDefinitionHolder> beanDefinitionHolders =findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {//3registerServiceBean(beanDefinitionHolder, registry, scanner); }// ......}
}
//2 处,会扫描指定包,按注解过滤其下所有 /**/*.class 文件,包装成 BeanDefinition
进而包装成 BeanDefinitionHolder
集合,如下
private Set<BeanDefinitionHolder> findServiceBeanDefinitionHolders(ClassPathBeanDefinitionScanner scanner, String packageToScan, BeanDefinitionRegistry registry,BeanNameGenerator beanNameGenerator) {//2.1Set<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 处,就已经扫描出了所有 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 的描述,其抽象实现
AbstractBeanDefinition
中,有一个成员Object beanClass;
用于描述被定义的 Bean 的类型
这两个 BD 之间的逻辑说白了就是通过输入流读取指定包下 class 文件,然后筛选出带有对应注解的,包装成第一种 BD
这些 BD,都需要让 Spring 声明为 dubbo 的提供者,于是用原 BD 声明出 dubbo 使用的 ServiceBean 的 BD(第二种),并注册到 Spring 容器
最后在 Spring 的 refresh 环节完成 BD 到 SpringBean 的转换
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.1AbstractBeanDefinition serviceBeanDefinition =buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { // check duplicated candidate bean//3.2registry.registerBeanDefinition(beanName, serviceBeanDefinition);//...}
}
//3.1 组装 ServiceBean
的 BD,组装用的信息从原 BD 及其注解上获取
//3.2 将新的 BD 注册到 Spring 容器,默认由 DefaultListableBeanFactory
实现,核心代码就是下面两句
会同时加入两个集合:beanDefinitionMap
/ beanDefinitionNames
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {//...this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName); // 可以视为 beanDefinitionMap 的索引//...}
}
生成 ServiceBean
生成 ServiceBean
是在 applicationContext.refresh()
中实现的,具体流程可以参考下面的脉络
- 入口是 Springboot 启动类
SpringApplication.run(XxxApplication.class, args);
- 触发容器刷新的方法也在
SpringApplication
中refreshContext(context);
applicationContext.refresh();
- 容器刷新的功能默认由
AbstractApplicationContext
实现finishBeanFactoryInitialization(beanFactory);
beanFactory.preInstantiateSingletons();
preInstantiateSingletons
的实现在DefaultListableBeanFactory
中
这个类刚刚提到过,最终 ServiceBean
的 BD 就在这个类的成员集合中
//1 处,就是上文提到的集合之一, preInstantiateSingletons
会遍历此集合
//2 处,beanDefinitionNames
中记录的每个 BD 都会通过此方法创建对象,本文讨论的话题中,最终生成的就是 ServiceBean
public void preInstantiateSingletons() throws BeansException { List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); //1for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) { //... } else {getBean(beanName); //2}}}// smart 初始化逻辑,忽略
}
getBean
中相关的核心逻辑如下
// 从前文所属集合获取 BD,此方法最终是从 beanDefinitionMap 获取 BD
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// ...
//处理依赖(递归 getBean)
String[] dependsOn = mbd.getDependsOn();
// ...
// 真正创建实例
if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> { //在这里实际创建,会使用前面生成的 beanDefinition return createBean(beanName, mbd, args);});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
服务导出
服务导出最终由 ServiceConfig.export()
实现(ServiceBean
是 ServiceConfig
的子类),这就好办了,打个断点,可见如下
原生注解的服务导出由 DubboBootstrapApplicationListener
监听(实现了 ApplicationListener
),由 Spring 的 ContextRefreshedEvent
事件触发
public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener implements Ordered {@Overridepublic void onApplicationContextEvent(ApplicationContextEvent event) {if (event instanceof ContextRefreshedEvent) {// 事件触发onContextRefreshedEvent((ContextRefreshedEvent) event);} else if (event instanceof ContextClosedEvent) {onContextClosedEvent((ContextClosedEvent) event);}}private void onContextRefreshedEvent(ContextRefreshedEvent event) {dubboBootstrap.start(); // dubbo 启动}
}
比较关键的代码摘要如下
public DubboBootstrap start() {if (started.compareAndSet(false, true)) {initialize();if (logger.isInfoEnabled()) {logger.info(NAME + " is starting...");}exportServices(); //从这里导出服务//...}return this;
}
里面的逻辑很好找重点,从 configManager 遍历,挨个执行 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
上定义的初始化方法(继承关系有点复杂,如下图)
ServiceBean
初始化完成后会通过此方法把自己加入 ConfigManager
,至于为什么这里叫做 ConfigManager
,这是因为 dubbo 的各种对象都叫 xxxConfig
@PostConstruct
public void addIntoConfigManager() {ApplicationModel.getConfigManager().addConfig(this);
}
§3 原生 xml 方式接入
xml 方式接入时,可以推测其整个流程的最大不同在 BeanDefinition 阶段
原生注解接入时,dubbo 的服务是由注解标注生效,服务也是由注解标注声明,因此我们从注解开始找入口(如果找不到应该结合 Spring 的机制找相关的类)
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.xsdhttp://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 {@Overridepublic 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));//1registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());}
}
扫描 BeanDefinition 阶段
DubboBeanDefinitionParser
见名知意,是用于将标签解析为 dubbo 的 BD 的解析器,既然是解析器其核心方法必然是 parse
,入口断点调试可见
解析由 spring 触发 XmlBeanDefinitionReader
, 在 DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions()
中执行
此方法的有效逻辑最终由 DubboBeanDefinitionParser.parse()
全权实现:解析标签为 BD 并完成注册
本帖相关核心代码如下
//1:这里的 beanClass 直接使用的成员变量,即 DubboBeanDefinitionParser
实例化时传来的 ServiceBean.class
//2:直接定义结果 BD,parse 方法的作用就是将标签 <dubbo:service/>
解析为 ServiceBean
的 BD,并完成 BD 的注册
//3:按 Dubbo 的规则生成 id,这个 id 会作为 ServiceBean
的 BD 的名字注册
//4:注册 ServiceBean
的 BD,parserContext.getRegistry()
实际返回的是 DefaultListableBeanFactory
,与注解方式接入时一致
//5/6:与注解方式接入的一个区别在于此,xml 方式是现有 ServiceBean
的 BD,在注册时再去生成实际服务的 BD,而注解方式是相反的
public BeanDefinition parse(Element element, ParserContext parserContext) {registerDubboConfigAliasPostProcessor(parserContext.getRegistry());//1return parse(element, parserContext, beanClass, required);
}private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {//2RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(beanClass); beanDefinition.setLazyInit(false);String id = element.getAttribute("id");//3if (StringUtils.isEmpty(id) && required) {//...} if (StringUtils.isNotEmpty(id)) {if (parserContext.getRegistry().containsBeanDefinition(id)) {throw new IllegalStateException("Duplicate spring bean id " + id);}//4parserContext.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)) {//5RootBeanDefinition classDefinition = new RootBeanDefinition(); classDefinition.setBeanClass(ReflectUtils.forName(className));classDefinition.setLazyInit(false);parseProperties(element.getChildNodes(), classDefinition);//6beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl")); }//...}//...
}
生成 ServiceBean
与原生注解方式一致
服务导出
与原生注解方式一致