《阅读源码系列》—— Dubbo的启动流程

2,351 阅读4分钟

Dubbo启动流程

伴随着SpringBoot容器的启动,Dubbo 主要是做了如下几件事情:

  • 将解析属性配置的class 和解析注解(@Service@Reference等等)配置的class 注册到beanDefinitionMap
  • 将解析后的配置类(AnnotationAttributes)和Dubbo的@Service服务,注册到beanDefinitionMap
  • 当Spring创建Bean、填充属性时,对@Reference 注解标注的属性做注入(inject)
  • 通过监听ContextRefreshedEvent事件,调用DubboBootstrap 启动器,暴露@Service 服务到注册中心

Dubbo启动流程图

image.png

下面是源码逻辑

属性配置解析器和注解解析器

@Dubbo、@EnableDubboConfig、@DubboComponentScan

当你使用@EnableDubbo注解启动Dubbo的时候,会加载它的@EnableDubboConfig@DubboComponentScan 注解,分别用于处理Dubbo属性配置和解析Dubbo服务

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
// 处理配置文件中(例如yaml文件)的dubbo配置
@EnableDubboConfig
// 扫描并解析使用了dubbo注解的组件(例如@Service)
@DubboComponentScan
public @interface EnableDubbo {
    ......
}

@EnableDubboConfig@DubboComponentScan 注解使用了Spring的@Import 注解来加载具体的实现类。

@Import(DubboConfigConfigurationRegistrar.class)
@Import(DubboComponentScanRegistrar.class)

DubboConfigConfigurationRegistrar

DubboConfigConfigurationRegistrar 用于将不同的属性加载到不同的配置文件中

public class DubboConfigConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                importingClassMetadata.getAnnotationAttributes(EnableDubboConfig.class.getName()));

        boolean multiple = attributes.getBoolean("multiple");

        // 加载单个的属性,例如<dubbo.protocol>
        registerBeans(registry, DubboConfigConfiguration.Single.class);

        if (multiple) { 
            //加载组合属性,例如<dubbo.protocols>
            registerBeans(registry, DubboConfigConfiguration.Multiple.class);
        }

        // 将通用class注册到beanDefinitionMap
        // 例如@Reference 注解的后置处理器:ReferenceAnnotationBeanPostProcessor.class
        registerCommonBeans(registry);
    }
}

registerBeans 方法最终通过 ConfigurationBeanBindingsRegister 将解析之后的配置类注册到BeanDefinitionMap

public class ConfigurationBeanBindingsRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private ConfigurableEnvironment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //省略部分代码.....
        for (AnnotationAttributes element : annotationAttributes) {
            //将解析后的对象注册到BeanDefinitionMap
            registrar.registerConfigurationBeanDefinitions(element, registry);
        }
    }
}

最终在Spring 容器中他们会被初始化成若干对象,例如:dubbo:registry 会转换成org.apache.dubbo.config.RegistryConfig#0

image.png

DubboComponentScanRegistrar

DubboComponentScanRegistrar 主要是用来将ServiceAnnotationBeanPostProcessor 注册到BeanDefinitionMap 中。
在Spring调用BeanFactory相关的后置处理器(invokeBeanFactoryPostProcessors)时,会使用ServiceAnnotationBeanPostProcessor@DubboService相关注解注册到BeanDefinitionMap

public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //省略部分代码......
        
        //将ServiceAnnotationBeanPostProcessor 注册到Spring容器
        registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);

        // 将通用class注册到beanDefinitionMap
        // 例如@Reference 注解的后置处理器:
        registerCommonBeans(registry);
    }
}

//注册ServiceAnnotationBeanPostProcessor 的方法
private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

        BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);
        builder.addConstructorArgValue(packagesToScan);
        builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);

    }

ServiceAnnotationBeanPostProcessor 中,真正做注解解析注册的是他的父类ServiceClassPostProcessor image.png

ServiceClassPostProcessor 中,它注册了一个dubbo监听器,用于监听Spring容器的刷新、关闭事件,同时也将@DubboService 注解的类注册到了BeanDefinitionMap 中.

public class ServiceClassPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
        ResourceLoaderAware, BeanClassLoaderAware {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //省略部分代码...
    
        //注册Dubbo监听器,建东Spring容器的事件
        registerBeans(registry, DubboBootstrapApplicationListener.class);

        //注册Dubbo 组件Bean
        registerServiceBeans(resolvedPackagesToScan, registry);
}

    private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
        //省略部分代码...
        
        for (String packageToScan : packagesToScan) {
            scanner.scan(packageToScan);

            //将@ComponentScan 组件扫描到的,并且是@DubboService 标注的Bean导出
            Set<BeanDefinitionHolder> beanDefinitionHolders =
                    findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);

            if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
                
                for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                    //注册Dubbo 的Service Bean
                    registerServiceBean(beanDefinitionHolder, registry, scanner);
                }
            }  
        }
    }

