dubbo(九)-dubbo服務(wù)導(dǎo)出詳解-1

介紹

本篇文章,我們來研究一下 Dubbo 導(dǎo)出服務(wù)的過程。Dubbo 服務(wù)導(dǎo)出過程始于 Spring 容器發(fā)布刷新事件,Dubbo 在接收到事件后踏枣,會立即執(zhí)行服務(wù)導(dǎo)出邏輯。整個邏輯大致可分為三個部分:

  • 第一部分是前置工作钙蒙,主要用于檢查參數(shù),組裝 URL间驮。
  • 第二部分是導(dǎo)出服務(wù)躬厌,包含導(dǎo)出服務(wù)到本地 (JVM),和導(dǎo)出服務(wù)到遠(yuǎn)程兩個過程竞帽。
  • 第三部分是向注冊中心注冊服務(wù)扛施,用于服務(wù)發(fā)現(xiàn)。

1. 第一部分- 導(dǎo)出服務(wù)前置工作

Dubbo 導(dǎo)出服務(wù)的入口class是com.alibaba.dubbo.config.spring.ServiceBean屹篓,這個類是spring通過解析<dubbo:service>節(jié)點創(chuàng)建的單例Bean疙渣,每一個<dubbo:service>都會創(chuàng)建一個ServiceBeanServiceBean的類圖如下:

ServiceBean

ServiceBean 的聲明為:

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

可以看出ServiceBean實現(xiàn)了spring的InitializingBean堆巧、DisposableBean妄荔、ApplicationContextAware泼菌、ApplicationListenerBeanNameAware接口,關(guān)于這幾個接口的使用在前面已經(jīng)提到了啦租,這里不再說明哗伯。

Dubbo 支持兩種服務(wù)導(dǎo)出方式,分別延遲導(dǎo)出和立即導(dǎo)出篷角。延遲導(dǎo)出的入口是 ServiceBeanafterPropertiesSet 方法焊刹,立即導(dǎo)出的入口是 ServiceBeanonApplicationEvent方法。

