Dubbo和Spring集成的原理

使用Dubbo最方便的地方在于它可以和Spring非常方便的集成,实际上,Dubbo对于配置的优化,也是随着Spring一同发展的,从最早的XML形式到后来的注解方式以及自动装配,都是在不断地简化开发过程来提高开发效率。

在Spring Boot集成Dubbo时,服务发布主要有以下几个步骤:

  • 添加dubbo-spring-boot-starter依赖
  • 定义@org.apache.dubbo.config.annotation.Service注解
  • 声明@DubboComponentScan,用于扫描@Service注解

其实不难猜出,Dubbo中的@Service注解和Spring中提供的@Service注解功能类似,用于实现Dubbo服务的暴露,与它相对应的时@Reference,它的作用类似于Spring中的@Autowired注解。

而@DubboComponentScan和Spring中的@ComponentScan作用类似,用于扫描@Service、@Reference等注解。

@DubboComponentScan注解解析

DubboComponentScan注解的定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
    String[] value() default {};
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
}

这个注解主要通过@Import导入一个DubboComponentScanRegistrar类。DubboComponentScanRegistrar实现了ImportBeanDefinitionRegistrar接口,并且重写了registerBeanDefinitions方法。在registerBeanDefinitions方法中主要做了以下几件事:

  • 获取扫描包的路径,默认扫描当前配置类所在的包
  • 注册@Service注解的解析类
  • 注册@Reference注解的解析类
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void refisterBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
        registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
        registerReferenceAnnotationBeanPostProcessor(registry);
    }
    ......
}

ImportBeanDefinitionRegistrar是Spring提供的一种动态注入Bean的机制,和ImportSelector接口的功能类似,在refisterBeanDefinitions方法中,主要会实例化一些BeanDefinition并且注入到Spring IoC容器中。

我们继续看registerServiceAnnotationBeanPostProcessor方法,逻辑比较简单,就是把SerficeAnnotationBeanPostProcessor注册到容器:

private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
        // 构建BeanDefinitionBuilder
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);
        builder.addConstructorArgValue(packagesToScan);
        builder.setRole(2);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        // 把BeanDefinition注册到IoC容器中
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
    }

所以,@DubboComponentScan只是诸如一个ServiceAnnotationBeanPostProcessor和一个ReferenceAnnotationBeanPostProcessor对象,那Dubbo服务的注解@Service是如何解析的呢?

其实,主要逻辑就在两个类中,ServiceAnnotationBeanPostProcessor用于解析@Service注解,ReferenceAnnotationBeanPostProcessor用于解析@Reference注解。

ServiceAnnotationBeanPostProcessor

ServiceAnnotationBeanPostProcessor类的定义如下,它的核心逻辑就是解析@Service注解

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

ServiceAnnotationBeanPostProcessor实现了4个接口,EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware这三个接口比较好理解,我们重点看一下BeanDefinitionRegistryPostProcessor。

BeanDefinitionRegistryPostProcessor接口继承自BeanFactoryPostProcessor,是一种比较特殊的BeanFactoryPostProcessor。BeanDefinitionRegistryPostProcessor中的postProcessBeanDefinitionRegistry方法可以让我们实现自定义的注册Bean定义的逻辑。该方法主要做了以下几件事:

  • 调用registerBeans注册DubboBootstrapApplicationListener类
  • 通过resolvePackagesToScan对packagesToScan参数进行去空格处理,并把配置文件中配置的扫描参数也一起处理。
  • 调用registerServiceBeans完成Bean的注册。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        AnnotatedBeanDefinitionRegistryUtils.registerBeans(registry, new Class[]{DubboBootstrapApplicationListener.class});
        Set<String> resolvedPackagesToScan = this.resolvePackagesToScan(this.packagesToScan);
        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
            this.registerServiceBeans(resolvedPackagesToScan, registry);
        } else if (this.logger.isWarnEnabled()) {
            this.logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
        }

    }

这个方法的核心逻辑都在registerServiceBeans这个方法中,这个方法会查找需要扫描的指定包里面有@Service注解的类并将其注册成Bean。

  • 定义DubboClassPathBeanDefinitionScanner扫描对象,扫描指定路径下的类,将符合条件的类装配到IoC容器中。
  • BeanNameGenerator是Beans体系中比较重要的一个组件,会通过一定的算法计算出需要装配的Bean的name。
  • addIncludeFilter设置Scan的过滤条件,只扫描@Service注解修饰的类。
  • 遍历指定的包,通过findServiceBeanDefinitionHolders查找@Service注解修饰的类。
  • 通过registerServiceBean完成Bean的注册。
