1. 程式人生 > >Dubbo 對配置檔案的解析

Dubbo 對配置檔案的解析

注意:本文所述均為dubbox,因dubbo 好長時間沒有維護了,dubbox的gitHub傳送門

dubbox介紹

以下引用自官方介紹

Dubbox當前的主要功能
支援REST風格遠端呼叫(HTTP + JSON/XML):基於非常成熟的JBoss RestEasy框架,在dubbo中實現了REST風格(HTTP + JSON/XML)的遠端呼叫,以顯著簡化企業內部的跨語言互動,同時顯著簡化企業對外的Open API、無線API甚至AJAX服務端等等的開發。事實上,這個REST呼叫也使得Dubbo可以對當今特別流行的“微服務”架構提供基礎性支援。 另外,REST呼叫也達到了比較高的效能,在基準測試下,HTTP + JSON與Dubbo 2.x預設的RPC協議(即TCP + Hessian2二進位制序列化)之間只有1.5倍左右的差距,詳見文件中的基準測試報告。
支援基於Kryo和FST的Java高效序列化實現:

基於當今比較知名的Kryo和FST高效能序列化庫,為Dubbo預設的RPC協議新增新的序列化實現,並優化調整了其序列化體系,比較顯著的提高了Dubbo RPC的效能,詳見文件中的基準測試報告。
支援基於Jackson的JSON序列化:基於業界應用最廣泛的Jackson序列化庫,為Dubbo預設的RPC協議新增新的JSON序列化實現。
支援基於嵌入式Tomcat的HTTP remoting體系:基於嵌入式tomcat實現dubbo的HTTP remoting體系(即dubbo-remoting-http),用以逐步取代Dubbo中舊版本的嵌入式Jetty,可以顯著的提高REST等的遠端呼叫效能,並將Servlet API的支援從2.5升級到3.1。(注:除了REST,dubbo中的WebServices、Hessian、HTTP Invoker等協議都基於這個HTTP remoting體系)。
升級Spring:
將dubbo中Spring由2.x升級到目前最常用的3.x版本,減少版本衝突帶來的麻煩。
升級ZooKeeper客戶端:將dubbo中的zookeeper客戶端升級到最新的版本,以修正老版本中包含的bug。
支援完全基於Java程式碼的Dubbo配置:基於Spring的Java Config,實現完全無XML的純Java程式碼方式來配置dubbo
調整Demo應用:暫時將dubbo的demo應用調整並改寫以主要演示REST功能、Dubbo協議的新序列化方式、基於Java程式碼的Spring配置等等。
修正了dubbo的bug 包括配置、序列化、管理介面等等的bug。

注:dubbox和dubbo 2.x是相容的,沒有改變dubbo的任何已有的功能和配置方式(除了升級了spring之類的版本)

常用的dubbo配置

一個常見的dubbo配置,參照我們專案中的配置

<!-- 提供方應用資訊,用於計算依賴關係-->
    <dubbo:application name="XXXXX" organization="YYYY" owner="ZZZZ" logger="log4j"/>

    <dubbo:registry address="${dubbo.registry}" file="${dubbo.cache.file}" />
    <!--uncomment this if you want to test dubbo's monitor-->
    <!--  
    <dubbo:monitor protocol="registry"/>
    -->
    <!-- 定義dubbo協議-->
    <dubbo:protocol name="dubbo" port="${dubbo.port}" serialization="kryo"/>
    <!-- 定義rest協議 -->
    <dubbo:protocol name="rest" server="servlet" port="${dubbo.rest.port}" contextpath="${dubbo.rest.contextpath}"/>

    <!-- consumer的預設配置,不檢查provider是否存在 -->
    <dubbo:consumer check="false" timeout="${dubbo.timeout}"/>
    <dubbo:provider timeout="${dubbo.timeout}" />
    <!-- 對外暴露的介面 -->
    <import resource="classpath:/config/server/spring-dubbo-provider.xml"/>

對外暴露的服務:

<dubbo:service
        interface="服務的介面類"
        ref="adDirectionalPackageServer" protocol="dubbo" />

服務消費者配置:

<dubbo:reference id="dubboExampleService"
        interface="服務的介面類"
        protocol="dubbo" />

以上是常見的dubbo配置方式,其配置與Spring結合堪稱完美,幾乎沒有程式碼侵入,像使用原生代碼一樣簡單,那麼這種配置是如何讀取的呢??
上文書《Spring原始碼閱讀之-自定義配置的解析》說道,dubbo也是通過spring.handlers 配置檔案宣告的名稱空間解析類,他負責的名稱空間是誰呢?

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