我們先看一下afterPropertiesSet 方法恳蹲,這個方法會在ServiceBean創(chuàng)建前調(diào)用虐块。

    @Override
    @SuppressWarnings({"unchecked", "deprecation"})
    public void afterPropertiesSet() throws Exception {
        // 初始化 provider
        if (getProvider() == null) {
            // 讀取 spring applicationContext 的 ProviderConfig
            Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);
            if (providerConfigMap != null && providerConfigMap.size() > 0) {
                // 讀取 spring applicationContext 的 ProtocolConfig
                Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
                if ((protocolConfigMap == null || protocolConfigMap.size() == 0)
                        && providerConfigMap.size() > 1) {
                    List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
                    for (ProviderConfig config : providerConfigMap.values()) {
                        if (config.isDefault() != null && config.isDefault().booleanValue()) {
                            providerConfigs.add(config);
                        }
                    }
                    if (!providerConfigs.isEmpty()) {
                        setProviders(providerConfigs);
                    }
                } else {
                    ProviderConfig providerConfig = null;
                    for (ProviderConfig config : providerConfigMap.values()) {
                        if (config.isDefault() == null || config.isDefault().booleanValue()) {
                            if (providerConfig != null) {
                                throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);
                            }
                            providerConfig = config;
                        }
                    }
                    if (providerConfig != null) {
                        setProvider(providerConfig);
                    }
                }
            }
        }

        // 初始化 application
        if (getApplication() == null
                && (getProvider() == null || getProvider().getApplication() == null)) {
            // 讀取 spring applicationContext 的 ApplicationConfig
            Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
            if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
                ApplicationConfig applicationConfig = null;
                for (ApplicationConfig config : applicationConfigMap.values()) {
                    if (config.isDefault() == null || config.isDefault().booleanValue()) {
                        if (applicationConfig != null) {
                            throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
                        }
                        applicationConfig = config;
                    }
                }
                if (applicationConfig != null) {
                    setApplication(applicationConfig);
                }
            }
        }

        // 初始化 module
        if (getModule() == null
                && (getProvider() == null || getProvider().getModule() == null)) {
            // 讀取 spring applicationContext 的 ModuleConfig
            Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
            if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
                ModuleConfig moduleConfig = null;
                for (ModuleConfig config : moduleConfigMap.values()) {
                    if (config.isDefault() == null || config.isDefault().booleanValue()) {
                        if (moduleConfig != null) {
                            throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
                        }
                        moduleConfig = config;
                    }
                }
                if (moduleConfig != null) {
                    setModule(moduleConfig);
                }
            }
        }

        // 初始化 Registries
        if ((getRegistries() == null || getRegistries().isEmpty())
                && (getProvider() == null || getProvider().getRegistries() == null || getProvider().getRegistries().isEmpty())
                && (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().isEmpty())) {
            // 讀取 spring applicationContext 的 RegistryConfig
            Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
            if (registryConfigMap != null && registryConfigMap.size() > 0) {
                List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
                for (RegistryConfig config : registryConfigMap.values()) {
                    if (config.isDefault() == null || config.isDefault().booleanValue()) {
                        registryConfigs.add(config);
                    }
                }
                if (registryConfigs != null && !registryConfigs.isEmpty()) {
                    super.setRegistries(registryConfigs);
                }
            }
        }

        // 初始化 Monitor
        if (getMonitor() == null
                && (getProvider() == null || getProvider().getMonitor() == null)
                && (getApplication() == null || getApplication().getMonitor() == null)) {
            // 讀取 spring applicationContext 的 MonitorConfig
            Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
            if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
                MonitorConfig monitorConfig = null;
                for (MonitorConfig config : monitorConfigMap.values()) {
                    if (config.isDefault() == null || config.isDefault().booleanValue()) {
                        if (monitorConfig != null) {
                            throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
                        }
                        monitorConfig = config;
                    }
                }
                if (monitorConfig != null) {
                    setMonitor(monitorConfig);
                }
            }
        }

        // 初始化 Protocols
        if ((getProtocols() == null || getProtocols().isEmpty())
                && (getProvider() == null || getProvider().getProtocols() == null || getProvider().getProtocols().isEmpty())) {
            // 讀取 spring applicationContext 的 ProtocolConfig
            Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
            if (protocolConfigMap != null && protocolConfigMap.size() > 0) {
                List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
                for (ProtocolConfig config : protocolConfigMap.values()) {
                    if (config.isDefault() == null || config.isDefault().booleanValue()) {
                        protocolConfigs.add(config);
                    }
                }
                if (protocolConfigs != null && !protocolConfigs.isEmpty()) {
                    super.setProtocols(protocolConfigs);
                }
            }
        }

        // 初始化 Path
        if (getPath() == null || getPath().length() == 0) {
            if (beanName != null && beanName.length() > 0
                    && getInterface() != null && getInterface().length() > 0
                    && beanName.startsWith(getInterface())) {
                setPath(beanName);
            }
        }

        // 延遲導(dǎo)出,這里不做講解
        if (!isDelay()) {
            export();
        }
    }

afterPropertiesSet檢查ServiceBean的某個屬性(這里的屬性包含如下6個)是否為空嘉蕾,如果為空贺奠,從applicationContext獲取相應(yīng)類型的bean,如果獲取到了荆针,則進(jìn)行相應(yīng)的設(shè)置敞嗡。6個屬性如下:

  • ProviderConfig provider:其實就是看有沒有配置<dubbo:provider>
  • ApplicationConfig application:其實就是看有沒有配置<dubbo:application>
  • ModuleConfig module:其實就是看有沒有配置<dubbo:module>
  • List<RegistryConfig> registries:其實就是看有沒有配置<dubbo:registry>
  • MonitorConfig monitor:其實就是看有沒有配置<dubbo:monitor>
  • List<ProtocolConfig> protocols:其實就是看有沒有配置<dubbo:protocol>
  • String path:服務(wù)名稱

填充完后,有一個重要的方法就是export()航背。這個方法會判斷延遲的時間是否大于0喉悴,如果是,才會執(zhí)行玖媚。這里如果我們沒有設(shè)置延遲箕肃,一個ServiceBean實例就完成了。

接下來我們看看ServiceBean實例完成后今魔,私有屬性的值都是啥:

