【Dubbo源码分析】服务导出

Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装 URL。第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。第三部分是向注册中心注册服务,用于服务发现。

在上篇文章中我们分析过了使用注解配置provider的时候服务暴露是通过Spring事件机制触发的,请看ServiceBean的onApplicationEvent方法。

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

1、前置工作

前置工作主要包含两个部分,分别是配置检查,以及 URL 装配。在导出服务之前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省配置。配置检查完成后,接下来需要根据这些配置组装 URL。在 Dubbo 中,URL 的作用十分重要。Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获取配置。

1.1、检查配置

本节我们接着前面的源码向下分析,前面说过 onApplicationEvent 方法在经过一些判断后,会决定是否调用 export 方法导出服务。那么下面我们从 export 方法开始进行分析,如下:

@Override
public void export() {
    //调用父类方法
    super.export();
    // Publish ServiceBeanExportedEvent
    publishExportEvent();
}

ServiceBean将导出服务交给了父类的export方法,下面看ServiceConfig的export方法:

public synchronized void export() {
    checkAndUpdateSubConfigs();

    if (!shouldExport()) {
        return;
    }

    if (shouldDelay()) {
        delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS);
    } else {
        doExport();
    }
}

checkAndUpdateSubConfigs方法用来检测一些必要的属性和设置一些默认值,shouldDelay方法代表是否@Service(delay>0)或者设置了全局属性dubbo.provider.delay。下面看一下复杂的checkAndUpdateSubConfigs方法。

public void checkAndUpdateSubConfigs() {
    // 用于检测 provider、application 等核心配置类对象是否为空,
    // 若为空,则尝试从其他配置类对象中获取相应的实例。
    completeCompoundConfigs();
    // 启动配置中心
    startConfigCenter();
    // 如果没有ProviderConfig创建一个默认的
    checkDefault();
    // 如果没有ApplicationConfig创建一个默认的
    checkApplication();
    // 检查注册表配置是否存在,然后将其转换为RegistryConfig
    checkRegistry();
    // 检查ProtocolConfig配置,未配置使用provider.getProtocols()或创建一个默认的
    checkProtocol();
    // 刷新ServiceConfig配置
    this.refresh();
    // 检查MetadataReportConfig,没有创建一个默认的
    checkMetadataReport();

    if (StringUtils.isEmpty(interfaceName)) {
        throw new IllegalStateException(" interface not allow null!");
    }

    if (ref instanceof GenericService) {
        interfaceClass = GenericService.class;
        if (StringUtils.isEmpty(generic)) {
            generic = Boolean.TRUE.toString();
        }
    } else {
        try {
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        checkInterfaceAndMethods(interfaceClass, methods);
        checkRef();
        generic = Boolean.FALSE.toString();
    }
    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);
        }
    }
    checkStubAndLocal(interfaceClass);
    checkMock(interfaceClass);
}

下面对配置检查的逻辑进行简单的总结,如下:

  • checkAndUpdateSubConfigs开头部分是对一些配置的填充和必要属性的检测
  • 启动配置中心,从dubbo2.7.0开始支持了配置中心动态修改配置
  • 然后是判断ref是否是GenericService类型,GenericService的使用请参考https://www.cnblogs.com/notlate/p/10127942.html
  • 如果配置了stub=true,检测是否存在存根类,关于Dubbo的存根请参考https://www.cnblogs.com/hzhuxin/p/8250602.html
  • 检测存根类的构造方法参数必须是有且仅有一个接口类型的参数
  • 检测服务端Mock类是否合法,关于mock的应用参考https://www.cnblogs.com/libin6505/p/10592669.html

因为配置检查不是服务导出的核心部分,以上只做简要介绍,下面重点doExport 方法所调用的方法进行分析。

1.2、多协议多注册中心导出服务

Dubbo 允许我们使用不同的协议导出服务,也允许我们向多个注册中心注册服务。Dubbo 在 doExportUrls 方法中对多协议,多注册中心进行了支持。相关代码如下:

protected synchronized void doExport() {
    if (unexported) {
        throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
    }
    if (exported) {
        return;
    }
    exported = true;

    if (StringUtils.isEmpty(path)) {
        path = interfaceName;
    }
    doExportUrls();
}
private void doExportUrls() {
    List registryURLs = loadRegistries(true);
    for (ProtocolConfig protocolConfig : protocols) {
        //拼接pathKey:group/contextpath/interfacename:version
        String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
        // ProviderModel 表示服务提供者模型,此对象中存储了与服务提供者相关的信息。
        // 比如服务的配置信息,服务实例等。每个被导出的服务对应一个 ProviderModel。
        // ApplicationModel 持有所有的 ProviderModel。
        ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
        ApplicationModel.initProviderModel(pathKey, providerModel);
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

上面代码首先是通过 loadRegistries 加载注册中心链接,然后再遍历 ProtocolConfig 集合导出每个服务。并在导出服务的过程中,将服务注册到注册中心。下面,我们先来看一下 loadRegistries 方法的逻辑。

protected List loadRegistries(boolean provider) {
    // check && override if necessary
    List registryList = new ArrayList();
    if (CollectionUtils.isNotEmpty(registries)) {
        for (RegistryConfig config : registries) {
            String address = config.getAddress();
            if (StringUtils.isEmpty(address)) {
                // 若 address 为空,则将其设为 0.0.0.0
                address = Constants.ANYHOST_VALUE;
            }
            if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                Map map = new HashMap();
                // 添加 ApplicationConfig 中getter方法返回值,key为@Paramer的key或getter方法名驼峰转句点分隔的名字入getXxxYyy->xxx.yyy
                appendParameters(map, application);
                // 添加 RegistryConfig 中getter方法返回值
                appendParameters(map, config);
                map.put(Constants.PATH_KEY, RegistryService.class.getName());
                appendRuntimeParameters(map);
                if (!map.containsKey(Constants.PROTOCOL_KEY)) {
                    map.put(Constants.PROTOCOL_KEY, Constants.DUBBO_PROTOCOL);
                }
                // 解析得到 URL 列表,address 可能包含多个注册中心 ip,
                // 因此解析得到的是一个 URL 列表
                List urls = UrlUtils.parseURLs(address, map);

                for (URL url : urls) {
                    url = URLBuilder.from(url)
                            .addParameter(Constants.REGISTRY_KEY, url.getProtocol())
                            // 将 URL 协议头设置为 registry
                            .setProtocol(Constants.REGISTRY_PROTOCOL)
                            .build();
                    // 通过判断条件,决定是否添加 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;
}

1.3、组装 URL

紧接着要做的事情是根据配置,以及其他一些信息二次组装 URL。前面说过,URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。URL 之于 Dubbo,犹如水之于鱼,非常重要。大家在阅读 Dubbo 服务导出相关源码的过程中,要注意 URL 内容的变化。既然 URL 如此重要,那么下面我们来了解一下 URL 组装的过程。

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
    String name = protocolConfig.getName();
    if (StringUtils.isEmpty(name)) {
        name = Constants.DUBBO;
    }

    Map map = new HashMap();
    // 添加 side、版本、时间戳以及进程号等信息到 map 中
    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
    appendRuntimeParameters(map);
    // 通过反射将对象的字段信息添加到 map 中,除返回值是Object或@Parameter(excluded=true)的
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, provider, Constants.DEFAULT_KEY);
    appendParameters(map, protocolConfig);
    appendParameters(map, this);
    if (CollectionUtils.isNotEmpty(methods)) {
        // methods为MethodConfig集合,MethodConfig中存储了@Service的methods属性@Method配置信息
        for (MethodConfig method : methods) {
            // 添加 MethodConfig 对象的字段信息到 map 中,键 = 方法名.属性名。
            // 比如存储 @Method(name="sayHello",retries="2") 对应的 MethodConfig,
            // 键 = sayHello.retries,map = {"sayHello.retries": 2, "xxx": "yyy"}
            appendParameters(map, method, method.getName());
            // 检测 MethodConfig retry 是否为 false,若是,则设置重试次数为0
            String retryKey = method.getName() + ".retry";
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                if ("false".equals(retryValue)) {
                    map.put(method.getName() + ".retries", "0");
                }
            }
            // 获取 ArgumentConfig 列表
            List arguments = method.getArguments();
            if (CollectionUtils.isNotEmpty(arguments)) {
                for (ArgumentConfig argument : arguments) {
                    // 检测 type 属性是否为空,或者空串(分支1)
                    if (argument.getType() != null && argument.getType().length() > 0) {
                        Method[] methods = interfaceClass.getMethods();
                        // visit all methods
                        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();
                                    // one callback in the method
                                    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 : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                        }
                                    } else {
                                        // // 分支3 multiple callbacks in the method
                                        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 : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    // 用户未配置 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 attribute.eg:  or ");
                    }

                }
            }
        } // end of methods for
    }
    // 检测 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 " + interfaceClass.getName());
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            // 将逗号作为分隔符连接方法名,并将连接后的字符串放入 map 中
            map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), ","));
        }
    }
    // 添加 token 到 map 中
    if (!ConfigUtils.isEmpty(token)) {
        if (ConfigUtils.isDefault(token)) {
            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
        } else {
            map.put(Constants.TOKEN_KEY, token);
        }
    }
    //下面省略
}

