从源码角度一步步窥探Dubbo服务发布原理

@DubboComponentScan注解

我们在使用Dubbo时,在没有使用阿里的starter时,都会在启动类打上@DubboComponentScan这个注解来将我们被Dubbo的@service注解标注的类,注册到Spring IOC容器中。这个注解很关键,Dubbo服务的发布,以及引用都从此注解开始。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
// 省略注解属性
}

注解里Import了DubboComponentScanRegistrar这个类,从字面上就能大致理解是做什么用的了–扫描Dubbo的组件并将其注册。我们看看这个类具体长什么样并干些什么。

public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
}

DubboComponentScanRegistrar 实现了spring 提供的ImportBeanDefinitionRegistrar接口。这个接口对于扩展spring非常重要,其作用是注册BeanDefinition。熟悉spring的应该会知道,spring在注册bean前,会将扫描的bean组装成BeanDefinition。由于这里不是讲解spring IOC原理,就不过多赘述,后续再另开篇幅写IOC原理。ImportBeanDefinitionRegistrar接口定义如下:

public interface ImportBeanDefinitionRegistrar {

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
			BeanNameGenerator importBeanNameGenerator) {
			
		// 忽略BeanNameGenerator 转发调用其重载的另一个registerBeanDefinitions方法
		registerBeanDefinitions(importingClassMetadata, registry);
	}

	default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	}

我们再看看DubboComponentScanRegistrar 是如何实现重写ImportBeanDefinitionRegistrar的接口:

public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {

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

		// 解析@service注解,获取将要扫描的包路径集合
        Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
		// 后置处理器--将带有@service注解的类解析并注册到Ioc容器中,并export进行服务暴露
        registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
		// 后置处理器--将带有@Reference注解的类解析并注册到IOC容器中,并初始化ReferenceBeanInvocationHandler,代理执行invoke进行服务调用
        registerReferenceAnnotationBeanPostProcessor(registry);

    }
    // 省略其他代码....
  }

由于篇幅限制,本文着重分析Dubbo的发布原理,调用原理将另起篇幅分析。

我们先分析getPackagesToScan方法,看看Dubbo是如何处理获取包路径的。在分析之前,我们先看看这个DubboComponentScan注解有哪些属性

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {

    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

接着具体看看getPackagesToScan的操作

private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
		// 获取DubboComponentScan注解的注解属性,这里的AnnotationAttributes类在spring源码中也是能寻觅到其踪迹的。
		// AnnotationAttributes是spring抽出来的注解属性对象,与其经常配对出现的就是spring封装的工具类AnnotationUtils和AnnotatedElementUtils
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(DubboComponentScan.class.getName()));
        // 获取注解的basePackages属性值
        String[] basePackages = attributes.getStringArray("basePackages");
        // 获取注解的basePackageClasses属性值
        Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
        // 获取注解的value属性值
        String[] value = attributes.getStringArray("value");
        // 用value 初始化一个packagesToScan 集合
        Set<String> packagesToScan = new LinkedHashSet<String>(Arrays.asList(value));
        // 添加basePackages的值
        packagesToScan.addAll(Arrays.asList(basePackages));
        // 遍历basePackageClasses,将制定的类所处的包路径也添加到packagesToScan集合中
        for (Class<?> basePackageClass : basePackageClasses) {
            packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
        }
        // 如果从三处basePackages、basePackageClasses、value获取的包路径都为空
        // 返回该注解所标注的当前类所处的包路径作为packagesToScan
        // 从此处我们就可以知道为什么@DubboComponentScan为何通常标注在启动类上的原因了
        if (packagesToScan.isEmpty()) {
            return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName()));
        }
        return packagesToScan;
    }

看到这里,如果熟悉spring的朋友不知是否会有跟笔者有这么这么一个疑问,value 和 basePackages 两个属性本质都一样,可互相作为别名,注解源码的注释也证明这一点,那为何不用@AliasFor注解来简化这两个属性的解析操作呢?

搞定了要扫描的包路径,我们当然就要在这些包路径来做解析了,具体怎么解析,我们来看一下源码:

private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

		// 得到一个将指定类作为RootBeanDefinition,并将其作为BeanDefinitionBuilder构造参数来实例化BeanDefinitionBuilder,跟BeanDefinitionBuilder 比较相似的还有个BeanDefinitionHolder,因此可见spring的抽象设计是做得很好的,拿beanDefinition 不会直接拿,而是通过builder或者holder这些包装类来拿
        BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);
        // 添加构造参数
        builder.addConstructorArgValue(packagesToScan);
        // 设置角色
        builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        // 获得beanDefinition 
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        // 将此beanDefinition 注册到IOC中
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
    }