ServiceBean={
    "beanName":"com.hui.wang.dubbo.learn.api.MyService",
    "supportedApplicationListener":"true",
    "interfaceName":"com.hui.wang.dubbo.learn.api.MyService",
    "ref":interfaceName的實例,
    "path":"com.hui.wang.dubbo.learn.api.MyService",
    "verision":"1.0",
    "application":{
        "name":"provider",
        "owner":"hui.wang"
        "organization":"dubbo-learn",
        "id":"provider"
    },
    registries:[
        "address":"127.0.0.1:2181",
        "protocol":"zookeeper",
        "group":"dubbo-learn",
        "id":"dubbo-provider"
    ],
    "id":"com.hui.wang.dubbo.learn.api.MyService"
}

實際上在創(chuàng)建ServiceBean實例的時候勺像,也會初始化其父類ServiceConfig的靜態(tài)屬性:

    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

這里使用了Adaptive,之前有過講解错森,會生成對應(yīng)的Protocol$Adaptive實例和ProxyFactory$Adaptive實例吟宦。對應(yīng)的源碼如下:
Protocol$Adaptive:


public class Protocol$Adaptive implements Protocol {
    @Override
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    @Override
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public Invoker refer(Class class_, URL uRL) throws RpcException {
        String string;
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
        String string2 = string = uRL2.getProtocol() == null ? "dubbo" : uRL2.getProtocol();
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(").append(uRL2.toString()).append(") use keys([protocol])").toString());
        }
        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
        return protocol.refer(class_, uRL);
    }

    public Exporter export(Invoker invoker) throws RpcException {
        String string;
        if (invoker == null) {
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
        String string2 = string = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(").append(uRL.toString()).append(") use keys([protocol])").toString());
        }
        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
        return protocol.export(invoker);
    }
}

ProxyFactory$Adaptive:


public class ProxyFactory$Adaptive implements ProxyFactory {
    public Invoker getInvoker(Object object, Class class_, URL uRL) throws RpcException {
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
        String string = uRL2.getParameter("proxy", "javassist");
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(").append(uRL2.toString()).append(") use keys([proxy])").toString());
        }
        ProxyFactory proxyFactory = (ProxyFactory) ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(string);
        return proxyFactory.getInvoker(object, class_, uRL);
    }

    public Object getProxy(Invoker invoker, boolean bl) throws RpcException {
        if (invoker == null) {
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
        String string = uRL.getParameter("proxy", "javassist");
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(").append(uRL.toString()).append(") use keys([proxy])").toString());
        }
        ProxyFactory proxyFactory = (ProxyFactory) ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(string);
        return proxyFactory.getProxy(invoker, bl);
    }

    public Object getProxy(Invoker invoker) throws RpcException {
        if (invoker == null) {
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
        String string = uRL.getParameter("proxy", "javassist");
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(").append(uRL.toString()).append(") use keys([proxy])").toString());
        }
        ProxyFactory proxyFactory = (ProxyFactory) ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(string);
        return proxyFactory.getProxy(invoker);
    }
}

到這里,整個ServiceBean的初始化過程已經(jīng)講解完成了涩维。接下來我們看看ServiceBean#onApplicationEvent方法殃姓,改方法會在 Spring 上下文刷新事件時調(diào)用。代碼如下:

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 是否有延遲導(dǎo)出 && 是否已導(dǎo)出 && 是不是已被取消導(dǎo)出
        if (isDelay() && !isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }

這個方法首先會根據(jù)條件決定是否導(dǎo)出服務(wù)瓦阐,比如有些服務(wù)設(shè)置了延時導(dǎo)出蜗侈,那么此時就不應(yīng)該在此處導(dǎo)出。還有一些服務(wù)已經(jīng)被導(dǎo)出了睡蟋,或者當(dāng)前服務(wù)被取消導(dǎo)出了踏幻,此時也不能再次導(dǎo)出相關(guān)服務(wù)。注意這里的 isDelay 方法戳杀,這個方法字面意思是“是否延遲導(dǎo)出服務(wù)”该面,返回 true 表示延遲導(dǎo)出夭苗,false 表示不延遲導(dǎo)出。但是該方法真實意思卻并非如此吆倦,當(dāng)方法返回 true 時听诸,表示無需延遲導(dǎo)出。返回 false 時蚕泽,表示需要延遲導(dǎo)出晌梨。

接下來對服務(wù)導(dǎo)出的前置邏輯進(jìn)行分析。

配置檢查以及 URL 裝配