上面的代码首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息。最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。需要注意的是,这里出现的 URL 并非 java.net.URL,而是 com.alibaba.dubbo.common.URL。

上面这段代码 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 中就行了。

2、导出 Dubbo 服务

前置工作做完,接下来就可以进行服务导出了。服务导出分为导出到本地 (JVM)和导出到远程。在深入分析服务导出的源码前,我们先来从宏观层面上看一下服务导出逻辑。doExportUrlsFor1Protocol方法其余部分如下如下:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
    // 上面代码略
    // 导出服务
    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = this.findConfigedPorts(protocolConfig, name, map);
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
        // 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }

    String scope = url.getParameter(Constants.SCOPE_KEY);
    // 如果 scope = none,则不会导出服务
    if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {

        // scope != remote,导出到本地
        if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) {
            exportLocal(url);
        }
        // scope != local,导出到远程
        if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {
            if (logger.isInfoEnabled()) {
                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
            }
            if (CollectionUtils.isNotEmpty(registryURLs)) {
                for (URL registryURL : registryURLs) {
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                    // 加载监视器链接
                    URL monitorUrl = loadMonitor(registryURL);
                    if (monitorUrl != null) {
                        // 将监视器链接作为参数添加到 url 中
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }
                    if (logger.isInfoEnabled()) {
                        logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                    }

                    // For providers, this is used to enable custom proxy to generate invoker
                    String proxy = url.getParameter(Constants.PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                    }
                    // Adaptive$ProxyFactory根据URL的proxy参数选择具体实现,如果没有指定这个参数使用SPI默认值javassist为服务提供类(ref)生成 Invoker
                    Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                    // DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                    // 导出服务,并生成 Exporter
                    Exporter exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            } else {
                // 不存在注册中心,仅导出服务
                Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                
                Exporter exporter = protocol.export(wrapperInvoker);
                exporters.add(exporter);
            }
            //如果配置了MetadataReportConfig 推送服务配置元数据到元数据中心
            MetadataReportService metadataReportService = null;
            if ((metadataReportService = getMetadataReportService()) != null) {
                metadataReportService.publishProvider(url);
            }
        }
    }
    this.urls.add(url);
}

上面代码根据 url 中的 scope 参数决定服务导出方式,分别如下:

  • scope = none,不导出服务
  • scope != remote,导出到本地
  • scope != local,导出到远程

不管是导出到本地,还是远程。进行服务导出之前,均需要先创建 Invoker,这是一个很重要的步骤。因此下面先来分析 Invoker 的创建过程。

2.1、Invoker 创建过程

在 Dubbo 中,Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中对 Invoker 进行了说明,这里引用一下。

Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

既然 Invoker 如此重要,那么我们很有必要搞清楚 Invoker 的用途。Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory。下面我们到 JavassistProxyFactory 代码中,探索 Invoker 的创建过程。如下:

public  Invoker getInvoker(T proxy, Class type, URL url) {
	// 为目标类创建 Wrapper
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
    // 创建匿名 Invoker 类对象,并实现 doInvoke 方法。
    return new AbstractProxyInvoker(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName,
                                  Class[] parameterTypes,
                                  Object[] arguments) throws Throwable {
			// 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

如上,JavassistProxyFactory 创建了一个继承自 AbstractProxyInvoker 类的匿名对象,并覆写了抽象方法 doInvoke。覆写后的 doInvoke 逻辑比较简单,仅是将调用请求转发给了 Wrapper 类的 invokeMethod 方法。Wrapper 用于“包裹”目标类,Wrapper 是一个抽象类,仅可通过 getWrapper(Class) 方法创建子类。在创建 Wrapper 子类的过程中,子类代码生成逻辑会对 getWrapper 方法传入的 Class 对象进行解析,拿到诸如类方法,类成员变量等信息。以及生成 invokeMethod 方法代码和其他一些方法代码。代码生成完毕后,通过 Javassist 生成 Class 对象,最后再通过反射创建 Wrapper 实例。相关的代码如下:

public static Wrapper getWrapper(Class c) {
    while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class.
    {
        c = c.getSuperclass();
    }

    if (c == Object.class) {
        return OBJECT_WRAPPER;
    }
    // 从缓存中获取 Wrapper 实例
    Wrapper ret = WRAPPER_MAP.get(c);
    if (ret == null) {
        // 缓存未命中,创建 Wrapper
        ret = makeWrapper(c);
        WRAPPER_MAP.put(c, ret);
    }
    return ret;
}

getWrapper 方法仅包含一些缓存操作逻辑,不难理解。下面我们看一下 makeWrapper 方法。

private static Wrapper makeWrapper(Class c) {
    // 检测 c 是否为基本类型,若是则抛出异常
    if (c.isPrimitive())
        throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c);

    String name = c.getName();
    ClassLoader cl = ClassHelper.getClassLoader(c);

    // c1 用于存储 setPropertyValue 方法代码
    StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
    // c2 用于存储 getPropertyValue 方法代码
    StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
    // c3 用于存储 invokeMethod 方法代码
    StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ ");

    // 生成类型转换代码及异常捕捉代码,比如:
    //   DemoService w; try { w = ((DemoServcie) $1); }}catch(Throwable e){ throw new IllegalArgumentException(e); }
    c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
    c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
    c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");

    // pts 用于存储成员变量名和类型
    Map> pts = new HashMap>();
    // ms 用于存储方法描述信息(可理解为方法签名)及 Method 实例
    Map ms = new LinkedHashMap();
    // mns 为方法名列表
    List mns = new ArrayList();
    // dmns 用于存储“定义在当前类中的方法”的名称
    List dmns = new ArrayList();

    // --------------------------------✨ 分割线1 ✨-------------------------------------

    // 获取 public 访问级别的字段,并为所有字段生成条件判断语句
    for (Field f : c.getFields()) {
        String fn = f.getName();
        Class ft = f.getType();
        if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers()))
            // 忽略关键字 static 或 transient 修饰的变量
            continue;

        // 生成条件判断及赋值语句,比如:
        // if( $2.equals("name") ) { w.name = (java.lang.String) $3; return;}
        // if( $2.equals("age") ) { w.age = ((Number) $3).intValue(); return;}
        c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }");

        // 生成条件判断及返回语句,比如:
        // if( $2.equals("name") ) { return ($w)w.name; }
        c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }");

        // 存储 <字段名, 字段类型> 键值对到 pts 中
        pts.put(fn, ft);
    }

    // --------------------------------✨ 分割线2 ✨-------------------------------------

    Method[] methods = c.getMethods();
    // 检测 c 中是否包含在当前类中声明的方法
    boolean hasMethod = hasMethods(methods);
    if (hasMethod) {
        c3.append(" try{");
    }
    for (Method m : methods) {
        if (m.getDeclaringClass() == Object.class)
            // 忽略 Object 中定义的方法
            continue;

        String mn = m.getName();
        // 生成方法名判断语句,比如:
        // if ( "sayHello".equals( $2 )
        c3.append(" if( \"").append(mn).append("\".equals( $2 ) ");
        int len = m.getParameterTypes().length;
        // 生成“运行时传入的参数数量与方法参数列表长度”判断语句,比如:
        // && $3.length == 2
        c3.append(" && ").append(" $3.length == ").append(len);

        boolean override = false;
        for (Method m2 : methods) {
            // 检测方法是否存在重载情况,条件为:方法对象不同 && 方法名相同
            if (m != m2 && m.getName().equals(m2.getName())) {
                override = true;
                break;
            }
        }
        // 对重载方法进行处理,考虑下面的方法:
        //    1. void sayHello(Integer, String)
        //    2. void sayHello(Integer, Integer)
        // 方法名相同,参数列表长度也相同,因此不能仅通过这两项判断两个方法是否相等。
        // 需要进一步判断方法的参数类型
        if (override) {
            if (len > 0) {
                for (int l = 0; l < len; l++) {
                    // 生成参数类型进行检测代码,比如:
                    // && $3[0].getName().equals("java.lang.Integer") 
                    //    && $3[1].getName().equals("java.lang.String")
                    c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"")
                            .append(m.getParameterTypes()[l].getName()).append("\")");
                }
            }
        }

        // 添加 ) {,完成方法判断语句,此时生成的代码可能如下(已格式化):
        // if ("sayHello".equals($2) 
        //     && $3.length == 2
        //     && $3[0].getName().equals("java.lang.Integer") 
        //     && $3[1].getName().equals("java.lang.String")) {
        c3.append(" ) { ");

        // 根据返回值类型生成目标方法调用语句
        if (m.getReturnType() == Void.TYPE)
            // w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null;
            c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");
        else
            // return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);
            c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");

        // 添加 }, 生成的代码形如(已格式化):
        // if ("sayHello".equals($2) 
        //     && $3.length == 2
        //     && $3[0].getName().equals("java.lang.Integer") 
        //     && $3[1].getName().equals("java.lang.String")) {
        //
        //     w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); 
        //     return null;
        // }
        c3.append(" }");

        // 添加方法名到 mns 集合中
        mns.add(mn);
        // 检测当前方法是否在 c 中被声明的
        if (m.getDeclaringClass() == c)
            // 若是,则将当前方法名添加到 dmns 中
            dmns.add(mn);
        ms.put(ReflectUtils.getDesc(m), m);
    }
    if (hasMethod) {
        // 添加异常捕捉语句
        c3.append(" } catch(Throwable e) { ");
        c3.append("     throw new java.lang.reflect.InvocationTargetException(e); ");
        c3.append(" }");
    }

    // 添加 NoSuchMethodException 异常抛出代码
    c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }");

    // --------------------------------✨ 分割线3 ✨-------------------------------------

    Matcher matcher;
    // 处理 get/set 方法
    for (Map.Entry entry : ms.entrySet()) {
        String md = entry.getKey();
        Method method = (Method) entry.getValue();
        // 匹配以 get 开头的方法
        if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            // 获取属性名
            String pn = propertyName(matcher.group(1));
            // 生成属性判断以及返回语句,示例如下:
            // if( $2.equals("name") ) { return ($w).w.getName(); }
            c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
            pts.put(pn, method.getReturnType());

        // 匹配以 is/has/can 开头的方法
        } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            String pn = propertyName(matcher.group(1));
            // 生成属性判断以及返回语句,示例如下:
            // if( $2.equals("dream") ) { return ($w).w.hasDream(); }
            c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
            pts.put(pn, method.getReturnType());

        // 匹配以 set 开头的方法
        } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            Class pt = method.getParameterTypes()[0];
            String pn = propertyName(matcher.group(1));
            // 生成属性判断以及 setter 调用语句,示例如下:
            // if( $2.equals("name") ) { w.setName((java.lang.String)$3); return; }
            c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }");
            pts.put(pn, pt);
        }
    }

    // 添加 NoSuchPropertyException 异常抛出代码
    c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }");
    c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }");

    // --------------------------------✨ 分割线4 ✨-------------------------------------

    long id = WRAPPER_CLASS_COUNTER.getAndIncrement();
    // 创建类生成器
    ClassGenerator cc = ClassGenerator.newInstance(cl);
    // 设置类名及超类
    cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id);
    cc.setSuperClass(Wrapper.class);

    // 添加默认构造方法
    cc.addDefaultConstructor();

    // 添加字段
    cc.addField("public static String[] pns;");
    cc.addField("public static " + Map.class.getName() + " pts;");
    cc.addField("public static String[] mns;");
    cc.addField("public static String[] dmns;");
    for (int i = 0, len = ms.size(); i < len; i++)
        cc.addField("public static Class[] mts" + i + ";");

    // 添加方法代码
    cc.addMethod("public String[] getPropertyNames(){ return pns; }");
    cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");
    cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");
    cc.addMethod("public String[] getMethodNames(){ return mns; }");
    cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");
    cc.addMethod(c1.toString());
    cc.addMethod(c2.toString());
    cc.addMethod(c3.toString());

    try {
        // 生成类
        Class wc = cc.toClass();
        
        // 设置字段值
        wc.getField("pts").set(null, pts);
        wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));
        wc.getField("mns").set(null, mns.toArray(new String[0]));
        wc.getField("dmns").set(null, dmns.toArray(new String[0]));
        int ix = 0;
        for (Method m : ms.values())
            wc.getField("mts" + ix++).set(null, m.getParameterTypes());

        // 创建 Wrapper 实例
        return (Wrapper) wc.newInstance();
    } catch (RuntimeException e) {
        throw e;
    } catch (Throwable e) {
        throw new RuntimeException(e.getMessage(), e);
    } finally {
        cc.release();
        ms.clear();
        mns.clear();
        dmns.clear();
    }
}