可見其處理的是http://code.alibabatech.com/schema/dubbo 名稱空間,並使用com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler類對其解析,其內部也是通過

 this.registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        this.registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        this.registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        this.registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        this.registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        this.registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        this.registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        this.registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        this.registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        this.registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));

這種方式對不同的元素,註冊不同的解析器(其實是一個類),通過這段程式碼,發現上面配置中定義的元素,基本都有自己的解析器(DubboBeanDefinitionParser,這裡dubbo把節點都通過該類來解析了)
這裡先摘一個重點出來。

this.registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));

其對應構造器(dubbo原始碼上沒有註釋,不過看接下來的parse方法很好理解)

 public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
        this.beanClass = beanClass;
        this.required = required;
    }

之前已經有文章說明了,其實針對自定義名稱空間的bean解析會呼叫BeanDefinitionParser中的parse方法。DubboBeanDefinitionParser類中的parse方法會呼叫如下的parse方法。這裡就很好理解了,因為建立beanDefinition物件,其設定的beanDefinition物件的beanClass為構造器第一個引數。結合起來即:dubbo將對應的節點內容,解析給對應的Class維護,下面可以總結配置節點和類例項的對應關係。

 @SuppressWarnings("unchecked")
    private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
        String id = element.getAttribute("id");
        if ((id == null || id.length() == 0) && required) {
            //***省略,生成Bean Id的過程**//
        }
        if (id != null && id.length() > 0) {
            if (parserContext.getRegistry().containsBeanDefinition(id))  {
                throw new IllegalStateException("Duplicate spring bean id " + id);
            }
            // 看這裡,對beanDefinition的註冊操作。
            parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("id", id);
        }
        if (ProtocolConfig.class.equals(beanClass)) {
            //對應節點的處理,不關注
        } else if (ServiceBean.class.equals(beanClass)) {
            //對應節點的處理,不關注
        } else if (ProviderConfig.class.equals(beanClass)) {
            //該方法內部會呼叫parse方法,即每個service方法會重新呼叫這個parse
            parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
        } else if (ConsumerConfig.class.equals(beanClass)) {
            parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
        }
        Set<String> props = new HashSet<String>();
        ManagedMap parameters = null;
        for (Method setter : beanClass.getMethods()) {
            String name = setter.getName();
            if (name.length() > 3 && name.startsWith("set")
                    && Modifier.isPublic(setter.getModifiers())
                    && setter.getParameterTypes().length == 1) {
                Class<?> type = setter.getParameterTypes()[0];
                String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");
                props.add(property);
                Method getter = null;
                try {
                    getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);
                } catch (NoSuchMethodException e) {
                    try {
                        getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]);
                    } catch (NoSuchMethodException e2) {
                    }
                }
                if (getter == null 
                        || ! Modifier.isPublic(getter.getModifiers())
                        || ! type.equals(getter.getReturnType())) {
                    continue;
                }
                //***呼叫相關class的getter,setter方法,設定bean的屬性值****//
                beanDefinition.getPropertyValues().addPropertyValue(property, reference);
            }
        }
        NamedNodeMap attributes = element.getAttributes();
        int len = attributes.getLength();
        for (int i = 0; i < len; i++) {
            Node node = attributes.item(i);
            String name = node.getLocalName();
            if (! props.contains(name)) {
                if (parameters == null) {
                    parameters = new ManagedMap();
                }
                String value = node.getNodeValue();
                parameters.put(name, new TypedStringValue(value, String.class));
            }
        }
        if (parameters != null) {
            beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
        }
        return beanDefinition;
    }

對應配置節點與類物件的配置關係。

application->com.alibaba.dubbo.configcom.alibaba.dubbo.config.ApplicationConfig
module->com.alibaba.dubbo.config.ModuleConfig
registry->com.alibaba.dubbo.config.RegistryConfig
monitor->com.alibaba.dubbo.config.MonitorConfig
provider->com.alibaba.dubbo.config.ProviderConfig
consumer->com.alibaba.dubbo.config.ConsumerConfig
protocol->com.alibaba.dubbo.config.ProtocolConfig
service->com.alibaba.dubbo.config.ServiceBean
reference->com.alibaba.dubbo.config.ReferenceBean
annotation->com.alibaba.dubbo.config.AnnotationBean

類物件具體的屬性值含義和節點配置方法,可參看一篇博文,