Dubbo: 在springboot中的啟動(dòng)過(guò)程

版本

  • dubbo: 2.7.8
  • springboot 2.3.2.RELEASE

SpringBoot中dubbo啟動(dòng)過(guò)程

1. @DubboService注解的bean是如何被注冊(cè)到容器中的

SpringBoot在啟動(dòng)時(shí),通過(guò)ConfigurationClassPostProcessor.postProcessBeanFactory完成對(duì)依賴(lài)jar包中XxAutopConfiguration類(lèi)的注冊(cè),自然DubboAutoConfiguration也會(huì)被注冊(cè)到容器內(nèi)部。

DubboAutoConfiguration中沛慢,定義了一個(gè)ServiceClassPostProcessor 确丢,同樣會(huì)被注冊(cè)到容器內(nèi)泥畅。

@Configuration
@AutoConfigureAfter({DubboRelaxedBindingAutoConfiguration.class})
@EnableConfigurationProperties({DubboConfigurationProperties.class})
@EnableDubboConfig
public class DubboAutoConfiguration implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor {
    public DubboAutoConfiguration() {
    }

    @ConditionalOnProperty(
        prefix = "dubbo.scan.",
        name = {"base-packages"}
    )
    @ConditionalOnBean(
        name = {"dubbo-service-class-base-packages"}
    )
    @Bean
    public ServiceClassPostProcessor serviceClassPostProcessor(@Qualifier("dubbo-service-class-base-packages") Set<String> packagesToScan) {
        return new ServiceClassPostProcessor(packagesToScan);
    }

ServiceClassPostProcessor 實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor接口丁鹉,同樣他也是一個(gè)BeanFactoryPostProcessor瓜贾。

public class ServiceClassPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware {

在SpringBoot刷新容器胆敞,調(diào)用所有BeanFactoryPostProcessors時(shí)着帽,對(duì)BeanDefinitionRegistryPostProcessor,會(huì)去調(diào)用其postProcessBeanDefinitionRegistry方法移层。

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 注冊(cè)監(jiān)聽(tīng)器仍翰,容器啟動(dòng)各個(gè)階段向監(jiān)聽(tīng)器發(fā)送消息
        BeanRegistrar.registerInfrastructureBean(registry, "dubboBootstrapApplicationListener", DubboBootstrapApplicationListener.class);
        Set<String> resolvedPackagesToScan = this.resolvePackagesToScan(this.packagesToScan);
        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
            // 從待掃描的包中獲取ServiceBean,然后注冊(cè)到容器
            this.registerServiceBeans(resolvedPackagesToScan, registry);
        } else if (this.logger.isWarnEnabled()) {
            this.logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
        }

    }
    private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
        DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, this.environment, this.resourceLoader);
        BeanNameGenerator beanNameGenerator = this.resolveBeanNameGenerator(registry);
        scanner.setBeanNameGenerator(beanNameGenerator);
        serviceAnnotationTypes.forEach((annotationType) -> {
            scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
        });
        Iterator var5 = packagesToScan.iterator();

        // 獲取迭代器观话,遍歷待掃描的packages予借,獲取ServiceBean信息,封裝beanDefinition并注冊(cè)到IOC容器
        while(true) {
            while(var5.hasNext()) {
                String packageToScan = (String)var5.next();
                scanner.scan(new String[]{packageToScan});
                Set<BeanDefinitionHolder> beanDefinitionHolders = this.findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
                if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
                    Iterator var8 = beanDefinitionHolders.iterator();

                    while(var8.hasNext()) {
                        BeanDefinitionHolder beanDefinitionHolder = (BeanDefinitionHolder)var8.next();
                        this.registerServiceBean(beanDefinitionHolder, registry, scanner);
                    }

                    if (this.logger.isInfoEnabled()) {
                        this.logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " + beanDefinitionHolders + " } were scanned under package[" + packageToScan + "]");
                    }
                } else if (this.logger.isWarnEnabled()) {
                    this.logger.warn("No Spring Bean annotating Dubbo's @Service was found under package[" + packageToScan + "]");
                }
            }

            return;
        }

至此,便將ServiceBean注冊(cè)進(jìn)了Spring IOC容器。至于對(duì)象和代理對(duì)象的創(chuàng)建灵迫,那是后話了秦叛。

2. DubboService是如何對(duì)外暴露其url的