在導(dǎo)出服務(wù)之前须妻,Dubbo 需要檢查用戶的配置是否合理仔蝌,或者為用戶補(bǔ)充缺省配置。配置檢查完成后荒吏,接下來需要根據(jù)這些配置組裝 URL敛惊。

  1. 檢查配置,本節(jié)我們接著前面的源碼向下分析绰更,前面說過 onApplicationEvent 方法在經(jīng)過一些判斷后瞧挤,會決定是否調(diào)用 export 方法導(dǎo)出服務(wù)。那么下面我們從 export 方法開始進(jìn)行分析儡湾,如下:
    public synchronized void export() {
        if (provider != null) {
            // 獲取 export 和 delay 配置
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        // 如果 export 為 false特恬,則不導(dǎo)出服務(wù)
        if (export != null && !export) {
            return;
        }

        // delay > 0,延時導(dǎo)出服務(wù)
        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        // 立即導(dǎo)出服務(wù)
        } else {
            doExport();
        }
    }

export方法分別對exportdelay配置進(jìn)行檢查徐钠,首先是 export 配置癌刽,這個配置決定了是否導(dǎo)出服務(wù)。有時候我們只是想本地啟動服務(wù)進(jìn)行一些調(diào)試工作尝丐,我們并不希望把本地啟動的服務(wù)暴露出去給別人調(diào)用显拜。此時,我們可通過配置 export 禁止服務(wù)導(dǎo)出爹袁,比如:

<dubbo:provider export="false" />

delay 配置顧名思義远荠,用于延遲導(dǎo)出服務(wù),這個就不分析了失息。

  1. 下面矮台,我們繼續(xù)分析源碼,這次要分析的是 doExport 方法根时。