最重要的一步就是构建ServiceAnnotationBeanPostProcessor后置处理器这个beanDefinition。我们来看看这个后置处理器做了什么。从声明上看,它实现了BeanDefinitionRegistryPostProcessor、EnvironmentAware、ResourceLoaderAware以及BeanClassLoaderAware接口。熟悉spring都知道,这四个接口都是spring留给我们开发人员扩展spring的宝贵途径。

public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
        ResourceLoaderAware, BeanClassLoaderAware {
        }

我们可以轻易拿到当前spring上下文的environment、resourceLoader 以及classLoader三个资源,为我们后续深入处理时,提供便捷。这里顺带解释一下这三者。Environment、ClassLoader不难理解,我们系统都会分环境,加载类时一定会用到类加载器来加载对应类。对于ResourceLoader 资源加载器又为何需要呢?在spring实现中,spring在扫描的类要将其转为beanDefinition 前,还有一步操作是将其转为一个个Resource。

// ResourceLoaderAware
 @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
// ResourceLoaderAware
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
// BeanClassLoaderAware 
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

接下来我们最关键的一个接口实现–BeanDefinitionRegistryPostProcessor,重写postProcessBeanDefinitionRegistry方法

@Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		// 处理包路径的空格,并解析转换包路径的占位符
        Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);

        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
            // 注册resolvedPackagesToScan包路径下带有@service的bean到BeanDefinitionRegistry
            registerServiceBeans(resolvedPackagesToScan, registry);
        } else {
            if (logger.isWarnEnabled()) {
                logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
            }
        }
    }

registerServiceBeans这一步就是正式注册。我们来看看如何注册的

private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
		// 实例化一个DubboClassPathBeanDefinitionScanner扫描器对象
        DubboClassPathBeanDefinitionScanner scanner =
                new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
        // 构建beanNameGenerator.分两步
        // 第一步:如果当前是registry是SingletonBeanRegistry类型,则构建spring内建的org.springframework.context.annotation.internalConfigurationBeanNameGenerator
        // 第二步:第一步不成立的话,则直接new AnnotationBeanNameGenerator()来构建
        BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
        // 给dubbo扫描器设置beanname生成器
        scanner.setBeanNameGenerator(beanNameGenerator);
        // 给dubbo扫描器设置过滤链,告诉扫描器只扫apacha的@Service的
        scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
        // 这里是修复issue -- 打阿里的@service不生效而加的
        scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));
        // 遍历包,逐一扫描
        for (String packageToScan : packagesToScan) {
            scanner.scan(packageToScan);
            
           // 查找所有@service的beanDefinitionHolders ,不管是否在spring的@ComponentScan指定扫描范围内
            Set<BeanDefinitionHolder> beanDefinitionHolders =
                    findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
            if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
				// 第三步:遍历beanDefinitionHolders 并将其注册到registry
                for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                    registerServiceBean(beanDefinitionHolder, registry, scanner);
                }
                if (logger.isInfoEnabled()) {
                    logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " +
                            beanDefinitionHolders +
                            " } were scanned under package[" + packageToScan + "]");
                }
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn("No Spring Bean annotating Dubbo's @Service was found under package["
                            + packageToScan + "]");
                }
            }
        }
    }

这段代码里有关键三步,第一步是扫描,第二步是查找,第三步是注册。
我们先来看第一步: DubboClassPathBeanDefinitionScanner继承了ClassPathBeanDefinitionScanner,调用父类ClassPathBeanDefinitionScanner的scan扫描,真正的扫描逻辑在doScan中。作用是扫描,并将其解析为BeanDefinition添加到registry中。由于这块是spring的内容了,不再过多赘述。
第二步:查找所有@service的beanDefinitionHolders ,不管是否在spring的@ComponentScan指定扫描范围内

 private Set<BeanDefinitionHolder> findServiceBeanDefinitionHolders(
            ClassPathBeanDefinitionScanner scanner, String packageToScan, BeanDefinitionRegistry registry,
            BeanNameGenerator beanNameGenerator) {
		// 通过dubbo扫描器在对应包路径下获取组件候选者们
        Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packageToScan);
		// 创建一个BeanDefinitionHolder 的集合
        Set<BeanDefinitionHolder> beanDefinitionHolders = new LinkedHashSet<>(beanDefinitions.size());
		// 遍历
        for (BeanDefinition beanDefinition : beanDefinitions) {
        	// 通过beanname生成器为当前的beanDefinition生成bean的name
            String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);
            // 包装成BeanDefinitionHolder
            BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
            // 添加到BeanDefinitionHolder 集合中
            beanDefinitionHolders.add(beanDefinitionHolder);
        }
        return beanDefinitionHolders;
    }

