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閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373