Dubbo源码分析(二)Dubbo是从哪里初始化的?

前言

上一节,我们通过与Spring集成的实例,把Dubbo项目跑了起来。但是Dubbo项目是怎么运行起来的呢?它的入口在哪里?

在官网上有这么一句话:Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展进行加载。

一、Spring的初始化

1、解析配置文件

不知诸位是否还有印象,Spring初始化的流程是什么样的呢?如果有不了解的同学,那你可以先看看笔者的往期文章:Spring源码分析(一)Spring的初始化和XML解析

  • 加载配置文件
  • 封装成Resource对象,然后解析为Document对象
  • 根据Document节点信息,封装Bean对象,并注册

其中,封装Bean信息,就是调用parseBeanDefinitions处理配置文件中的节点信息。

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }
}

2、查找命名空间

上面的方法里面,大部分时候会走到parseCustomElement(ele); 它通过获取配置文件中的namespaceUri,来获得NamespaceHandler,然后调用其parse方法,完成对Bean的注册。

public class BeanDefinitionParserDelegate {

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        //在Dubbo中,namespaceUri就是http://dubbo.apache.org/schema/dubbo
        String namespaceUri = getNamespaceURI(ele);
        //根据命名空间获取处理类
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        //调用方法,完成对Bean的加载和注册
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }
}

那么,问题的关键就变成了:如何来一个确定NamespaceHandler?

在Spring中,它会通过AppClassLoader加载所有的META-INF/spring.handlers文件,转换成Map<String, Object> handlerMappings,那么在Dubbo源代码中,就有一个这样的文件。内容为:

http://dubbo.apache.org/schema/dubbo=
com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
http://code.alibabatech.com/schema/dubbo=
com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

很显然,namespaceUri为http://dubbo.apache.org/schema/dubbo就对应com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler处理类。

然后Spring会通过反射,实例化DubboNamespaceHandler对象,调用其初始化方法init()

NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);

初始化之后,返回NamespaceHandler对象,调用parse()来处理Bean。

3、DubboNamespaceHandler

在DubboNamespaceHandler的初始化方法中,Dubbo为每个节点注册了相同的处理类DubboBeanDefinitionParser,但需要注意,它们的beanClass不同。

public class DubboNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }
}

所以,在调用handler.parse()方法时,实际调用的是父类的方法。父类方法中,通过配置文件节点的名称,找到对应的处理类,实际调用parse。那么在Dubbo中,大部分调用到的就是DubboBeanDefinitionParser.parse()

public abstract class NamespaceHandlerSupport implements NamespaceHandler {

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        return findParserForElement(element, parserContext).parse(element, parserContext);
    }
    
    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        //获取配置文件中的节点名称
        String localName = parserContext.getDelegate().getLocalName(element);
        //找到对应的处理类返回
        BeanDefinitionParser parser = this.parsers.get(localName);
        return parser;
    }
}

看完这个过程,我们再回到开头引用的官网上那句话,就已经明白了。Dubbo就是通过这种方式进行加载的。

二、加载Bean

上面我们看到Spring已经扫描到Dubbo的配置文件,接下来就是解析并构建BeanDefinition。

这块代码比较长,因为它是把所有的节点都放在一个类来处理的,依靠beanClass来确定当前处理的是哪个节点,所以里面充斥着大量的ie else判断。

它的作用就是,把配置文件中的每一个标签,封装成BeanDefinition对象,然后处理这个对象的属性和方法,最后注册到容器中,等待Spring在实例化的时候遍历它们。

我们以上一章节生产者端的配置文件为例,它们解析之后,都会封装为BeanDefinition对象,放入beanFactory。

dubbo_producer1=Root bean: class [com.alibaba.dubbo.config.ApplicationConfig]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, 

dubbo=Root bean: class [com.alibaba.dubbo.config.ProtocolConfig]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, 

com.alibaba.dubbo.config.RegistryConfig=Root bean: class [com.alibaba.dubbo.config.RegistryConfig]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, 

com.viewscenes.netsupervisor.service.InfoUserService=Root bean: class [com.alibaba.dubbo.config.spring.ServiceBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null, 

infoUserService=Generic bean: class [com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [dubbo_provider1.xml]

三、Bean的实例化

上一步从配置文件中读取信息,封装成BeanDefinition对象之后,Spring要循环这些Bean,对它们进行实例化和依赖注入。关于这一块知识的具体内容,请参考笔者往期文章:Spring源码分析(二)bean的实例化和IOC依赖注入

回到Dubbo上来说,配置的每一个节点都对应一个处理类。

dubbo:application---------对应--------ApplicationConfig.class
dubbo:registry---------对应--------RegistryConfig.class
dubbo:protocol---------对应--------ProtocolConfig.class
dubbo:service---------对应--------ServiceBean.class

显然,在Spring进行实例化和依赖注入的时候,势必会调用到这些类的方法。而在这些业务方法里,Dubbo就激活了整个框架的各个部件。

我们以dubbo:service为例,它对应的是ServiceBean,它实现了Spring中的不同接口,就是在Spring Bean的不同时期调用方法,最后完成Dubbo的服务暴露。

package com.alibaba.dubbo.config.spring;

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

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        //......
    }
    public void onApplicationEvent(ContextRefreshedEvent event) {
        //服务暴露
        export();  
    }
    public void afterPropertiesSet() throws Exception {
        //类初始化方法
        //......
    }
}

那么,其他的配置节点也都是一样,就是Spring在实例化Bean的时候调用到Dubbo里的代码,完成它们各自的使命。

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

推荐阅读更多精彩内容