上面代码很长,大家耐心看一下。我们在上面代码中做了大量的注释,并按功能对代码进行了分块,以帮助大家理解代码逻辑。下面对这段代码进行讲解。首先我们把目光移到分割线1之上的代码,这段代码主要用于进行一些初始化操作。比如创建 c1、c2、c3 以及 pts、ms、mns 等变量,以及向 c1、c2、c3 中添加方法定义和类型转换代码。接下来是分割线1到分割线2之间的代码,这段代码用于为 public 级别的字段生成条件判断取值与赋值代码。这段代码不是很难看懂,就不多说了。继续向下看,分割线2和分隔线3之间的代码用于为定义在当前类中的方法生成判断语句,和方法调用语句。因为需要对方法重载进行校验,因此到这这段代码看起来有点复杂。不过耐心看一下,也不是很难理解。接下来是分割线3和分隔线4之间的代码,这段代码用于处理 getter、setter 以及以 is/has/can 开头的方法。处理方式是通过正则表达式获取方法类型(get/set/is/...),以及属性名。之后为属性名生成判断语句,然后为方法生成调用语句。最后我们再来看一下分隔线4以下的代码,这段代码通过 ClassGenerator 为刚刚生成的代码构建 Class 类,并通过反射创建对象。ClassGenerator 是 Dubbo 自己封装的,该类的核心是 toClass() 的重载方法 toClass(ClassLoader, ProtectionDomain),该方法通过 javassist 构建 Class。这里就不分析 toClass 方法了,大家请自行分析。

2.2、导出服务到本地

本节我们来看一下服务导出相关的代码,按照代码执行顺序,本节先来分析导出服务到本地的过程。相关代码如下:

private void exportLocal(URL url) {
    // 如果 URL 的协议头等于 injvm,说明已经导出到本地了,无需再次导出
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
        URL local = URLBuilder.from(url)
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(LOCALHOST_VALUE)
                .setPort(0)
                .build();
        // 创建 Invoker,并导出服务,这里的 protocol 会在运行时调用 InjvmProtocol 的 export 方法
        Exporter exporter = protocol.export(
                proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
    }
}

exportLocal 方法比较简单,首先根据 URL 协议头决定是否导出服务。若需导出,则创建一个新的 URL 并将协议头、主机名以及端口设置成新的值。然后创建 Invoker,并调用 InjvmProtocol 的 export 方法导出服务。

在ServiceConfig中使用的Protocol是使用javassist动态创建的Protocol$Adaptive如下:

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

这个Protocol$Adaptive执行带有@Adaptive方法的时候会根据参数的getUrl().getProtocol()方法的返回值再此调用ExtensionLoader选出最适合的Protocol,这里指的就是InjvmProtocol。

下面我们来看一下 InjvmProtocol 的 export 方法都做了哪些事情。

