美文网首页
dubbo(九)-dubbo服务导出详解-1

dubbo(九)-dubbo服务导出详解-1

作者: KissGoodby | 来源:发表于2019-02-18 20:33 被阅读0次

    介绍

    本篇文章,我们来研究一下 Dubbo 导出服务的过程。Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分:

    • 第一部分是前置工作,主要用于检查参数,组装 URL。
    • 第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。
    • 第三部分是向注册中心注册服务,用于服务发现。

    1. 第一部分- 导出服务前置工作

    Dubbo 导出服务的入口class是com.alibaba.dubbo.config.spring.ServiceBean,这个类是spring通过解析<dubbo:service>节点创建的单例Bean,每一个<dubbo:service>都会创建一个ServiceBeanServiceBean的类图如下:

    ServiceBean
    ServiceBean 的声明为:
    public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, 
            ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>,  BeanNameAware 
    
    

    可以看出ServiceBean实现了spring的InitializingBeanDisposableBeanApplicationContextAwareApplicationListenerBeanNameAware接口,关于这几个接口的使用在前面已经提到了,这里不再说明。

    Dubbo 支持两种服务导出方式,分别延迟导出和立即导出。延迟导出的入口是 ServiceBeanafterPropertiesSet 方法,立即导出的入口是 ServiceBeanonApplicationEvent方法。

    我们先看一下afterPropertiesSet 方法,这个方法会在ServiceBean创建前调用。

        @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);
                }
            }
    
            // 延迟导出,这里不做讲解
            if (!isDelay()) {
                export();
            }
        }
    

    afterPropertiesSet检查ServiceBean的某个属性(这里的属性包含如下6个)是否为空,如果为空,从applicationContext获取相应类型的bean,如果获取到了,则进行相应的设置。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:服务名称

    填充完后,有一个重要的方法就是export()。这个方法会判断延迟的时间是否大于0,如果是,才会执行。这里如果我们没有设置延迟,一个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"
    }
    

    实际上在创建ServiceBean实例的时候,也会初始化其父类ServiceConfig的静态属性:

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

    这里使用了Adaptive,之前有过讲解,会生成对应的Protocol$Adaptive实例和ProxyFactory$Adaptive实例。对应的源码如下:
    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的初始化过程已经讲解完成了。接下来我们看看ServiceBean#onApplicationEvent方法,改方法会在 Spring 上下文刷新事件时调用。代码如下:

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

    这个方法首先会根据条件决定是否导出服务,比如有些服务设置了延时导出,那么此时就不应该在此处导出。还有一些服务已经被导出了,或者当前服务被取消导出了,此时也不能再次导出相关服务。注意这里的 isDelay 方法,这个方法字面意思是“是否延迟导出服务”,返回 true 表示延迟导出,false 表示不延迟导出。但是该方法真实意思却并非如此,当方法返回 true 时,表示无需延迟导出。返回 false 时,表示需要延迟导出。

    接下来对服务导出的前置逻辑进行分析。

    配置检查以及 URL 装配

    在导出服务之前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省配置。配置检查完成后,接下来需要根据这些配置组装 URL。

    1. 检查配置,本节我们接着前面的源码向下分析,前面说过 onApplicationEvent 方法在经过一些判断后,会决定是否调用 export 方法导出服务。那么下面我们从 export 方法开始进行分析,如下:
        public synchronized void export() {
            if (provider != null) {
                // 获取 export 和 delay 配置
                if (export == null) {
                    export = provider.getExport();
                }
                if (delay == null) {
                    delay = provider.getDelay();
                }
            }
            // 如果 export 为 false,则不导出服务
            if (export != null && !export) {
                return;
            }
    
            // delay > 0,延时导出服务
            if (delay != null && delay > 0) {
                delayExportExecutor.schedule(new Runnable() {
                    @Override
                    public void run() {
                        doExport();
                    }
                }, delay, TimeUnit.MILLISECONDS);
            // 立即导出服务
            } else {
                doExport();
            }
        }
    

    export方法分别对exportdelay配置进行检查,首先是 export 配置,这个配置决定了是否导出服务。有时候我们只是想本地启动服务进行一些调试工作,我们并不希望把本地启动的服务暴露出去给别人调用。此时,我们可通过配置 export 禁止服务导出,比如:

    <dubbo:provider export="false" />
    

    delay 配置顾名思义,用于延迟导出服务,这个就不分析了。

    1. 下面,我们继续分析源码,这次要分析的是 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 是否为空,为空则新建一个,并通过系统变量为其初始化
            checkDefault();
    
            // 下面几个 if 语句用于检测 provider、application 等核心配置类对象是否为空,
            // 若为空,则尝试从其他配置类对象中获取相应的实例。
            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 是否为泛化服务类型
            if (ref instanceof GenericService) {
                // 设置 interfaceClass 为 GenericService.class
                interfaceClass = GenericService.class;
                if (StringUtils.isEmpty(generic)) {
                    // 设置 generic = "true"
                    generic = Boolean.TRUE.toString();
                }
            // ref 非 GenericService 类型
            } else {
                try {
                    // 设置 interfaceClass
                    interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                            .getContextClassLoader());
                } catch (ClassNotFoundException e) {
                    throw new IllegalStateException(e.getMessage(), e);
                }
                // 对 interfaceClass,以及 <dubbo:method> 标签中的必要字段进行检查
                checkInterfaceAndMethods(interfaceClass, methods);
                // 对 ref 合法性进行检测
                checkRef();
                // 设置 generic = "false"
                generic = Boolean.FALSE.toString();
            }
            // local 和 stub 在功能应该是一致的,用于配置本地存根
            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;
            }
            // 导出服务
            doExportUrls();
            // ProviderModel 表示服务提供者模型,此对象中存储了与服务提供者相关的信息。
            // 比如服务的配置信息,服务实例等。每个被导出的服务对应一个 ProviderModel。
            // ApplicationModel 持有所有的 ProviderModel。
            ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
            ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
        }
    

    以上就是配置检查的相关分析,代码比较多,需要大家耐心看一下。下面对配置检查的逻辑进行简单的总结,如下:

    1. 检测 <dubbo:service> 标签的 interface 属性合法性,不合法则抛出异常
    2. 检测 ProviderConfig、ApplicationConfig 等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例。
    3. 检测并处理泛化服务和普通服务类
    4. 检测本地存根配置,并进行相应的处理
    5. 对 ApplicationConfig、RegistryConfig 等配置类进行检测,为空则尝试创建,若无法创建则抛出异常

    配置检查并非本文重点,因此这里不打算对 doExport 方法所调用的方法进行分析(doExportUrls 方法除外)。在这些方法中,除了 appendProperties 方法稍微复杂一些,其他方法逻辑不是很复杂。因此,大家可自行分析。

    1. doExportUrls方法,Dubbo 允许我们使用不同的协议导出服务,也允许我们向多个注册中心注册服务。相关代码如下:
        private void doExportUrls() {
            // 加载注册中心链接
            List<URL> registryURLs = loadRegistries(true);
            // 遍历 protocols,并在每个协议下导出服务
            for (ProtocolConfig protocolConfig : protocols) {
                doExportUrlsFor1Protocol(protocolConfig, registryURLs);
            }
        }
    

    上面代码首先是通过 loadRegistries加载注册中心链接,然后再遍历 ProtocolConfig 集合导出每个服务。并在导出服务的过程中,将服务注册到注册中心。下面,我们先来看一下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 为空,则将其设为 0.0.0.0
                        address = Constants.ANYHOST_VALUE;
                    }
                    // 从系统属性中加载注册中心地址
                    String sysaddress = System.getProperty("dubbo.registry.address");
                    // 如果系统属性中的配置中心地址不为空,将注册地址设置为系统属性配置中的地址
                    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()));
                        }
    
                        // 配置协议
                        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 协议头设置为 registry
                            url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
                            // 通过判断条件,决定是否添加 url 到 registryList 中,条件如下:
                            // (服务提供者 && register = true 或 null)
                            //    || (非服务提供者 && 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. 构建参数映射集合,也就是 map
    3. 构建注册中心链接列表
    4. 遍历链接列表,并根据条件决定是否将其添加到 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后,就是继续组装 导出的URL。下面开始分析doExportUrlsFor1Protocol方法,代码如下:

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        String name = protocolConfig.getName();
        // 如果协议名为空,或空串,则将协议名变量设置为 dubbo
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }
    
        Map<String, String> map = new HashMap<String, String>();
        // 添加 side、版本、时间戳以及进程号等信息到 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> 标签的配置信息
        if (methods != null && !methods.isEmpty()) {
            // 这段代码用于添加 Callback 配置到 map 中,代码太长,待会单独分析
        }
    
        // 检测 generic 是否为 "true",并根据检测结果向 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 中包含了接口的详细信息,比如接口方法名数组,字段信息等
            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)) {
                // 随机生成 token
                map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(Constants.TOKEN_KEY, token);
            }
        }
        // 判断协议名是否为 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);
        
        // 省略无关代码
    }
    

    上面的代码首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息。最后将 map 和主机名等数据传给 URL 构造方法创建 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> 标签中的配置信息,并将相关配置添加到 map 中。代码如下:

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        // ...
    
        // methods 为 MethodConfig 集合,MethodConfig 中存储了 <dubbo:method> 标签的配置信息
        if (methods != null && !methods.isEmpty()) {
            for (MethodConfig method : methods) {
                // 添加 MethodConfig 对象的字段信息到 map 中,键 = 方法名.属性名。
                // 比如存储 <dubbo:method name="sayHello" retries="2"> 对应的 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,若是,则设置重试次数为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();
                                    // 比对方法名,查找目标方法
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        if (argument.getIndex() != -1) {
                                            // 检测 ArgumentConfig 中的 type 属性与方法参数列表
                                            // 中的参数名称是否一致,不一致则抛出异常(分支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];
                                                // 从参数类型列表中查找类型名称为 argument.type 的参数
                                                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 循环和 if else 分支嵌套太多,导致层次太深,不利于阅读,需要耐心看一下。大家在看这段代码时,注意把几个重要的条件分支找出来。只要理解了这几个分支的意图,就可以弄懂这段代码。请注意上面代码中⭐️符号,这几个符号标识出了4个重要的分支,下面用伪代码解释一下这几个分支的含义。

    // 获取 ArgumentConfig 列表
    for (遍历 ArgumentConfig 列表) {
        if (type 不为 null,也不为空串) {    // 分支1
            1. 通过反射获取 interfaceClass 的方法列表
            for (遍历方法列表) {
                1. 比对方法名,查找目标方法
                2. 通过反射获取目标方法的参数类型数组 argtypes
                if (index != -1) {    // 分支2
                    1. 从 argtypes 数组中获取下标 index 处的元素 argType
                    2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致
                    3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常
                } else {    // 分支3
                    1. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数
                    2. 添加 ArgumentConfig 字段信息到 map 中
                }
            }
        } else if (index != -1) {    // 分支4
            1. 添加 ArgumentConfig 字段信息到 map 中
        }
    }
    

    在本节分析的源码中,appendParameters 这个方法出现的次数比较多,该方法用于将对象字段信息添加到 map 中。实现上则是通过反射获取目标对象的 getter 方法,并调用该方法获取属性值。然后再通过 getter 方法名解析出属性名,比如从方法名 getName 中可解析出属性 name。如果用户传入了属性名前缀,此时需要将属性名加入前缀内容。最后将 <属性名,属性值> 键值对存入到 map 中就行了。限于篇幅原因,这里就不分析 appendParameters 方法的源码了,大家请自行分析。

    相关文章

      网友评论

          本文标题:dubbo(九)-dubbo服务导出详解-1

          本文链接:https://www.haomeiwen.com/subject/yexzeqtx.html