当前位置: 首页 > article >正文

原理 | dubbo [与 springboot 整合时服务导出的触发]

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 {
    @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 的实现
其关键方法 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:执行扫描,这里已经获取了相关的 BeanDefinition,但是这是原始类的 BeanDefinition
  • //3:生成并注册 ServiceBeanBeanDefinition
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 处,就已经扫描出了所有 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.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 实现,核心代码就是下面两句
会同时加入两个集合: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() 中实现的,具体流程可以参考下面的脉络

  • 入口是 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); //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 中相关的核心逻辑如下

// 从前文所属集合获取 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() 实现(ServiceBeanServiceConfig 的子类),这就好办了,打个断点,可见如下
在这里插入图片描述
原生注解的服务导出由 DubboBootstrapApplicationListener 监听(实现了 ApplicationListener),由 Spring 的 ContextRefreshedEvent 事件触发

public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener implements Ordered {
    @Override
    public 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.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,入口断点调试可见
在这里插入图片描述

解析由 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());
    //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

与原生注解方式一致


服务导出

与原生注解方式一致


http://www.kler.cn/a/441591.html

相关文章:

  • JAVA:Spring Boot 实现责任链模式处理订单流程的技术指南
  • HP 笔记本重新安装 Windows 11 无法启动
  • C#深度神经网络(TensorFlow.NET)
  • 一文大白话讲清楚webpack基本使用——11——chunkIds和runtimeChunk
  • 数据结构与算法之递归: LeetCode 131. 分割回文串 (Ts 版)
  • STM32学习9---EXIT外部中断(理论)
  • Java全栈项目 - 学生宿舍管理系统
  • 加载文件到docker中的mysql上
  • Linux高性能服务器编程 | 读书笔记 | 6. 高性能服务器程序框架
  • 【报错解决】pip install volcengine-python-sdk无法安装包
  • 【行政区编码对应表及生态等级数据的制作】-python
  • centos下安装ffmpeg
  • Python爬虫之代理的设置
  • linux0.11源码分析第一弹——bootset.s内容
  • (2024.12)记录——Ubuntu20.04安装opencv库
  • 【JVM】JVM基础教程(四)
  • ubuntu20.04安装qt creator
  • Leetcode经典题8--H指数
  • 深度学习之Autoencoders GANs for Anomaly Detection 视频异常检测
  • xshell连接虚拟机,更换网络模式:NAT->桥接模式
  • SpringBoot集成ENC对配置文件进行加密
  • 阶段性demo 键盘信息过滤
  • 天猫魔盒M17/M17S_超级UI 线刷固件包-可救砖(刷机取消双勾)
  • 【HF设计模式】03-装饰者模式
  • Transformer 中 Self-Attention 的二次方复杂度(Quadratic Complexity )问题及改进方法:中英双语
  • 【Flink-scala】DataStream编程模型总结