不知读者是否有跟笔者有这么一个疑问–第一步交由spring的扫描与第二步dubbo自己查找扫描,都是获取到@service的beanDefinition并将其注册到registry。这两部像是重复操作?其实不然,第二步会在第三步的配合下,其beanDefinition将具体为Dubbo内建抽象的SviceBean的beanDefinition

思考:findServiceBeanDefinitionHolders可否用scanner.doScan替代,或者这两者选一?

2021-04-07补充:原来dubbo会分别注册:一份是普通的spring bean(是在指定扫描包下);另一份是serviceBean的spring bean(没有扫描包范围限制 只要是有@service注解的都会扫描到并注册)
在这里插入图片描述

第三步:registerServiceBean进行注册

private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
                                     DubboClassPathBeanDefinitionScanner scanner) {

		// 得到当前beanDefinitionHolder对应的bean的class
		// 底层是org.springframework.util.ClassUtils#resolveClassName
        Class<?> beanClass = resolveClass(beanDefinitionHolder);
		// 获取apacha或者阿里的@service的Annotation对象
        Annotation service = findServiceAnnotation(beanClass);
		// 获取其注解属性
        AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false);
		// 处理获取Dubbo service 的接口类型 InterfaceClass
		// 等下着重分析
        Class<?> interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass);
		// 获取beanname
        String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
		//包装成Dubbo抽象的ServiceBean的BeanDefinition
		// 等下着重分析
        AbstractBeanDefinition serviceBeanDefinition =
                buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);
		// 生成ServiceBean 的beanname
        String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);
        // 检查是否重复候选者
        if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { 
        	// 注册
            registry.registerBeanDefinition(beanName, serviceBeanDefinition);
// 省略部分代码
    }

我们继续深入分析resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass)

public static Class<?> resolveServiceInterfaceClass(AnnotationAttributes attributes, Class<?> defaultInterfaceClass)
            throws IllegalArgumentException {

        ClassLoader classLoader = defaultInterfaceClass != null ? defaultInterfaceClass.getClassLoader() : Thread.currentThread().getContextClassLoader();
		// 获取注解的interfaceClass的属性值,默认为void.class
        Class<?> interfaceClass = getAttribute(attributes, "interfaceClass");
		// 如果为默认值
        if (void.class.equals(interfaceClass)) {
            interfaceClass = null;
			// 取注解interfaceName的属性值
            String interfaceClassName = getAttribute(attributes, "interfaceName");
			// 如果interfaceClassName 有值
            if (hasText(interfaceClassName)) {
            	// 如果interfaceClassName存在并能被classLoader加载
                if (ClassUtils.isPresent(interfaceClassName, classLoader)) {
                	// 解析其interfaceClassName所对应的interfaceClass 
                    interfaceClass = resolveClassName(interfaceClassName, classLoader);
                }
            }
        }
        // 如果interfaceClass属性没有指定并且interfaceClassName也没指定或者所指定的类不存在或者不可加载
        if (interfaceClass == null && defaultInterfaceClass != null) {
           // 获取beanDefinitionHolder对应的bean Class的所实现的所有接口,并取第一个
           // 看到这一步有没有突然发现我们的@service是打在实现类上,然后在注册中心看到的接口类,为其接口的权限定名的原因。
           // 因此这里有个坑,我们的实现类实现的接口的顺序切记,业务接口先放,再放其他接口
            Class<?>[] allInterfaces = getAllInterfacesForClass(defaultInterfaceClass);
            if (allInterfaces.length > 0) {
                interfaceClass = allInterfaces[0];
            }
        }
        return interfaceClass;
    }

从源码就可以清楚看出,interfaceClass的优先级高于interfaceName,所以在指定interface时要注意!

我们再继续分析buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName)。其作用是将BeanDefinition包装成ServiceBean的BeanDefinition,包含ref、interface、parameters、provider、monitor、application、module等信息。ref属性持有是传入的beanDefinitionHolder对应的beanname所对应的bean引用。

ServiceBean实现了ApplicationListener监听,会在ServiceBean被注册到spring上下文时,触发监听,监听器里的实现就是export暴露