public  Exporter export(Invoker invoker) throws RpcException {
    // 创建 InjvmExporter
    return new InjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

如上,InjvmProtocol 的 export 方法仅创建了一个 InjvmExporter,无其他逻辑。到此导出服务到本地就分析完了,接下来,我们继续分析导出服务到远程的过程。

2.3、导出服务到远程

与导出服务到本地相比,导出服务到远程的过程要复杂不少,其包含了服务导出与服务注册两个过程。这两个过程涉及到了大量的调用,比较复杂。按照代码执行顺序,本节先来分析服务导出逻辑,服务注册逻辑将在下一节进行分析。下面开始分析,我们把目光移动到 RegistryProtocol 的 export 方法上。

public  Exporter export(final Invoker originInvoker) throws RpcException {
    // 获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:
    // zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=samples-annotation-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F10.92.214.15%3A20880%2Forg.apache.dubbo.samples.annotation.api.GreetingService%3Fanyhost%3Dtrue%26application%3Dsamples-annotation-provider%26bean.name%3Dproviders%3Adubbo%3Aorg.apache.dubbo.samples.annotation.api.GreetingService%3A1.0.0_annotation%26bind.ip%3D10.92.214.15%26bind.port%3D20880%26default.deprecated%3Dfalse%26default.dynamic%3Dfalse%26default.register%3Dtrue%26default.timeout%3D1000%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dfalse%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.annotation.api.GreetingService%26methods%3Dgreeting%2CreplyGreeting%26pid%3D23358%26register%3Dtrue%26release%3D2.7.1%26revision%3D1.0.0_annotation%26side%3Dprovider%26timestamp%3D1561014246471%26version%3D1.0.0_annotation&pid=23358&release=2.7.1×tamp=1561014246459
    URL registryUrl = getRegistryUrl(originInvoker);
    // 获取服务URL,就是originInvoker.getUrl() export参数部分,如
    // dubbo://127.0.0.1:20880/org.apache.dubbo.samples.annotation.api.GreetingService?anyhost=true&application=samples-annotation-provider&bean.name=providers:dubbo:org.apache.dubbo.samples.annotation.api.GreetingService:1.0.0_annotation&bind.ip=10.92.214.15&bind.port=20880&default.deprecated=false&default.dynamic=false&default.register=true&default.timeout=1000&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&interface=org.apache.dubbo.samples.annotation.api.GreetingService&methods=greeting,replyGreeting&pid=23358®ister=true&release=2.7.1&revision=1.0.0_annotation&side=provider×tamp=1561014246471&version=1.0.0_annotation
    URL providerUrl = getProviderUrl(originInvoker);

    // 获取订阅 URL,比如:
    // provider://127.0.0.1:20880/org.apache.dubbo.samples.annotation.api.GreetingService?anyhost=true&application=samples-annotation-provider&bean.name=providers:dubbo:org.apache.dubbo.samples.annotation.api.GreetingService:1.0.0_annotation&bind.ip=10.92.214.15&bind.port=20880&category=configurators&check=false&default.deprecated=false&default.dynamic=false&default.register=true&default.timeout=1000&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&interface=org.apache.dubbo.samples.annotation.api.GreetingService&methods=greeting,replyGreeting&pid=23358®ister=true&release=2.7.1&revision=1.0.0_annotation&side=provider×tamp=1561014246471&version=1.0.0_annotation
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    // 创建监听器
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    //可使用
    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    //export invoker
    final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl);

    // 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
    final Registry registry = getRegistry(originInvoker);
    // 获取已注册的服务提供者 URL,比如:
    // dubbo://127.0.0.1:20880/org.apache.dubbo.samples.annotation.api.GreetingService?anyhost=true&application=samples-annotation-provider&bean.name=providers:dubbo:org.apache.dubbo.samples.annotation.api.GreetingService:1.0.0_annotation&default.deprecated=false&default.dynamic=false&default.register=true&default.timeout=1000&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&interface=org.apache.dubbo.samples.annotation.api.GreetingService&methods=greeting,replyGreeting&pid=23358®ister=true&release=2.7.1&revision=1.0.0_annotation&side=provider×tamp=1561014246471&version=1.0.0_annotation
    final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
    // 向服务提供者与消费者注册表中注册服务提供者
    ProviderInvokerWrapper providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
            registryUrl, registeredProviderUrl);
    //to judge if we need to delay publish
    boolean register = registeredProviderUrl.getParameter("register", true);
    if (register) {
        register(registryUrl, registeredProviderUrl);
        providerInvokerWrapper.setReg(true);
    }

    // 已过时的代码,订阅2.6.x或之前的覆盖规则。
    // 向注册中心进行订阅 override 数据
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    exporter.setRegisterUrl(registeredProviderUrl);
    exporter.setSubscribeUrl(overrideSubscribeUrl);
    // 创建并返回 DestroyableExporter
    return new DestroyableExporter<>(exporter);
}

上面代码看起来比较复杂,主要做如下一些操作:

  1. 调用 doLocalExport 导出服务
  2. 向注册中心注册服务
  3. 向注册中心进行订阅 override 数据
  4. 创建并返回 DestroyableExporter

在以上操作中,除了创建并返回 DestroyableExporter 没什么难度外,其他几步操作都不是很简单。这其中,导出服务和注册服务是本章要重点分析的逻辑。 订阅 override 数据并非本文重点内容,后面会简单介绍一下。下面先来分析 doLocalExport 方法的逻辑,如下:

private  ExporterChangeableWrapper doLocalExport(final Invoker originInvoker, URL providerUrl) {
    String key = getCacheKey(originInvoker);

    return (ExporterChangeableWrapper) bounds.computeIfAbsent(key, s -> {
        Invoker invokerDelegete = new InvokerDelegate<>(originInvoker, providerUrl);
        return new ExporterChangeableWrapper<>((Exporter) protocol.export(invokerDelegete), originInvoker);
    });
}

我们把重点放在 Protocol 的 export 方法上。假设运行时协议为 dubbo,此处的 protocol 变量会在运行时加载 DubboProtocol,并调用 DubboProtocol 的 export 方法。所以,接下来我们目光转移到 DubboProtocol 的 export 方法上,相关分析如下:

public  Exporter export(Invoker invoker) throws RpcException {
    URL url = invoker.getUrl();

    // 获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。比如:
    // demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880
    String key = serviceKey(url);
    // 创建 DubboExporter
    DubboExporter exporter = new DubboExporter(invoker, key, exporterMap);
    // 将  键值对放入缓存中
    exporterMap.put(key, exporter);

    // 本地存根相关代码
    Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
    Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
    if (isStubSupportEvent && !isCallbackservice) {
        String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
        if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
            // 省略日志打印代码
        } else {
            stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
        }
    }

    // 启动服务器
    openServer(url);
    // 优化序列化
    optimizeSerialization(url);
    return exporter;
}

如上,我们重点关注 DubboExporter 的创建以及 openServer 方法,其他逻辑看不懂也没关系,不影响理解服务导出过程。另外,DubboExporter 的代码比较简单,就不分析了。下面分析 openServer 方法。

private void openServer(URL url) {
    // 获取 host:port,并将其作为服务器实例的 key,用于标识当前的服务器实例
    String key = url.getAddress();
    boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
    if (isServer) {
        // 访问缓存
        ExchangeServer server = serverMap.get(key);
        if (server == null) {
            // 创建服务器实例
            serverMap.put(key, createServer(url));
        } else {
            // 服务器已创建,则根据 url 中的配置重置服务器
            server.reset(url);
        }
    }
}

如上,在同一台机器上(单网卡),同一个端口上仅允许启动一个服务器实例。若某个端口上已有服务器实例,此时则调用 reset 方法重置服务器的一些配置。考虑到篇幅问题,关于服务器实例重置的代码就不分析了。接下来分析服务器实例的创建过程。如下:

private ExchangeServer createServer(URL url) {
    url = URLBuilder.from(url)
            // send readonly event when server closes, it's enabled by default
            .addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
            // enable heartbeat by default
            // 添加心跳检测配置到 url 中
            .addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT))
            // 添加编码解码器参数
            .addParameter(Constants.CODEC_KEY, DubboCodec.NAME)
            .build();
    // 获取 server 参数,默认为 netty
    String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);
    // 通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常
    if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
        throw new RpcException("Unsupported server type: " + str + ", url: " + url);
    }

    ExchangeServer server;
    try {
        // 创建 ExchangeServer
        server = Exchangers.bind(url, requestHandler);
    } catch (RemotingException e) {
        throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
    }
    // 获取 client 参数,可指定 netty,mina
    str = url.getParameter(Constants.CLIENT_KEY);
    if (str != null && str.length() > 0) {
        // 获取所有的 Transporter 实现类名称集合,比如 supportedTypes = [netty, mina]
        Set supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
        // 检测当前 Dubbo 所支持的 Transporter 实现类名称列表中,
        // 是否包含 client 所表示的 Transporter,若不包含,则抛出异常
        if (!supportedTypes.contains(str)) {
            throw new RpcException("Unsupported client type: " + str);
        }
    }

    return server;
}

如上,createServer 包含三个核心的逻辑。第一是检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常。第二是创建服务器实例。第三是检测是否支持 client 参数所表示的 Transporter 拓展,不存在也是抛出异常。两次检测操作所对应的代码比较直白了,无需多说。但创建服务器的操作目前还不是很清晰,我们继续往下看。

public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handler == null) {
        throw new IllegalArgumentException("handler == null");
    }
    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    // 获取 Exchanger,默认为 HeaderExchanger。
    // 紧接着调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例
    return getExchanger(url).bind(url, handler);
}

上面代码比较简单,就不多说了。下面看一下 HeaderExchanger 的 bind 方法。

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
    // 创建 HeaderExchangeServer 实例,该方法包含了多个逻辑,分别如下:
    //   1. new HeaderExchangeHandler(handler)
    //	 2. new DecodeHandler(new HeaderExchangeHandler(handler))
    //   3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
    return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

HeaderExchanger 的 bind 方法包含的逻辑比较多,但目前我们仅需关心 Transporters 的 bind 方法逻辑即可。该方法的代码如下:

public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handlers == null || handlers.length == 0) {
        throw new IllegalArgumentException("handlers == null");
    }
    ChannelHandler handler;
    if (handlers.length == 1) {
        handler = handlers[0];
    } else {
    	// 如果 handlers 元素数量大于1,则创建 ChannelHandler 分发器
        handler = new ChannelHandlerDispatcher(handlers);
    }
    // 获取自适应 Transporter 实例,并调用实例方法
    return getTransporter().bind(url, handler);
}