我們知道DubboService會(huì)被注冊(cè)到注冊(cè)中心,最終的結(jié)果是:將服務(wù)名瀑粥、服務(wù)對(duì)外暴露的url等信息通過(guò)網(wǎng)絡(luò)請(qǐng)求發(fā)送到注冊(cè)中心挣跋,那么對(duì)外暴露的時(shí)機(jī)是什么時(shí)候?它又是如何做到這件事情的狞换?
關(guān)于時(shí)機(jī)避咆,它應(yīng)該在容器刷新完成之后將所有DubboService對(duì)外暴露,那么如何感知到容器刷新呢修噪?SpringBoot中可以注冊(cè)listener查库,容器開(kāi)始啟動(dòng)、啟動(dòng)完成等事件會(huì)通知注冊(cè)進(jìn)來(lái)的listener黄琼。
ServiceClassPostProcessor 除了向容器注冊(cè)ServiceBean之外樊销,還注冊(cè)了一個(gè)監(jiān)聽(tīng)器:DubboBootstrapApplicationListener,當(dāng)感知到容器刷新完成和關(guān)閉事件時(shí),做出相應(yīng)處理适荣,在這里關(guān)注刷新完成該如何處理现柠。

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

啟動(dòng)DubboBootStrap

    public DubboBootstrap start() {
            // ... 省略諸多初始化代碼

            // 服務(wù)暴露
            this.exportServices();
            if (!this.isOnlyRegisterProvider() || this.hasExportedServices()) {
                this.exportMetadataService();
                this.registerServiceInstance();
            }

           // 服務(wù)引用
            this.referServices();
            if (this.asyncExportingFutures.size() > 0) {
                (new Thread(() -> {
                    try {
                        this.awaitFinish();
                    } catch (Exception var2) {
                        this.logger.warn(NAME + " exportAsync occurred an exception.");
                    }

                    this.ready.set(true);
                    if (this.logger.isInfoEnabled()) {
                        this.logger.info(NAME + " is ready.");
                    }

                })).start();
            } else {
                this.ready.set(true);
                if (this.logger.isInfoEnabled()) {
                    this.logger.info(NAME + " is ready.");
                }
            }
            // log ... 
        }
        return this;
    }
    private void exportServices() {
        this.configManager.getServices().forEach((sc) -> {
            ServiceConfig serviceConfig = (ServiceConfig)sc;
            serviceConfig.setBootstrap(this);
            if (this.exportAsync) {
                ExecutorService executor = this.executorRepository.getServiceExporterExecutor();
                Future<?> future = executor.submit(() -> {
                    sc.export();
                    this.exportedServices.add(sc);
                });
                this.asyncExportingFutures.add(future);
            } else {
                sc.export();
                this.exportedServices.add(sc);
            }
        });
    }

至于Service如何暴露:如下圖

export過(guò)程

參考:https://mergades.blog.csdn.net/article/details/109338146
上面注冊(cè)的ServiceBean實(shí)際上就繼承了ServiceConfig院领。
配合下圖右半部分:
DubboBootStrap在exportServices中獲取ServiceConfigs弛矛。迭代每個(gè)ServiceConfig在export的時(shí)候創(chuàng)建URL, 通過(guò)代理工廠創(chuàng)建invoker,最后由Protocol完成導(dǎo)出。在導(dǎo)出時(shí)比然,分別要向本地和注冊(cè)中心分別暴露丈氓。本地導(dǎo)出時(shí),通過(guò)proxyFactory獲取invoker强法,然后由InJVMProtocol完成導(dǎo)出工作万俗,做這件事情的目的是:有時(shí),一個(gè)服務(wù)既是provider饮怯,又是consumer闰歪。如果進(jìn)程內(nèi)調(diào)用該服務(wù),那么它不應(yīng)該像其他進(jìn)程一樣通過(guò)網(wǎng)絡(luò)請(qǐng)求來(lái)進(jìn)行調(diào)用蓖墅,而是在進(jìn)程內(nèi)通信库倘,減少I(mǎi)O帶來(lái)的開(kāi)銷(xiāo)。遠(yuǎn)程暴露就比較容易理解论矾,根據(jù)invoker的url做encode教翩,然后想注冊(cè)中心發(fā)起請(qǐng)求。
image.png

最后服務(wù)暴露到底對(duì)外暴露了個(gè)啥呢贪壳?其實(shí)就是invoker的url饱亿,當(dāng)consumer發(fā)起服務(wù)調(diào)用的時(shí)候,發(fā)起請(qǐng)求,當(dāng)provider接收到請(qǐng)求之后彪笼,將請(qǐng)求攜帶的信息(接口钻注、方法名、參數(shù)數(shù)組...)封裝為Invocation對(duì)象配猫,分派到相應(yīng)的invoker队寇,通過(guò)invoker.invoke(invocation)完成調(diào)用。
另外敖丙的文章也還不錯(cuò)章姓,Dubbo系列之服務(wù)暴露過(guò)程

3. 服務(wù)引入及遠(yuǎn)程調(diào)用

從springboot解析@DubboReference開(kāi)始講起佳遣。
DubboAutoConfiguration被@EnableDubboConfig注解,通過(guò)該注解引入了DubboConfigConfigurationRegistrar類(lèi)凡伊。