private AbstractBeanDefinition buildServiceBeanDefinition(Annotation serviceAnnotation,
                                                              AnnotationAttributes serviceAnnotationAttributes,
                                                              Class<?> interfaceClass,
                                                              String annotatedServiceBeanName) {

        BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);

        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();

        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();

        String[] ignoreAttributeNames = of("provider", "monitor", "application", "module", "registry", "protocol",
                "interface", "interfaceName", "parameters");

        propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(serviceAnnotation, environment, ignoreAttributeNames));

        // References "ref" property to annotated-@Service Bean
        addPropertyReference(builder, "ref", annotatedServiceBeanName);
        // Set interface
        builder.addPropertyValue("interface", interfaceClass.getName());
        // Convert parameters into map
        builder.addPropertyValue("parameters", convertParameters(serviceAnnotationAttributes.getStringArray("parameters")));

        /**
         * Add {@link org.apache.dubbo.config.ProviderConfig} Bean reference
         */
        String providerConfigBeanName = serviceAnnotationAttributes.getString("provider");
        if (StringUtils.hasText(providerConfigBeanName)) {
            addPropertyReference(builder, "provider", providerConfigBeanName);
        }

        /**
         * Add {@link org.apache.dubbo.config.MonitorConfig} Bean reference
         */
        String monitorConfigBeanName = serviceAnnotationAttributes.getString("monitor");
        if (StringUtils.hasText(monitorConfigBeanName)) {
            addPropertyReference(builder, "monitor", monitorConfigBeanName);
        }

        /**
         * Add {@link org.apache.dubbo.config.ApplicationConfig} Bean reference
         */
        String applicationConfigBeanName = serviceAnnotationAttributes.getString("application");
        if (StringUtils.hasText(applicationConfigBeanName)) {
            addPropertyReference(builder, "application", applicationConfigBeanName);
        }

        /**
         * Add {@link org.apache.dubbo.config.ModuleConfig} Bean reference
         */
        String moduleConfigBeanName = serviceAnnotationAttributes.getString("module");
        if (StringUtils.hasText(moduleConfigBeanName)) {
            addPropertyReference(builder, "module", moduleConfigBeanName);
        }


        /**
         * Add {@link org.apache.dubbo.config.RegistryConfig} Bean reference
         */
        String[] registryConfigBeanNames = serviceAnnotationAttributes.getStringArray("registry");

        List<RuntimeBeanReference> registryRuntimeBeanReferences = toRuntimeBeanReferences(registryConfigBeanNames);

        if (!registryRuntimeBeanReferences.isEmpty()) {
            builder.addPropertyValue("registries", registryRuntimeBeanReferences);
        }

        /**
         * Add {@link org.apache.dubbo.config.ProtocolConfig} Bean reference
         */
        String[] protocolConfigBeanNames = serviceAnnotationAttributes.getStringArray("protocol");

        List<RuntimeBeanReference> protocolRuntimeBeanReferences = toRuntimeBeanReferences(protocolConfigBeanNames);

        if (!protocolRuntimeBeanReferences.isEmpty()) {
            builder.addPropertyValue("protocols", protocolRuntimeBeanReferences);
        }
        return builder.getBeanDefinition();
    }

我们来具体看看ServiceBean类定义

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
        ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
        ApplicationEventPublisherAware {

其中实现了监听接口,监听一个spring上下文刷新事件。我们看看监听器实现

 @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }

里面进行export进行服务暴露。我们来看看如何服务暴露,源码跟进去一看究竟。
两步:1、调用父类ServiceConfig的export; 2、发布一个ServiceBeanExportedEvent事件

@Override
    public void export() {
        super.export();
        // Publish ServiceBeanExportedEvent
        publishExportEvent();
    }

ServiceConfig的export方法的关键就是调用checkAndUpdateSubConfigs、doExport方法。

 public synchronized void export() {
 		// 一些列检查并启动注册中心
        checkAndUpdateSubConfigs();

        if (!shouldExport()) {
            return;
        }
		// 是否延时暴露
        if (shouldDelay()) {
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
        	// 暴露
            doExport();
        }
    }
protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
        }
        if (exported) {
            return;
        }
        exported = true;

        if (StringUtils.isEmpty(path)) {
            path = interfaceName;
        }
        doExportUrls();
    }

前面都是做一些校验,真正实现在doExportUrls方法上。这个方法主要做的是遍历RegistryConfig集合registries,这个属性的就是注册中心。得到List registryURLs并配合协议,执行protocoldoExportUrlsFor1Protocol(protocolConfig, registryURLs)完成暴露

由于具体暴露也是相对有点复杂,所以另起篇幅,详细分析export具体实现。我们先暂时有这么个宏观理解。

本文就到此结束了,我们下文见,谢谢
github: honey开源系列组件作者

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊杰eboy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值