如上,getTransporter() 方法获取的 Transporter 是在运行时动态创建的,类名为 TransporterAdaptive,也就是自适应拓展类。TransporterAdaptive 会在运行时根据传入的 URL 参数决定加载什么类型的 Transporter,默认为 NettyTransporter。下面我们继续跟下去,这次分析的是 NettyTransporter 的 bind 方法。

public Server bind(URL url, ChannelHandler listener) throws RemotingException {
	// 创建 NettyServer
	return new NettyServer(url, listener);
}

这里仅有一句创建 NettyServer 的代码,无需多说,我们继续向下看。

public class NettyServer extends AbstractServer implements Server {
    public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
    }
    // 其余代码略
}
public abstract class AbstractServer extends AbstractEndpoint implements Server {
    public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
        // 调用父类构造方法,从url中找出codec、timeout、connectTimeout
        super(url, handler);
        localAddress = getUrl().toInetSocketAddress();
        // 获取 ip 和端口
        String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
        int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
        if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
            // 设置 ip 为 0.0.0.0
            bindIp = Constants.ANYHOST_VALUE;
        }
        bindAddress = new InetSocketAddress(bindIp, bindPort);
        // 获取最大可接受连接数
        this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
        this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
        try {
            // 调用模板方法 doOpen 启动服务器
            doOpen();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
            }
        } catch (Throwable t) {
            throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
                    + " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
        }
        //fixme replace this with better method
        DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
        executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
    }
    // 其余代码略
}

上面代码多为赋值代码,不需要多讲。我们重点关注 doOpen 抽象方法,该方法需要子类实现。下面回到 NettyServer 中。

protected void doOpen() throws Throwable {
    // 创建 ServerBootstrap
    bootstrap = new ServerBootstrap();
    // 创建 boss 和 worker 线程池
    bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
    workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
            new DefaultThreadFactory("NettyServerWorker", true));

    final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
    channels = nettyServerHandler.getChannels();

    bootstrap.group(bossGroup, workerGroup)
            .channel(NioServerSocketChannel.class)
            .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
            .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
            .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
            .childHandler(new ChannelInitializer() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    // FIXME: should we use getTimeout()?
                    int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
                    // 获取配置的Codec、
                    NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                    ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
                            .addLast("decoder", adapter.getDecoder())
                            .addLast("encoder", adapter.getEncoder())
                            .addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
                            .addLast("handler", nettyServerHandler);
                }
            });
    // 绑定到指定的 ip 和端口上
    ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
    channelFuture.syncUninterruptibly();
    channel = channelFuture.channel();

}

以上就是 NettyServer 创建的过程,dubbo2.7.0之前 默认使用的 NettyServer 是基于 netty 3.x 版本实现的,从dubbo2.7.0开始使用了基于 netty 4.x 版本的 NettyServer。

2.4、服务注册

本节我们来分析服务注册过程,服务注册操作对于 Dubbo 来说不是必需的,通过服务直连的方式就可以绕过注册中心。但通常我们不会这么做,直连方式不利于服务治理,仅推荐在测试服务时使用。对于 Dubbo 来说,注册中心虽不是必需,但却是必要的。因此,关于注册中心以及服务注册相关逻辑,我们也需要搞懂。

本节内容以 Zookeeper 注册中心作为分析目标,其他类型注册中心大家可自行分析。下面从服务注册的入口方法开始分析,我们把目光再次移到 RegistryProtocol 的 export 方法上。如下:

public  Exporter export(final Invoker originInvoker) throws RpcException {
    URL registryUrl = getRegistryUrl(originInvoker);
    // url to export locally
    URL providerUrl = getProviderUrl(originInvoker);

    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    // provider://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?category=configurators&check=false&anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    // 导出服务
    final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl);

    // 获取注册中心
    final Registry registry = getRegistry(originInvoker);
    final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
    ProviderInvokerWrapper providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
            registryUrl, registeredProviderUrl);
    //to judge if we need to delay publish
    boolean register = registeredProviderUrl.getParameter("register", true);
    if (register) {
        // 注册服务
        register(registryUrl, registeredProviderUrl);
        providerInvokerWrapper.setReg(true);
    }

    // Deprecated! Subscribe to override rules in 2.6.x or before.
    // 订阅 override 数据
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    exporter.setRegisterUrl(registeredProviderUrl);
    exporter.setSubscribeUrl(overrideSubscribeUrl);
    //Ensure that a new exporter instance is returned every time export
    return new DestroyableExporter<>(exporter);
}

RegistryProtocol 的 export 方法包含了服务导出,注册,以及数据订阅等逻辑。其中服务导出逻辑上一节已经分析过了,本节将分析服务注册逻辑,相关代码如下:

public void register(URL registryUrl, URL registedProviderUrl) {
    // 获取 Registry
    Registry registry = registryFactory.getRegistry(registryUrl);
    // 注册服务
    registry.register(registedProviderUrl);
}

register 方法包含两步操作,第一步是获取注册中心实例,第二步是向注册中心注册服务。接下来分两节内容对这两步操作进行分析。

2.4.1、创建注册中心

本节内容以 Zookeeper 注册中心为例进行分析。下面先来看一下 getRegistry 方法的源码,这个方法由 AbstractRegistryFactory 实现。如下:

@Override
public Registry getRegistry(URL url) {
    url = URLBuilder.from(url)
            .setPath(RegistryService.class.getName())
            .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
            .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY)
            .build();
    String key = url.toServiceStringWithoutResolving();
    // Lock the registry access process to ensure a single instance of the registry
    LOCK.lock();
    try {
        // 访问缓存
        Registry registry = REGISTRIES.get(key);
        if (registry != null) {
            return registry;
        }
        // 缓存未命中,创建 Registry 实例 by spi/ioc
        registry = createRegistry(url);
        if (registry == null) {
            throw new IllegalStateException("Can not create registry " + url);
        }
        REGISTRIES.put(key, registry);
        return registry;
    } finally {
        // Release the lock
        LOCK.unlock();
    }
}

protected abstract Registry createRegistry(URL url);

如上,getRegistry 方法先访问缓存,缓存未命中则调用 createRegistry 创建 Registry,然后写入缓存。这里的 createRegistry 是一个模板方法,由具体的子类实现。因此,下面我们到 ZookeeperRegistryFactory 中探究一番。

public class ZookeeperRegistryFactory extends AbstractRegistryFactory {

    // zookeeperTransporter 由 SPI 在运行时注入,类型为 ZookeeperTransporter$Adaptive
    private ZookeeperTransporter zookeeperTransporter;

    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
        this.zookeeperTransporter = zookeeperTransporter;
    }

    @Override
    public Registry createRegistry(URL url) {
        // 创建 ZookeeperRegistry
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }
}

ZookeeperRegistryFactory 的 createRegistry 方法仅包含一句代码,无需解释,继续跟下去。