至此,被@DubboService(2.7.7)、@Service(2.7.0\alibaba) 注解标注的Bean 就被注册到了IOC容器,成为IOC 中Bean 集合的一员。

DubboBootstrap 启动器

伴随着Spring 容器的启动,在invokeBeanFactoryPostProcessors阶段我们注册了dubbo相关的组件到IOC,在finishBeanFactoryInitialization(beanFactory) Dubbo的组件被初始化、实例化,最后Dubbo通过监听Spring事件的方式完成启动器的调用、服务导出等操作

DubboBootstrap

DubboBootstrap 的启动是通过监听Spring事件实现的。Spring会在容器Refresh 的最后一步发送一个事件ContextRefreshedEvent,表示容器刷新完毕。 image.png

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();
    }
}

对于ContextRefreshedEvent 事件的监听,最终调用了dubboBootstrap.start() 方法,在这个方法里,Dubbo 完成了对服务的导出(暴露),导出服务的过程中,用到了DUBBO SPI机制

public class DubboBootstrap extends GenericEventListener {
    public DubboBootstrap start() {
        //省略部分代码...
        
        //导出Dubbo 服务
        exportServices();
        
        //处理ReferenceConfig对象,添加异步任务
        referServices();
    }
}
    //这里最终调用了ServiceConfig 的export()
    private void exportServices() {
        configManager.getServices().forEach(sc -> {
            // TODO, compatible with ServiceConfig.export()
            ServiceConfig serviceConfig = (ServiceConfig) sc;
            serviceConfig.setBootstrap(this);

            if (exportAsync) {
                ExecutorService executor = executorRepository.getServiceExporterExecutor();
                Future<?> future = executor.submit(() -> {
                    sc.export();
                    exportedServices.add(sc);
                });
                asyncExportingFutures.add(future);
            } else {
                sc.export();
                exportedServices.add(sc);
            }
        });
    }

ServiceConfig

ServiceConfig 最终发送了一个事件:ServiceConfigExportedEvent,通过事件通知的方式实现导出逻辑。

    public void exported() {
        // dispatch a ServiceConfigExportedEvent since 2.7.4
        dispatch(new ServiceConfigExportedEvent(this));
    }

ServiceNameMappingListener

public class ServiceNameMappingListener implements EventListener<ServiceConfigExportedEvent> {

    private final ServiceNameMapping serviceNameMapping = getDefaultExtension();

    //这里监听并解析了DubboReference 用到的主要参数
    @Override
    public void onEvent(ServiceConfigExportedEvent event) {
        ServiceConfig serviceConfig = event.getServiceConfig();
        List<URL> exportedURLs = serviceConfig.getExportedUrls();
        exportedURLs.forEach(url -> {
            String serviceInterface = url.getServiceInterface();
            String group = url.getParameter(GROUP_KEY);
            String version = url.getParameter(VERSION_KEY);
            String protocol = url.getProtocol();
            serviceNameMapping.map(serviceInterface, group, version, protocol);
        });
    }
}

ServiceNameMapping 及其子类承接了对注册中心的调用,以nacos 为例调用逻辑如下图:

image.png

文字描述一下这个过程就是:
DynamicConfigurationServiceNameMapping.map
-> CompositeDynamicConfiguration.publishConfig
-> NacosDynamicConfiguration.publishConfig

public class DynamicConfigurationServiceNameMapping implements ServiceNameMapping {
    @Override
    public void map(String serviceInterface, String group, String version, String protocol) {

        if (IGNORED_SERVICE_INTERFACES.contains(serviceInterface)) {
            return;
        }

        //在这里存储了所有的注册中心组件
        //返回一个CompositeDynamicConfiguration 实例
        DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration();

        // the Dubbo Service Key as group
        // the service(application) name as key
        // It does matter whatever the content is, we just need a record
        String key = getName();
        String content = valueOf(System.currentTimeMillis());
        execute(() -> {
            //调用CompositeDynamicConfiguration,然后由他来调用具体的注册中心
            dynamicConfiguration.publishConfig(key, buildGroup(serviceInterface, group, version, protocol), content);
            if (logger.isInfoEnabled()) {
                logger.info(String.format("Dubbo service[%s] mapped to interface name[%s].",
                        group, serviceInterface, group));
            }
        });
    }

nacos 的注册中心代码实现如下:

public class NacosDynamicConfiguration implements DynamicConfiguration {
    @Override
    public boolean publishConfig(String key, String group, String content) {
        boolean published = false;
        String resolvedGroup = resolveGroup(group);
        try {
            //这里的configService 就是nacos 对应的api了
            String value = configService.getConfig(key, resolvedGroup, getDefaultTimeout());
            if (StringUtils.isNotEmpty(value)) {
                content = value + "," + content;
            }
            published = configService.publishConfig(key, resolvedGroup, content);
        } catch (NacosException e) {
            logger.error(e.getErrMsg());
        }
        return published;
    }
}

至此dubbo 的服务就顺利的暴露到注册中心了,dubbo 也基本完成了他的启动工作。