protected synchronized void doExport() {
        // unexported 和 exported 檢查
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        if (exported) {
            return;
        }
        exported = true;

        // 檢測 interfaceName 是否合法
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }

        // 檢測 provider 是否為空,為空則新建一個辰晕,并通過系統(tǒng)變量為其初始化
        checkDefault();

        // 下面幾個 if 語句用于檢測 provider蛤迎、application 等核心配置類對象是否為空,
        // 若為空含友,則嘗試從其他配置類對象中獲取相應(yīng)的實例替裆。
        if (provider != null) {
            if (application == null) {
                application = provider.getApplication();
            }
            if (module == null) {
                module = provider.getModule();
            }
            if (registries == null) {
                registries = provider.getRegistries();
            }
            if (monitor == null) {
                monitor = provider.getMonitor();
            }
            if (protocols == null) {
                protocols = provider.getProtocols();
            }
        }
        if (module != null) {
            if (registries == null) {
                registries = module.getRegistries();
            }
            if (monitor == null) {
                monitor = module.getMonitor();
            }
        }
        if (application != null) {
            if (registries == null) {
                registries = application.getRegistries();
            }
            if (monitor == null) {
                monitor = application.getMonitor();
            }
        }

        // 檢測 ref 是否為泛化服務(wù)類型
        if (ref instanceof GenericService) {
            // 設(shè)置 interfaceClass 為 GenericService.class
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                // 設(shè)置 generic = "true"
                generic = Boolean.TRUE.toString();
            }
        // ref 非 GenericService 類型
        } else {
            try {
                // 設(shè)置 interfaceClass
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 對 interfaceClass校辩,以及 <dubbo:method> 標(biāo)簽中的必要字段進(jìn)行檢查
            checkInterfaceAndMethods(interfaceClass, methods);
            // 對 ref 合法性進(jìn)行檢測
            checkRef();
            // 設(shè)置 generic = "false"
            generic = Boolean.FALSE.toString();
        }
        // local 和 stub 在功能應(yīng)該是一致的,用于配置本地存根
        if (local != null) {
            if ("true".equals(local)) {
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            try {
                // 獲取本地存根類
                localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 檢測本地存根類是否可賦值給接口類辆童,若不可賦值則會拋出異常宜咒,提醒使用者本地存根類類型不合法
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        if (stub != null) {
            if ("true".equals(stub)) {
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            try {
                stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(stubClass)) {
                throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
        // 檢測各種對象是否為空,為空則新建把鉴,或者拋出異常
        checkApplication();
        checkRegistry();
        checkProtocol();
        appendProperties(this);
        checkStub(interfaceClass);
        checkMock(interfaceClass);
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        // 導(dǎo)出服務(wù)
        doExportUrls();
        // ProviderModel 表示服務(wù)提供者模型故黑,此對象中存儲了與服務(wù)提供者相關(guān)的信息。
        // 比如服務(wù)的配置信息庭砍,服務(wù)實例等场晶。每個被導(dǎo)出的服務(wù)對應(yīng)一個 ProviderModel。
        // ApplicationModel 持有所有的 ProviderModel怠缸。
        ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
        ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    }

以上就是配置檢查的相關(guān)分析诗轻,代碼比較多,需要大家耐心看一下揭北。下面對配置檢查的邏輯進(jìn)行簡單的總結(jié)扳炬,如下:

  1. 檢測 <dubbo:service> 標(biāo)簽的 interface 屬性合法性,不合法則拋出異常
  2. 檢測 ProviderConfig搔体、ApplicationConfig 等核心配置類對象是否為空恨樟,若為空,則嘗試從其他配置類對象中獲取相應(yīng)的實例嫉柴。
  3. 檢測并處理泛化服務(wù)和普通服務(wù)類
  4. 檢測本地存根配置厌杜,并進(jìn)行相應(yīng)的處理
  5. 對 ApplicationConfig、RegistryConfig 等配置類進(jìn)行檢測计螺,為空則嘗試創(chuàng)建夯尽,若無法創(chuàng)建則拋出異常

配置檢查并非本文重點,因此這里不打算對 doExport 方法所調(diào)用的方法進(jìn)行分析(doExportUrls 方法除外)登馒。在這些方法中匙握,除了 appendProperties 方法稍微復(fù)雜一些,其他方法邏輯不是很復(fù)雜陈轿。因此圈纺,大家可自行分析。

  1. doExportUrls方法麦射,Dubbo 允許我們使用不同的協(xié)議導(dǎo)出服務(wù)蛾娶,也允許我們向多個注冊中心注冊服務(wù)。相關(guān)代碼如下:
    private void doExportUrls() {
        // 加載注冊中心鏈接
        List<URL> registryURLs = loadRegistries(true);
        // 遍歷 protocols潜秋,并在每個協(xié)議下導(dǎo)出服務(wù)
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

上面代碼首先是通過 loadRegistries加載注冊中心鏈接蛔琅,然后再遍歷 ProtocolConfig 集合導(dǎo)出每個服務(wù)。并在導(dǎo)出服務(wù)的過程中峻呛,將服務(wù)注冊到注冊中心罗售。下面辜窑,我們先來看一下loadRegistries方法的邏輯。

    protected List<URL> loadRegistries(boolean provider) {
        // 檢測是否存在注冊中心配置類寨躁,不存在則拋出異常
        checkRegistry();
        List<URL> registryList = new ArrayList<URL>();
        if (registries != null && !registries.isEmpty()) {
            for (RegistryConfig config : registries) {
                // 獲取注冊中心address
                String address = config.getAddress();
                if (address == null || address.length() == 0) {
                    // 若 address 為空穆碎,則將其設(shè)為 0.0.0.0
                    address = Constants.ANYHOST_VALUE;
                }
                // 從系統(tǒng)屬性中加載注冊中心地址
                String sysaddress = System.getProperty("dubbo.registry.address");
                // 如果系統(tǒng)屬性中的配置中心地址不為空,將注冊地址設(shè)置為系統(tǒng)屬性配置中的地址
                if (sysaddress != null && sysaddress.length() > 0) {
                    address = sysaddress;
                }
                if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map<String, String> map = new HashMap<String, String>();
                    // 添加 ApplicationConfig 中的字段信息到 map 中
                    appendParameters(map, application);
                    // 添加 RegistryConfig 字段信息到 map 中
                    appendParameters(map, config);
                    // 添加 path职恳、pid所禀,protocol 等信息到 map 中
                    map.put("path", RegistryService.class.getName());
                    map.put("dubbo", Version.getProtocolVersion());
                    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
                    if (ConfigUtils.getPid() > 0) {
                        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
                    }

                    // 配置協(xié)議
                    if (!map.containsKey("protocol")) {
                        if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
                            map.put("protocol", "remote");
                        } else {
                            map.put("protocol", "dubbo");
                        }
                    }
                    // 解析得到 URL 列表,address 可能包含多個注冊中心 ip话肖,
                    // 因此解析得到的是一個 URL 列表
                    List<URL> urls = UrlUtils.parseURLs(address, map);
                    for (URL url : urls) {
                        url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
                        // 將 URL 協(xié)議頭設(shè)置為 registry
                        url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
                        // 通過判斷條件北秽,決定是否添加 url 到 registryList 中,條件如下:
                        // (服務(wù)提供者 && register = true 或 null)
                        //    || (非服務(wù)提供者 && subscribe = true 或 null)
                        if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
                                || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    }

loadRegistries 方法主要包含如下的邏輯:

  1. 檢測是否存在注冊中心配置類最筒,不存在則拋出異常
  2. 構(gòu)建參數(shù)映射集合贺氓,也就是 map
  3. 構(gòu)建注冊中心鏈接列表
  4. 遍歷鏈接列表,并根據(jù)條件決定是否將其添加到 registryList 中

我的dubbo配置如下:

    <dubbo:application name="provider" owner="hui.wang" organization="dubbo-learn"/>

    <dubbo:registry protocol="zookeeper"
                    address="192.168.33.10:2181"
                    id="dubbo-provider"
                    register="true"
                    check="false"
                    group="dubbo-learn"/>


    <dubbo:service interface="com.hui.wang.dubbo.learn.api.MyService"
                   ref="myServiceImpl"
                   version="1.0"
                   registry="dubbo-provider"/>

生成的registryURLs如下:

registry://192.168.33.10:2181/com.alibaba.dubbo.registry.RegistryService?application=provider&check=false&dubbo=2.0.1&group=dubbo-learn&organization=dubbo-learn&owner=hui.wang&pid=87270&register=true&registry=zookeeper&timestamp=1550484891114

配置完registryURLs后床蜘,就是繼續(xù)組裝 導(dǎo)出的URL辙培。下面開始分析doExportUrlsFor1Protocol方法,代碼如下:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    String name = protocolConfig.getName();
    // 如果協(xié)議名為空邢锯,或空串扬蕊,則將協(xié)議名變量設(shè)置為 dubbo
    if (name == null || name.length() == 0) {
        name = "dubbo";
    }

    Map<String, String> map = new HashMap<String, String>();
    // 添加 side、版本丹擎、時間戳以及進(jìn)程號等信息到 map 中
    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
    map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
    if (ConfigUtils.getPid() > 0) {
        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
    }

    // 通過反射將對象的字段信息添加到 map 中
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, provider, Constants.DEFAULT_KEY);
    appendParameters(map, protocolConfig);
    appendParameters(map, this);

    // methods 為 MethodConfig 集合尾抑,MethodConfig 中存儲了 <dubbo:method> 標(biāo)簽的配置信息
    if (methods != null && !methods.isEmpty()) {
        // 這段代碼用于添加 Callback 配置到 map 中,代碼太長蒂培,待會單獨分析
    }

    // 檢測 generic 是否為 "true"再愈,并根據(jù)檢測結(jié)果向 map 中添加不同的信息
    if (ProtocolUtils.isGeneric(generic)) {
        map.put(Constants.GENERIC_KEY, generic);
        map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
    } else {
        String revision = Version.getVersion(interfaceClass, version);
        if (revision != null && revision.length() > 0) {
            map.put("revision", revision);
        }

        // 為接口生成包裹類 Wrapper,Wrapper 中包含了接口的詳細(xì)信息护戳,比如接口方法名數(shù)組翎冲,字段信息等
        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
        // 添加方法名到 map 中,如果包含多個方法名媳荒,則用逗號隔開抗悍,比如 method = init,destroy
        if (methods.length == 0) {
            logger.warn("NO method found in service interface ...");
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            // 將逗號作為分隔符連接方法名,并將連接后的字符串放入 map 中
            map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
        }
    }

    // 添加 token 到 map 中
    if (!ConfigUtils.isEmpty(token)) {
        if (ConfigUtils.isDefault(token)) {
            // 隨機(jī)生成 token
            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
        } else {
            map.put(Constants.TOKEN_KEY, token);
        }
    }
    // 判斷協(xié)議名是否為 injvm
    if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
        protocolConfig.setRegister(false);
        map.put("notify", "false");
    }

    // 獲取上下文路徑
    String contextPath = protocolConfig.getContextpath();
    if ((contextPath == null || contextPath.length() == 0) && provider != null) {
        contextPath = provider.getContextpath();
    }

    // 獲取 host 和 port
    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = this.findConfigedPorts(protocolConfig, name, map);
    // 組裝 URL
    URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
    
    // 省略無關(guān)代碼
}

上面的代碼首先是將一些信息钳枕,比如版本缴渊、時間戳、方法名以及各種配置對象的字段信息放入到 map 中鱼炒,map 中的內(nèi)容將作為 URL 的查詢字符串疟暖。構(gòu)建好 map 后,緊接著是獲取上下文路徑、主機(jī)名以及端口號等信息俐巴。最后將 map 和主機(jī)名等數(shù)據(jù)傳給 URL 構(gòu)造方法創(chuàng)建 URL 對象。

這里我生成的URL配置為:

dubbo://192.168.33.1:20880/com.hui.wang.dubbo.learn.api.MyService?anyhost=true&application=provider&bind.ip=192.168.33.1&bind.port=20880&dubbo=2.0.1&generic=false&interface=com.hui.wang.dubbo.learn.api.MyService&methods=say&organization=dubbo-learn&owner=hui.wang&pid=87636&revision=1.0.0&side=provider&timestamp=1550485766884&version=1.0

上面省略了一段代碼硬爆,這里簡單分析一下欣舵。這段代碼用于檢測 <dubbo:argument> 標(biāo)簽中的配置信息,并將相關(guān)配置添加到 map 中缀磕。代碼如下:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    // ...

    // methods 為 MethodConfig 集合缘圈,MethodConfig 中存儲了 <dubbo:method> 標(biāo)簽的配置信息
    if (methods != null && !methods.isEmpty()) {
        for (MethodConfig method : methods) {
            // 添加 MethodConfig 對象的字段信息到 map 中,鍵 = 方法名.屬性名袜蚕。
            // 比如存儲 <dubbo:method name="sayHello" retries="2"> 對應(yīng)的 MethodConfig糟把,
            // 鍵 = sayHello.retries,map = {"sayHello.retries": 2, "xxx": "yyy"}
            appendParameters(map, method, method.getName());

            String retryKey = method.getName() + ".retry";
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                // 檢測 MethodConfig retry 是否為 false牲剃,若是遣疯,則設(shè)置重試次數(shù)為0
                if ("false".equals(retryValue)) {
                    map.put(method.getName() + ".retries", "0");
                }
            }
            
            // 獲取 ArgumentConfig 列表
            List<ArgumentConfig> arguments = method.getArguments();
            if (arguments != null && !arguments.isEmpty()) {
                for (ArgumentConfig argument : arguments) {
                    // 檢測 type 屬性是否為空,或者空串(分支1 ??)
                    if (argument.getType() != null && argument.getType().length() > 0) {
                        Method[] methods = interfaceClass.getMethods();
                        if (methods != null && methods.length > 0) {
                            for (int i = 0; i < methods.length; i++) {
                                String methodName = methods[i].getName();
                                // 比對方法名凿傅,查找目標(biāo)方法
                                if (methodName.equals(method.getName())) {
                                    Class<?>[] argtypes = methods[i].getParameterTypes();
                                    if (argument.getIndex() != -1) {
                                        // 檢測 ArgumentConfig 中的 type 屬性與方法參數(shù)列表
                                        // 中的參數(shù)名稱是否一致缠犀,不一致則拋出異常(分支2 ??)
                                        if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                            // 添加 ArgumentConfig 字段信息到 map 中,
                                            // 鍵前綴 = 方法名.index聪舒,比如:
                                            // map = {"sayHello.3": true}
                                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                        } else {
                                            throw new IllegalArgumentException("argument config error: ...");
                                        }
                                    } else {    // 分支3 ??
                                        for (int j = 0; j < argtypes.length; j++) {
                                            Class<?> argclazz = argtypes[j];
                                            // 從參數(shù)類型列表中查找類型名稱為 argument.type 的參數(shù)
                                            if (argclazz.getName().equals(argument.getType())) {
                                                appendParameters(map, argument, method.getName() + "." + j);
                                                if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                    throw new IllegalArgumentException("argument config error: ...");
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }

                    // 用戶未配置 type 屬性辨液,但配置了 index 屬性,且 index != -1
                    } else if (argument.getIndex() != -1) {    // 分支4 ??
                        // 添加 ArgumentConfig 字段信息到 map 中
                        appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                    } else {
                        throw new IllegalArgumentException("argument config must set index or type");
                    }
                }
            }
        }
    }

    // ...
}

上面這段代碼 for 循環(huán)和 if else 分支嵌套太多箱残,導(dǎo)致層次太深滔迈,不利于閱讀,需要耐心看一下被辑。大家在看這段代碼時燎悍,注意把幾個重要的條件分支找出來。只要理解了這幾個分支的意圖敷待,就可以弄懂這段代碼间涵。請注意上面代碼中??符號,這幾個符號標(biāo)識出了4個重要的分支榜揖,下面用偽代碼解釋一下這幾個分支的含義勾哩。

// 獲取 ArgumentConfig 列表
for (遍歷 ArgumentConfig 列表) {
    if (type 不為 null,也不為空串) {    // 分支1
        1. 通過反射獲取 interfaceClass 的方法列表
        for (遍歷方法列表) {
            1. 比對方法名举哟,查找目標(biāo)方法
            2. 通過反射獲取目標(biāo)方法的參數(shù)類型數(shù)組 argtypes
            if (index != -1) {    // 分支2
                1. 從 argtypes 數(shù)組中獲取下標(biāo) index 處的元素 argType
                2. 檢測 argType 的名稱與 ArgumentConfig 中的 type 屬性是否一致
                3. 添加 ArgumentConfig 字段信息到 map 中思劳,或拋出異常
            } else {    // 分支3
                1. 遍歷參數(shù)類型數(shù)組 argtypes,查找 argument.type 類型的參數(shù)
                2. 添加 ArgumentConfig 字段信息到 map 中
            }
        }
    } else if (index != -1) {    // 分支4
        1. 添加 ArgumentConfig 字段信息到 map 中
    }
}

在本節(jié)分析的源碼中妨猩,appendParameters 這個方法出現(xiàn)的次數(shù)比較多潜叛,該方法用于將對象字段信息添加到 map 中。實現(xiàn)上則是通過反射獲取目標(biāo)對象的 getter 方法,并調(diào)用該方法獲取屬性值威兜。然后再通過 getter 方法名解析出屬性名销斟,比如從方法名 getName 中可解析出屬性 name。如果用戶傳入了屬性名前綴椒舵,此時需要將屬性名加入前綴內(nèi)容蚂踊。最后將 <屬性名,屬性值> 鍵值對存入到 map 中就行了笔宿。限于篇幅原因犁钟,這里就不分析 appendParameters 方法的源碼了,大家請自行分析泼橘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末涝动,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子炬灭,更是在濱河造成了極大的恐慌醋粟,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件担败,死亡現(xiàn)場離奇詭異昔穴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)提前,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門吗货,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人狈网,你說我怎么就攤上這事宙搬。” “怎么了拓哺?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵勇垛,是天一觀的道長。 經(jīng)常有香客問我士鸥,道長闲孤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任烤礁,我火速辦了婚禮讼积,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脚仔。我一直安慰自己勤众,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布鲤脏。 她就那樣靜靜地躺著们颜,像睡著了一般吕朵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窥突,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天努溃,我揣著相機(jī)與錄音,去河邊找鬼波岛。 笑死茅坛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的则拷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼曹鸠,長吁一口氣:“原來是場噩夢啊……” “哼煌茬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起彻桃,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤坛善,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后邻眷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眠屎,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年肆饶,在試婚紗的時候發(fā)現(xiàn)自己被綠了改衩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡驯镊,死狀恐怖葫督,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情板惑,我是刑警寧澤橄镜,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站冯乘,受9級特大地震影響洽胶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裆馒,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一姊氓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦垛贤、人聲如沸堕阔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春蟀俊,著一層夾襖步出監(jiān)牢的瞬間钦铺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工肢预, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留矛洞,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓烫映,卻偏偏與公主長得像沼本,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子锭沟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

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