public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
    super(url);
    if (url.isAnyHost()) {
        throw new IllegalStateException("registry address == null");
    }
    
    // 获取组名,默认为 dubbo
    String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
    if (!group.startsWith(Constants.PATH_SEPARATOR)) {
        // group = "/" + group
        group = Constants.PATH_SEPARATOR + group;
    }
    this.root = group;
    // 创建 Zookeeper 客户端,默认为 CuratorZookeeperTransporter
    zkClient = zookeeperTransporter.connect(url);
    // 添加状态监听器
    zkClient.addStateListener(new StateListener() {
        @Override
        public void stateChanged(int state) {
            if (state == RECONNECTED) {
                try {
                    recover();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    });
}

在上面的代码代码中,我们重点关注 ZookeeperTransporter 的 connect 方法调用,这个方法用于创建 Zookeeper 客户端。创建好 Zookeeper 客户端,意味着注册中心的创建过程就结束了。接下来,再来分析一下 Zookeeper 客户端的创建过程。

前面说过,这里的 zookeeperTransporter 类型为自适应拓展类,因此 connect 方法会在被调用时决定加载什么类型的 ZookeeperTransporter 拓展,默认为 CuratorZookeeperTransporter。CuratorZookeeperTransporter继承于AbstractZookeeperTransporter,connect方法的实现在其中。

public ZookeeperClient connect(URL url) {
    ZookeeperClient zookeeperClient;
    // 获取zookeeper地址包括backup地址
    List addressList = getURLBackupAddress(url);
    // 该字段定义zookeeper服务器,包括协议,主机,端口,用户名,密码
    // 先从缓存中取出
    if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) {
        logger.info("find valid zookeeper client from the cache for address: " + url);
        return zookeeperClient;
    }
    // 避免创建太多连接,因此添加锁定
    synchronized (zookeeperClientMap) {
        if ((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) != null && zookeeperClient.isConnected()) {
            logger.info("find valid zookeeper client from the cache for address: " + url);
            return zookeeperClient;
        }
        // 缓存中不存在则由子类创建一个新的客户端
        zookeeperClient = createZookeeperClient(toClientURL(url));
        logger.info("No valid zookeeper client found from cache, therefore create a new client for url. " + url);
        writeToClientMap(addressList, zookeeperClient);
    }
    return zookeeperClient;
}

上述代码先从缓存取出已完成连接的客户端,如果缓存没有(也可能断开连接),则使用模板方法createZookeeperClient由子类完成新连接客户端的创建,下面是CuratorZookeeperTransporter的实现。

public class CuratorZookeeperTransporter extends AbstractZookeeperTransporter {
    @Override
    public ZookeeperClient createZookeeperClient(URL url) {
        return new CuratorZookeeperClient(url);
    }
}

CuratorZookeeperClient 构造方法主要用于创建和启动 CuratorFramework 实例。以上基本上都是 Curator 框架的代码,大家如果对 Curator 框架不是很了解,可以参考 Curator 官方文档。

public class CuratorZookeeperClient extends AbstractZookeeperClient {

    static final Charset charset = Charset.forName("UTF-8");
    private final CuratorFramework client;
    private Map treeCacheMap = new ConcurrentHashMap<>();


    public CuratorZookeeperClient(URL url) {
        super(url);
        try {
            int timeout = url.getParameter(Constants.TIMEOUT_KEY, 5000);
            // 创建 CuratorFramework 构造器
            CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
                    .connectString(url.getBackupAddress())
                    .retryPolicy(new RetryNTimes(1, 1000))
                    .connectionTimeoutMs(timeout);
            String authority = url.getAuthority();
            if (authority != null && authority.length() > 0) {
                builder = builder.authorization("digest", authority.getBytes());
            }
            client = builder.build();
            // 添加监听器
            client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
                @Override
                public void stateChanged(CuratorFramework client, ConnectionState state) {
                    if (state == ConnectionState.LOST) {
                        CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
                    } else if (state == ConnectionState.CONNECTED) {
                        CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
                    } else if (state == ConnectionState.RECONNECTED) {
                        CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
                    }
                }
            });
            client.start();
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
}

本节分析了 ZookeeperRegistry 实例的创建过程,整个过程并不是很复杂。大家在看完分析后,可以自行调试,以加深理解。现在注册中心实例创建好了,接下来要做的事情是向注册中心注册服务,我们继续往下看。

2.4.2、节点创建

以 Zookeeper 为例,所谓的服务注册,本质上是将服务配置数据写入到 Zookeeper 的某个路径的节点下。为了让大家有一个直观的了解,下面我们将 Dubbo 的 demo 跑起来,然后通过 Zookeeper 可视化客户端 ZooInspector 查看节点数据。如下:

【Dubbo源码分析】服务导出_第1张图片

从上图中可以看到 com.alibaba.dubbo.demo.DemoService 这个服务对应的配置信息(存储在 URL 中)最终被注册到了 /dubbo/com.alibaba.dubbo.demo.DemoService/providers/ 节点下。搞懂了服务注册的本质,那么接下来我们就可以去阅读服务注册的代码了。服务注册的接口为 register(URL),这个方法定义在 FailbackRegistry 抽象类中。代码如下:

public void register(URL url) {
    super.register(url);
    failedRegistered.remove(url);
    failedUnregistered.remove(url);
    try {
        // 模板方法,由子类实现
        doRegister(url);
    } catch (Exception e) {
        Throwable t = e;

        // 获取 check 参数,若 check = true 将会直接抛出异常
        boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                && url.getParameter(Constants.CHECK_KEY, true)
                && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
        boolean skipFailback = t instanceof SkipFailbackWrapperException;
        if (check || skipFailback) {
            if (skipFailback) {
                t = t.getCause();
            }
            throw new IllegalStateException("Failed to register");
        } else {
            logger.error("Failed to register");
        }

        // 记录注册失败的链接
        failedRegistered.add(url);
    }
}

protected abstract void doRegister(URL url);

如上,我们重点关注 doRegister 方法调用即可,其他的代码先忽略。doRegister 方法是一个模板方法,因此我们到 FailbackRegistry 子类 ZookeeperRegistry 中进行分析。如下:

protected void doRegister(URL url) {
    try {
        // 通过 Zookeeper 客户端创建节点,节点路径由 toUrlPath 方法生成,路径格式如下:
        //   /${group}/${serviceInterface}/providers/${url}
        // 比如
        //   /dubbo/org.apache.dubbo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1......
        zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
    } catch (Throwable e) {
        throw new RpcException("Failed to register...");
    }
}

如上,ZookeeperRegistry 在 doRegister 中调用了 Zookeeper 客户端创建服务节点。节点路径由 toUrlPath 方法生成,该方法逻辑不难理解,就不分析了。接下来分析 create 方法,如下:

public void create(String path, boolean ephemeral) {
    if (!ephemeral) {
        // 如果要创建的节点类型非临时节点,那么这里要检测节点是否存在
        if (checkExists(path)) {
            return;
        }
    }
    int i = path.lastIndexOf('/');
    if (i > 0) {
        // 递归创建上一级路径
        create(path.substring(0, i), false);
    }
    
    // 根据 ephemeral 的值创建临时或持久节点
    if (ephemeral) {
        createEphemeral(path);
    } else {
        createPersistent(path);
    }
}

上面方法先是通过递归创建当前节点的上一级路径,然后再根据 ephemeral 的值决定创建临时还是持久节点。createEphemeral 和 createPersistent 这两个方法都比较简单,这里简单分析其中的一个。如下:

public void createEphemeral(String path) {
    try {
        // 通过 Curator 框架创建节点
        client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
    } catch (NodeExistsException e) {
    } catch (Exception e) {
        throw new IllegalStateException(e.getMessage(), e);
    }
}

好了,到此关于服务注册的过程就分析完了。整个过程可简单总结为:先创建注册中心实例,之后再通过注册中心实例注册服务。本节先到这,接下来分析数据订阅过程。

2.5、订阅 override 数据(动态配置中心)

在 2.7 之前的版本,Dubbo 只配备了注册中心,主流使用的注册中心为 zookeeper。所有的元数据都放到了注册中心中,这造成了一系列的问题:推送量大 -> 存储数据量大 -> 网络传输量大 -> 延迟严重。

从2.7版本开始增加了配置中心,可以将动态配置迁移到配置中心,但为了兼容之前的版本保留了在注册中心动态修改配置的功能。让我们再次回到RegistryProtocol#export 方法来分析订阅 override 数据的过程。

public  Exporter export(final Invoker originInvoker) throws RpcException {
    URL registryUrl = getRegistryUrl(originInvoker);
    // url to export locally
    URL providerUrl = getProviderUrl(originInvoker);

    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);

    // Deprecated! Subscribe to override rules in 2.6.x or before.
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
}

上述代码只保留了订阅 override 数据相关的代码便于我们来分析。接下来分别分析配置中心和注册中心订阅 override 数据,不管使用哪种方式都涉及三个步骤:注册订阅、构建Configurator、更新配置。

2.5.1、配置中心订阅override 数据

Configurator是一个配置对象,configure方法可对方法参数provider url进行配置,返回一个新的配置后的URL。

先看overrideUrlWithConfig方法实现。

private URL overrideUrlWithConfig(URL providerUrl, OverrideListener listener) {
    // 在导出服务之前获取现有配置规则并覆盖提供者URL。
    providerUrl = providerConfigurationListener.overrideUrl(providerUrl);
    ServiceConfigurationListener serviceConfigurationListener = new ServiceConfigurationListener(providerUrl, listener);
    serviceConfigurationListeners.put(providerUrl.getServiceKey(), serviceConfigurationListener);
    return serviceConfigurationListener.overrideUrl(providerUrl);
}

overrideUrlWithConfig方法目的是在导出服务之前通过ProviderConfigurationListener和ServiceConfigurationListener获取现有配置规则并覆盖提供者URL。这两个类的overrideUrl方法实现相同,都是调用已有的configurators来完成对providerUrl的配置改造。

private  URL overrideUrl(URL providerUrl) {
    return RegistryProtocol.getConfigedInvokerUrl(configurators, providerUrl);
}

configurators是在ProviderConfigurationListener和ServiceConfigurationListener实例化的时候创建的,下面看一下ProviderConfigurationListener构造方法。

public ProviderConfigurationListener() {
    this.initWith(ApplicationModel.getApplication() + CONFIGURATORS_SUFFIX);
}
protected final void initWith(String key) {
    // 获取一个DynamicConfiguration
    DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration();
    // 增加ProviderConfigurationListener作为监听器,一旦DynamicConfiguration动态配置发生变化会调用ProviderConfigurationListener的process方法
    dynamicConfiguration.addListener(key, this);
    // 如果是zookeeper获取config.namespace/group/config/key的内容yml格式,参考ConfiguratorConfig
    String rawConfig = dynamicConfiguration.getConfig(key);
    if (!StringUtils.isEmpty(rawConfig)) {
        process(new ConfigChangeEvent(key, rawConfig));
    }
}