/**
     * Registers Beans whose classes was annotated {@link Service}
     *
     * @param packagesToScan The base packages to scan
     * @param registry       {@link BeanDefinitionRegistry}
     */
    private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

        DubboClassPathBeanDefinitionScanner scanner =
                new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);

        BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);

        scanner.setBeanNameGenerator(beanNameGenerator);

        scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));

        for (String packageToScan : packagesToScan) {

            // Registers @Service Bean first
            scanner.scan(packageToScan);

            // Finds all BeanDefinitionHolders of @Service whether @ComponentScan scans or not.
            Set<BeanDefinitionHolder> beanDefinitionHolders =
                    findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);

            if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {

                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 + "]");
                }

            }

        }

    }

上面的代码主要作用就是通过扫描指定路径下添加了@Service注解的类,通过registerServiceBean来注册ServiceBean,整体来看,Dubbo的注解扫描进行服务发布的过程,实际上就是基于Spring的扩展。

继续分析registerServiceBean方法:

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

        Class<?> beanClass = resolveClass(beanDefinitionHolder);

        Service service = findAnnotation(beanClass, Service.class);

        Class<?> interfaceClass = resolveServiceInterfaceClass(beanClass, service);

        String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();

        AbstractBeanDefinition serviceBeanDefinition =
                buildServiceBeanDefinition(service, interfaceClass, annotatedServiceBeanName);

        // ServiceBean Bean name
        String beanName = generateServiceBeanName(service, interfaceClass, annotatedServiceBeanName);

        if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { // check duplicated candidate bean
            registry.registerBeanDefinition(beanName, serviceBeanDefinition);

            if (logger.isInfoEnabled()) {
                logger.info("The BeanDefinition[" + serviceBeanDefinition +
                        "] of ServiceBean has been registered with name : " + beanName);
            }

        } else {

            if (logger.isWarnEnabled()) {
                logger.warn("The Duplicated BeanDefinition[" + serviceBeanDefinition +
                        "] of ServiceBean[ bean name : " + beanName +
                        "] was be found , Did @DubboComponentScan scan to same package in many times?");
            }

        }

    }
  • resolveClass获取BeanDefinitionHolder中的Bean
  • findServiceAnnotation方法从beanClass类中找到@Service注解
  • getAnnotationAttributes方法获得注解中的属性,比如loadBalance、cluster等。
  • resolveServiceInterfaceClass方法用于获得beanClass对应的接口定义,其实在@Service(interfaceClass=xxxx.class)注解的声明中也可以声明interfaceClass,注解中声明的优先级最高,如果没有声明该属性,则会从父类中查找。
  • annotatedServiceBeanName代表Bean的名称。
  • buildServiceBeanDefinition用来构造org.apache.dubbo.config.spring.ServiceBean对象,每个Dubbo服务的发布最终都会出现一个ServiceBean。
  • 调用registerBeanDefinition将ServiceBean注入Spring IoC容器中。

从整个方法的分析来看,registerServiceBean方法主要是把一个ServiceBean注入到Spring IoC容器中,比如:

@Service
public class HelloServiceImpl implements IHelloService {
    ......
}

它并不是像普通的Bean注入一样直接将HelloServiceImpl对象的实例注入容器,而是注入一个ServiceBean对象。对于HelloServiceImpl来说,它并不需要把自己注入Spring IoC容器中,而是需要把自己发布到网络上,提供给网络上的服务消费者来访问。那它是怎么发布到网络上的呢?

上面在postProcessBeanDefinitionRegistry方法中注册了DubboBootstrapApplicationListener事件监听Bean。

public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener implements Ordered {
    private final DubboBootstrap dubboBootstrap = DubboBootstrap.getInstance();

    public DubboBootstrapApplicationListener() {
    }

    public void onApplicationContextEvent(ApplicationContextEvent event) {
        if (event instanceof ContextRefreshedEvent) {
            this.onContextRefreshedEvent((ContextRefreshedEvent)event);
        } else if (event instanceof ContextClosedEvent) {
            this.onContextClosedEvent((ContextClosedEvent)event);
        }

    }

    private void onContextRefreshedEvent(ContextRefreshedEvent event) {
        this.dubboBootstrap.start();
    }

    private void onContextClosedEvent(ContextClosedEvent event) {
        this.dubboBootstrap.stop();
    }

    public int getOrder() {
        return 2147483647;
    }
}

当所有的Bean都处理完成之后,Spring IoC会发布一个事件,事件类型为ComtextRefreshedEvent,当触发整个事件时,会调用onContextRefreshedEvent方法。在这个方法中,可以看到Dubbo服务启动的触发机制dubboBootstrap.start()。从这个方法中会进入org.apache.dubbo.config.ServiceConfig类中的export()方法,这个方法会启动一个网络监听,从而实现服务发布。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271