@EnableDubboConfig
...
@Import({DubboConfigConfigurationRegistrar.class})

DubboConfigConfigurationRegistrar::registerBeanDefinitions(args)

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //...省略一些代碼
        DubboBeanUtils.registerCommonBeans(registry);
    }
public interface DubboBeanUtils {
    static void registerCommonBeans(BeanDefinitionRegistry registry) {
        BeanRegistrar.registerInfrastructureBean(registry, "referenceAnnotationBeanPostProcessor", ReferenceAnnotationBeanPostProcessor.class);
        // ... 省略
    }
}

ReferenceAnnotationBeanPostProcessor是一個(gè)InstantiationAwareBeanPostProcessorAdapter零渐,在對(duì)象實(shí)例化后,填充屬性中系忙,會(huì)調(diào)用其postProcessPropertyValues

public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor implements ApplicationContextAware {
    // ... 省略

    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
        InjectionMetadata metadata = this.findInjectionMetadata(beanName, bean.getClass(), pvs);

        try {
            // 這里的metadata就是AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement對(duì)象
            metadata.inject(bean, beanName, pvs);
            return pvs;
        } catch (BeanCreationException var7) {
            throw var7;
        } catch (Throwable var8) {
            throw new BeanCreationException(beanName, "Injection of @" + this.getAnnotationType().getSimpleName() + " dependencies is failed", var8);
        }
    }
}

    public class AnnotatedFieldElement extends InjectedElement {
        // ...

        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
            Class<?> injectedType = this.resolveInjectedType(bean, this.field);
            Object injectedObject = AbstractAnnotationBeanPostProcessor.this.getInjectedObject(this.attributes, bean, beanName, injectedType, this);
            ReflectionUtils.makeAccessible(this.field);
            this.field.set(bean, injectedObject);
        }
}

最終到達(dá)referenceBean.get()方法诵盼,返回代理對(duì)象,并注入到目標(biāo)對(duì)象的field银还。

    protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType, InjectedElement injectedElement) throws Exception {
        String referencedBeanName = this.buildReferencedBeanName(attributes, injectedType);
        String referenceBeanName = this.getReferenceBeanName(attributes, injectedType);
        ReferenceBean referenceBean = this.buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
        boolean localServiceBean = this.isLocalServiceBean(referencedBeanName, referenceBean, attributes);
        this.prepareReferenceBean(referencedBeanName, referenceBean, localServiceBean);
        this.registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);
        this.cacheInjectedReferenceBean(referenceBean, injectedElement);
        return referenceBean.get();
    }

如果拋開(kāi)SpringBoot如何注入被@DubboReference注解的Bean风宁,可以說(shuō)referenceBean.get()就是服務(wù)引用的入口。

    // ... 省略諸多代碼
    private transient volatile T ref;
    // 線程安全單例
    public synchronized T get() {
        if (this.destroyed) {
            throw new IllegalStateException("The invoker of ReferenceConfig(" + this.url + ") has already destroyed!");
        } else {
            if (this.ref == null) {
                this.init();
            }

            return this.ref;
        }
    }
 // ...
}

后面的事情就是Dubbo內(nèi)部要完成的了蛹疯,大概過(guò)程如這樣:

image.png

ref:https://zhuanlan.zhihu.com/p/224938929

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末戒财,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子捺弦,更是在濱河造成了極大的恐慌饮寞,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件列吼,死亡現(xiàn)場(chǎng)離奇詭異幽崩,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)寞钥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)慌申,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人理郑,你說(shuō)我怎么就攤上這事蹄溉。” “怎么了香浩?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵类缤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我邻吭,道長(zhǎng)餐弱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮膏蚓,結(jié)果婚禮上瓢谢,老公的妹妹穿的比我還像新娘。我一直安慰自己驮瞧,他們只是感情好氓扛,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著论笔,像睡著了一般采郎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狂魔,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天蒜埋,我揣著相機(jī)與錄音,去河邊找鬼最楷。 笑死整份,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的籽孙。 我是一名探鬼主播烈评,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼犯建!你這毒婦竟也來(lái)了讲冠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤胎挎,失蹤者是張志新(化名)和其女友劉穎沟启,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體犹菇,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年芽卿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了揭芍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卸例,死狀恐怖称杨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情筷转,我是刑警寧澤姑原,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站呜舒,受9級(jí)特大地震影響锭汛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一唤殴、第九天 我趴在偏房一處隱蔽的房頂上張望般婆。 院中可真熱鬧,春花似錦朵逝、人聲如沸蔚袍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)啤咽。三九已至,卻和暖如春渠脉,著一層夾襖步出監(jiān)牢的瞬間闰蚕,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工连舍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留没陡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓索赏,卻偏偏與公主長(zhǎng)得像盼玄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子潜腻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容