public void process(ConfigChangeEvent event) {
    if (logger.isInfoEnabled()) {
        logger.info("Notification of overriding rule, change type is: " + event.getChangeType() +
                ", raw config content is:\n " + event.getValue());
    }

    if (event.getChangeType().equals(ConfigChangeType.DELETED)) {
        configurators.clear();
    } else {
        try {
            // ConfigParser.parseConfigurators将配置内容转换成URL在将配置URL传入OverrideConfigurator返回
            configurators = Configurator.toConfigurators(ConfigParser.parseConfigurators(event.getValue()))
                    .orElse(configurators);
        } catch (Exception e) {
            logger.error("Failed to parse raw dynamic config and it will not take effect, the raw config is: " +
                    event.getValue(), e);
            return;
        }
    }
    // 更新配置
    notifyOverrides();
}

configurators原本是一个空集合,在构造方法中调用了initWith方法,这个方法中会获取一个DynamicConfiguration,通过这个对象的getConfig方法返回一个rawConfig,将其包装成一个ConfigChangeEvent传入process方法,process方法将这个Event对象转换成了configurators。

dynamicConfiguration是通过Environment.getInstance().getDynamicConfiguration()取得的,Environment中的dynamicConfiguration是在ServiceConfig调用checkAndUpdateSubConfigs方法进行配置检查阶段完成创建的。这个方法中会调用checkRegistry方法,checkRegistry方法中调用useRegistryForConfigIfNecessary方法,这个方法出于兼容性目的,如果注册表协议是zookeeper并且没有明确指定配置中心,使用registry作为默认配置中心。

private void useRegistryForConfigIfNecessary() {
    //遍历所有的注册中心,找到第一个是ZookeeperProtocol,创建一个使用这个ZookeeperProtocol配置的ConfigCenterConfig
    registries.stream().filter(RegistryConfig::isZookeeperProtocol).findFirst().ifPresent(rc -> {
        // we use the loading status of DynamicConfiguration to decide whether ConfigCenter has been initiated.
        Environment.getInstance().getDynamicConfiguration().orElseGet(() -> {
            // 创建一个使用zookeeper作为注册中心的配置中心
            ConfigManager configManager = ConfigManager.getInstance();
            ConfigCenterConfig cc = configManager.getConfigCenter().orElse(new ConfigCenterConfig());
            cc.setProtocol(rc.getProtocol());
            cc.setAddress(rc.getAddress());
            cc.setHighestPriority(false);
            // 开启配置中心
            setConfigCenter(cc);
            startConfigCenter();
            return null;
        });
    });
}

创建配置中心后调用了startConfigCenter方法来开启配置中心,DynamicConfiguration就是在开启配置中心的过程中创建的。

void startConfigCenter() {
    if (configCenter == null) {
        ConfigManager.getInstance().getConfigCenter().ifPresent(cc -> this.configCenter = cc);
    }

    if (this.configCenter != null) {
        // TODO there may have duplicate refresh
        this.configCenter.refresh();
        // 准备Environment对象
        prepareEnvironment();
    }
    ConfigManager.getInstance().refreshAll();
}

上述代码中关于配置中心的配置刷新我们先不关心,主要看prepareEnvironment方法内部对Environment的初始化。

private void prepareEnvironment() {
    if (configCenter.isValid()) {
        if (!configCenter.checkOrUpdateInited()) {
            return;
        }
        // configCenter url zookeeper://127.0.0.1:2181/ConfigCenterConfig?address=zookeeper://127.0.0.1:2181&check=true&configFile=dubbo.properties&group=dubbo&highestPriority=false&namespace=dubbo&prefix=dubbo.config-center&timeout=3000&valid=true
        // 使用DynamicConfigurationFactory创建一个DynamicConfiguration
        DynamicConfiguration dynamicConfiguration = getDynamicConfiguration(configCenter.toUrl());
        // 如果是ZookeeperDynamicConfiguration,获取/config.namespace/group/config/key的内容
        String configContent = dynamicConfiguration.getConfig(configCenter.getConfigFile(), configCenter.getGroup());

        String appGroup = application != null ? application.getName() : null;
        String appConfigContent = null;
        if (StringUtils.isNotEmpty(appGroup)) {
            appConfigContent = dynamicConfiguration.getConfig
                    (StringUtils.isNotEmpty(configCenter.getAppConfigFile()) ? configCenter.getAppConfigFile() : configCenter.getConfigFile(),
                     appGroup
                    );
        }
        try {
            //更新Environment
            Environment.getInstance().setConfigCenterFirst(configCenter.isHighestPriority());
            Environment.getInstance().updateExternalConfigurationMap(parseProperties(configContent));
            Environment.getInstance().updateAppExternalConfigurationMap(parseProperties(appConfigContent));
        } catch (IOException e) {
            throw new IllegalStateException("Failed to parse configurations from Config Center.", e);
        }
    }
}

getDynamicConfiguration方法会使用SPI根据url返回一个DynamicConfigurationFactory,这里就是ZookeeperDynamicConfigurationFactory,ZookeeperDynamicConfigurationFactory根据传入的url创建一个ZookeeperDynamicConfiguration。

public abstract class AbstractDynamicConfigurationFactory implements DynamicConfigurationFactory {

    private volatile DynamicConfiguration dynamicConfiguration;

    @Override
    public DynamicConfiguration getDynamicConfiguration(URL url) {
        if (dynamicConfiguration == null) {
            synchronized (this) {
                if (dynamicConfiguration == null) {
                    dynamicConfiguration = createDynamicConfiguration(url);
                }
            }
        }
        return dynamicConfiguration;
    }

    protected abstract DynamicConfiguration createDynamicConfiguration(URL url);
}
public class ZookeeperDynamicConfigurationFactory extends AbstractDynamicConfigurationFactory {

    private ZookeeperTransporter zookeeperTransporter;

    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
        this.zookeeperTransporter = zookeeperTransporter;
    }

    @Override
    protected DynamicConfiguration createDynamicConfiguration(URL url) {
        return new ZookeeperDynamicConfiguration(url, zookeeperTransporter);
    }
}

ZookeeperDynamicConfiguration构造方法中会监听zookeeper节点/config.namespace/group/config下的子节点一旦数据发生变化会调用ProviderConfigurationListener的process方法更新配置。

以上就是使用配置中心的方式进行动态配置,下面看使用2.7版本前的zookeeper订阅方式。

2.5.2、Dubbo2.7版本前订阅override 数据

订阅方法subscribe在抽象类FailbackRegistry中实现。

订阅符合条件的注册数据,并在注册数据更改时自动推送。
订阅需要支持的约定:
1.当URL设置check = false参数时。注册失败时,不会抛出异常并在后台重试。
2.当URL设置category = routers时,它仅通知指定的分类数据。多个分类用逗号分隔,并允许星号匹配,这表示订阅了所有分类数据。
3.允许接口,组,版本和分类器作为条件查询,例如:interface=org.apache.dubbo.foo.BarService&version=1.0.0
4.查询条件允许星号匹配,订阅所有接口的所有版本的所有版本,例如:interface=*&group=*&version=*&classifier=*
5.重新启动注册表并进行网络抖动时,需要自动恢复订阅请求。
6.允许具有相同URL但不同参数的URL共存,它们不能相互覆盖。
7.当第一个通知完成然后返回时,必须阻止订阅过程。
public void subscribe(URL url, NotifyListener listener) {
    // 父类AbstractRegistry是实现:参数非空判断,保存listener到subscribed
    super.subscribe(url, listener);
    // 当订阅失败会保存失败的url->listener,对此url重新订阅会删除失败的
    removeFailedSubscribed(url, listener);
    try {
        // 向服务器端发送订阅请求
        doSubscribe(url, listener);
    } catch (Exception e) {
        Throwable t = e;
        // 当订阅失败,读取本地缓存文件System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache"
        List urls = getCacheUrls(url);
        if (CollectionUtils.isNotEmpty(urls)) {
            // 通知更新
            notify(url, listener, urls);
            logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t);
        } else {
            // If the startup detection is opened, the Exception is thrown directly.
            boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                    && url.getParameter(Constants.CHECK_KEY, true);
            boolean skipFailback = t instanceof SkipFailbackWrapperException;
            if (check || skipFailback) {
                if (skipFailback) {
                    t = t.getCause();
                }
                throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t);
            } else {
                logger.error("Failed to subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
            }
        }

        // 将失败的注册请求记录到失败的列表中,定期重试
        addFailedSubscribed(url, listener);
    }
}

doSubscribe方法是模板方法由子类实现这里我们看ZookeeperRegistry的实现。

public void doSubscribe(final URL url, final NotifyListener listener) {
    try {
        // 这个if分支是当url.getServiceInterface()时*的时候需要监听group下的所有子节点
        // 然后针对每个子节点在做subscribe监听,我们直接看else分支即可
        if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
            // group
            String root = toRootPath();
            ConcurrentMap listeners = zkListeners.get(url);
            if (listeners == null) {
                zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
                listeners = zkListeners.get(url);
            }
            ChildListener zkListener = listeners.get(listener);
            if (zkListener == null) {
                listeners.putIfAbsent(listener, (parentPath, currentChilds) -> {
                    for (String child : currentChilds) {
                        child = URL.decode(child);
                        if (!anyServices.contains(child)) {
                            anyServices.add(child);
                            subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
                                    Constants.CHECK_KEY, String.valueOf(false)), listener);
                        }
                    }
                });
                zkListener = listeners.get(listener);
            }
            zkClient.create(root, false);
            List services = zkClient.addChildListener(root, zkListener);
            if (CollectionUtils.isNotEmpty(services)) {
                for (String service : services) {
                    service = URL.decode(service);
                    anyServices.add(service);
                    subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
                            Constants.CHECK_KEY, String.valueOf(false)), listener);
                }
            }
        } else {
            List urls = new ArrayList<>();
            // 格式/dubbo/org.apache.dubbo.samples.local.api.DemoService/configurators
            for (String path : toCategoriesPath(url)) {
                ConcurrentMap listeners = zkListeners.get(url);
                // 先查缓存
                if (listeners == null) {
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
                    listeners = zkListeners.get(url);
                }
                ChildListener zkListener = listeners.get(listener);
                if (zkListener == null) {
                    // 创建一个ChildListener监听一个接下下的子节点,这里指的是path,当path下的节点发生变化就会调用ZookeeperRegistry的notify方法完成url配置
                    listeners.putIfAbsent(listener, (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)));
                    zkListener = listeners.get(listener);
                }
                // 创建path节点
                zkClient.create(path, false);
                // 使用zkListener监听path下所有子节点
                List children = zkClient.addChildListener(path, zkListener);
                // path节点创建成功,这里不会返回null
                if (children != null) {
                    // 这里children是在dubbo-admin配置的override数据
                    // 从children选出与url相匹配的override配置,如果children是空集合
                    // 则返回一个empty协议的URL如
                    // empty://127.0.0.1:20890/org.apache.dubbo.samples.local.api.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.samples.local.api.DemoService&bind.ip=10.92.214.15&bind.port=20890&category=configurators&check=false&default.deprecated=false&default.dynamic=false&default.register=true&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&interface=org.apache.dubbo.samples.local.api.DemoService&methods=sayHello&pid=8706®ister=true&release=2.7.1&side=provider×tamp=1559617644296
                    urls.addAll(toUrlsWithEmpty(url, path, children));
                }
            }
            // 通知提供者URL的更改。
            notify(url, listener, urls);
        }
    } catch (Throwable e) {
        throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

doSubscribe方法就是监听/group/serviceInterface/configurators下的节点,一旦子节点发生变化则调用ChildListener的回调,回调方法会调用ZookeeperRegistry的notify方法完成override 数据通知更新。下面看一下notify的实现。

@Override
protected void notify(URL url, NotifyListener listener, List urls) {
    if (url == null) {
        throw new IllegalArgumentException("notify url == null");
    }
    if (listener == null) {
        throw new IllegalArgumentException("notify listener == null");
    }
    try {
        doNotify(url, listener, urls);
    } catch (Exception t) {
        // 将失败的注册请求记录到失败的列表中,定期重试
        addFailedNotified(url, listener, urls);
        logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
    }
}

在FailbackRegistry中doNotify方法的通知部分交由父类AbstractRegistry完成它负责记录通知失败后定期重试。我们看一下AbstractRegistry.notify实现原理。

protected void notify(URL url, NotifyListener listener, List urls) {
    if (url == null) {
        throw new IllegalArgumentException("notify url == null");
    }
    if (listener == null) {
        throw new IllegalArgumentException("notify listener == null");
    }
    if ((CollectionUtils.isEmpty(urls))
            && !Constants.ANY_VALUE.equals(url.getServiceInterface())) {
        logger.warn("Ignore empty notify urls for subscribe url " + url);
        return;
    }
    if (logger.isInfoEnabled()) {
        logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
    }
    // keep every provider's category.
    Map> result = new HashMap<>();
    for (URL u : urls) {
        // 筛选出于url匹配的override url
        if (UrlUtils.isMatch(url, u)) {
            String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            List categoryList = result.computeIfAbsent(category, k -> new ArrayList<>());
            categoryList.add(u);
        }
    }
    if (result.size() == 0) {
        return;
    }
    Map> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>());
    for (Map.Entry> entry : result.entrySet()) {
        String category = entry.getKey();
        List categoryList = entry.getValue();
        categoryNotified.put(category, categoryList);
        // 更新provider配置URL
        listener.notify(categoryList);
        // 我们将在每次通知后更新缓存文件。当我们的注册表由于网络抖动而导致订阅失败时,我们至少可以返回现有的缓存URL。
        saveProperties(url);
    }
}

notify方法主要做了两件事一个是使用listener更新配置,一个是保存最新的更新到本地缓存文件。

public synchronized void notify(List urls) {
    logger.debug("original override urls: " + urls);
    // 过滤出匹配provider url的配置url列表
    List matchedUrls = getMatchedUrls(urls, subscribeUrl.addParameter(CATEGORY_KEY,
            CONFIGURATORS_CATEGORY));
    logger.debug("subscribe url: " + subscribeUrl + ", override urls: " + matchedUrls);

    // 没有匹配则返回
    if (matchedUrls.isEmpty()) {
        return;
    }
    // 构建Configurator对象
    this.configurators = Configurator.toConfigurators(classifyUrls(matchedUrls, UrlUtils::isConfigurator))
            .orElse(configurators);
    // 使用configurators覆盖原provider url
    doOverrideIfNecessary();
}

public synchronized void doOverrideIfNecessary() {
    final Invoker invoker;
    if (originInvoker instanceof InvokerDelegate) {
        invoker = ((InvokerDelegate) originInvoker).getInvoker();
    } else {
        invoker = originInvoker;
    }
    // 原始url如dubbo://127.0.0.1:20890/org.apache.dubbo.samples.local.api.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.samples.local.api.DemoService&bind.ip=10.92.214.15&bind.port=20890&default.deprecated=false&default.dynamic=false&default.register=true&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&interface=org.apache.dubbo.samples.local.api.DemoService&methods=sayHello&pid=8764®ister=true&release=2.7.1&side=provider×tamp=1559619897120
    URL originUrl = RegistryProtocol.this.getProviderUrl(invoker);
    String key = getCacheKey(originInvoker);
    // 从缓存中取出之前导出的服务
    ExporterChangeableWrapper exporter = bounds.get(key);
    if (exporter == null) {
        logger.warn(new IllegalStateException("error state, exporter should not be null"));
        return;
    }
    // 当前的URl,可能和最开始相比做了多次合并
    URL currentUrl = exporter.getInvoker().getUrl();
    // 使用configurators合并配置
    URL newUrl = getConfigedInvokerUrl(configurators, originUrl);
    // 再次使用serviceConfigurationListeners配置url
    newUrl = getConfigedInvokerUrl(serviceConfigurationListeners.get(originUrl.getServiceKey())
            .getConfigurators(), newUrl);
    newUrl = getConfigedInvokerUrl(providerConfigurationListener.getConfigurators(), newUrl);
    // 如果有更改需要使用新url重新导出服务
    if (!currentUrl.equals(newUrl)) {
        RegistryProtocol.this.reExport(originInvoker, newUrl);
        logger.info("exported provider url changed, origin url: " + originUrl +
                ", old export url: " + currentUrl + ", new export url: " + newUrl);
    }
}
private void saveProperties(URL url) {
    if (file == null) {
        return;
    }

    try {
        StringBuilder buf = new StringBuilder();
        Map> categoryNotified = notified.get(url);
        if (categoryNotified != null) {
            for (List us : categoryNotified.values()) {
                for (URL u : us) {
                    if (buf.length() > 0) {
                        buf.append(URL_SEPARATOR);
                    }
                    buf.append(u.toFullString());
                }
            }
        }
        // serviceKey-->通知URL
        properties.setProperty(url.getServiceKey(), buf.toString());
        // 最新版本
        long version = lastCacheChanged.incrementAndGet();
        // 默认false
        if (syncSaveFile) {
            doSaveProperties(version);
        } else {
            registryCacheExecutor.execute(new SaveProperties(version));
        }
    } catch (Throwable t) {
        logger.warn(t.getMessage(), t);
    }
}

 

你可能感兴趣的:(Dubbo源码分析)