4.[dubbo源码解析]-[配置][详解]org.apche.dubbo.config.ServiceConfig-服务提供者暴露服务配置

1.ServiceConfig–结构图

2.ServiceConfig-服务暴露

2.1.export()

export()先判断是否需要延迟暴露(这里我们使用的是不延迟暴露),然后执行doExport方法。

    /**
    * 暴露服务 同步方法
    */
    public synchronized void export() {
        // 当 export 或者 delay 为null,从 ProviderConfig 对象读取。 @ sjt 1
        if (provider != null) {
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        // 不暴露服务( export = false ) ,则不进行暴露服务逻辑。@ sjt 2
        if (export != null && !export) { // 
            return;
        }
        // //延迟暴露 通过delay参数配置的,延迟暴露,放入单独线程中。@sjt 3
        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
            // 立即暴露 @sjt 4
        } else {
            doExport();
        }
    }
  • @sjt 1: 如果当前exportdelay数据为null,从providerConfig中读取。
  • @sjt 2: 判断是否暴露服务,由dubbo:service export=“true|false”来指定。
  • @sjt 3: 如果启用了 delay 机制,如果 delay 大于0,表示延迟多少毫秒后暴露服务,使用 ScheduledExecutorService [TODO] 延迟调度,最终调用 doExport() 方法。
  • @sjt 4: 执行具体的暴露逻辑 doExport()delay=-1 的处理逻辑( 基于Spring事件机制触发 )。

@From:《博客-聊聊Dubbo(九):核心源码-服务端启动流程1
0 前言》

@From:《源码-org.apache.dubbo.config.ServiceConfig#export()》

2.2.doExport()

doExport()方法先执行一系列的检查方法,然后调用doExportUrls方法。检查方法会检测dubbo的配置是否在Spring配置文件中声明,没有的话读取properties文件初始化。
@From:博客-Dubbo中暴露服务的过程解析

调用链: ServiceBean#afterPropertiesSet --> ServiceConfig#export–> ServiceConfig#doExport

@From:《博客-聊聊Dubbo(九):核心源码-服务端启动流程-2.2 第二步:ServiceConfig#doExport() 暴露服务》

    /**
    * 检查填充配置项,按优先级覆盖
    */
    protected synchronized void doExport() {
        //如果取消暴露服务,直接抛异常
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        //如果已经暴露服务,直接返回
        if (exported) {
            return;
        }
        exported = true;
        // 校验接口名非空
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }
        // 拼接属性配置(环境变量 + properties 属性)到 ProviderConfig 对象 @sjt 1
        checkDefault();
        // 从 ProviderConfig 对象中,读取 application、module、registries、monitor、protocols 配置对象。
        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();
            }
        }
        // 从 ModuleConfig 对象中,读取 registries、monitor 配置对象。
        if (module != null) {
            if (registries == null) {
                registries = module.getRegistries();
            }
            if (monitor == null) {
                monitor = module.getMonitor();
            }
        }
        // 从 ApplicationConfig 对象中,读取 registries、monitor 配置对象。
        if (application != null) {
            if (registries == null) {
                registries = application.getRegistries();
            }
            if (monitor == null) {
                monitor = application.getMonitor();
            }
        }
        // 泛化接口的实现 @sjt 2
        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);
            }
            // 最后处理ServiceConfig,优先级最高
            checkInterfaceAndMethods(interfaceClass, methods);
            // 校验指向的 service 对象
            checkRef();
            generic = Boolean.FALSE.toString();
        }
        // 处理服务接口客户端本地代理( `local` )相关。实际目前已经废弃,使用 `stub` 属性,参见 `AbstractInterfaceConfig#setLocal` 方法。 @sjt 3
        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);
            }
        }
        // 处理服务接口客户端本地代理( `stub` )相关 @sjt 4
        if (stub != null) {
            // 设为 true,表示使用缺省代理类名,即:接口名 + Stub 后缀
            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);
            }
        }    // @sjt 5
             // 校验 ApplicationConfig 配置。
             checkApplication();
             // 校验 RegistryConfig 配置。
             checkRegistry();
            // 校验 ProtocolConfig 配置数组。
            checkProtocol();
             // 读取环境变量和 properties 配置到 ServiceConfig 对象。
            appendProperties(this);
             // 校验 Stub 和 Mock 相关的配置 @sjt 6
             checkStubAndMock(interfaceClass);
            // 服务路径,缺省为接口名
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        // 暴露服务 @sjt 7
        doExportUrls();
        /**
         * @From:[Dubbo源码阅读笔记1](https://www.cnblogs.com/amwyyyy/p/8353504.html)
        * 把要服务提供者信息封装成model,并设置方法可见性
        */
        ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), ref, interfaceClass);
        /**
         * @From:[Dubbo源码阅读笔记1](https://www.cnblogs.com/amwyyyy/p/8353504.html)
        * 放进全局的context中
        */
        ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    }
  • @sjt 1: 如果 dubbo:servce 标签也就是 ServiceBeanprovider 属性为null,调用 appendProperties 方法,填充默认属性,其具体加载顺序:

    1. 从系统属性加载对应参数值,参数键:dubbo.provider.属性名System.getProperty
    2. 从属性配置文件加载对应参数值,可通过系统属性指定属性配置文件: dubbo.properties.file。
    3. 如果dubbo.properties.file未配置,则默认取 dubbo.properties 属性配置文件。
  • @sjt 2: 校验 refinterface 属性。如果 refGenericService,则为Dubbo的泛化实现,然后验证 interface 接口与 ref 引用的类型是否一致。《dubbo文档-示例-泛化引用》

  • @sjt 3: dubbo:service local机制,已经废弃,被stub属性所替换。

  • @sjt 4 处理本地存根 Stub

  • @sjt 5 校验 ServiceBeanapplicationregistryprotocol 是否为空,并从系统属性(优先)、资源文件中填充其属性。系统属性、资源文件属性的配置如下:

    1. application dubbo.application属性名,例如 dubbo.application.name
    2. registry dubbo.registry.属性名例如 dubbo.registry.address
    3. protocol dubbo.protocol.属性名,例如 dubbo.protocol.port
    4. service dubbo.service.属性名,例如 dubbo.service.stub
  • @sjt 6 校验 stubmock 类的合理性,是否是 interface 的实现类。

  • @sjt 7 执行 doExportUrls() 方法暴露服务,重头戏!

  • @sjt 8 将服务提供者信息注册到ApplicationModel实例中。

@From:《[博客]-聊聊Dubbo(九):核心源码-服务端启动流程-2.2 第二步:ServiceConfig#doExport 暴露服务》

@From:《[vip]-芋道源码-精尽 Dubbo 源码分析-API 配置(二)之服务提供者-8. ServiceConfig》

@From 《[源码]-org.apache.dubbo.configServiceConfig#doExport()》

2.3.doExportUrls()

@From:《博客-Dubbo中暴露服务的过程解析》

doExportUrls() 先调用loadRegistries获取所有的注册中心url,然后遍历调用doExportUrlsFor1Protocol方法。对于在标签中指定了 registry属性的Bean,会在加载BeanDefinition的时候就加载了注册中心。

调用链: ServiceBean#afterPropertiesSet --> ServiceConfig#export–> ServiceConfig#doExport

@From:《博客-聊聊Dubbo(九):核心源码-服务端启动流程-2.3 第三步:ServiceConfig#doExportUrls 暴露服务》

    /**
     * @From: [dubbo服务暴露过程源码分析(https://my.oschina.net/u/146130/blog/1630870)
    * 暴露url
    */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private void doExportUrls() {
        获取注册中心信息 @sjt 1
        List<URL> registryURLs = loadRegistries(true);
        //多个协议,暴露多次 @sjt 2
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }
  • @sjt 1: 首先遍历 ServiceBeanList registries(所有注册中心的配置信息),然后将地址封装成URL对象,关于注册中心的所有配置属性,最终转换成url的属性(?属性名=属性值),loadRegistries(true),参数的意思:true,代表服务提供者,false:代表服务消费者,如果是服务提供者,则检测注册中心的配置,如果配置了 register=“false”,则忽略该地址,如果是服务消费者,并配置了 subscribe=“false”则表示不从该注册中心订阅服务,故也不返回。
  • @sjt 2: 遍历配置的所有协议,根据每个协议,向注册中心暴露服务,重点:doExportUrlsFor1Protocol()

@From:《[博客]-聊聊Dubbo(九):核心源码-服务端启动流程-2.2第二步:ServiceConfig#doExportUrls
-暴露服务》



@From:《[vip]-芋道源码-精尽 Dubbo 源码分析-API 配置(二)之服务提供者-8. ServiceConfig》

@From 《[源码]-org.apache.dubbo.configServiceConfig#doExport()》

2.3.doExportUrlsFor1Protocol()

@From:《博客-Dubbo中暴露服务的过程解析》

根据不同的协议将服务以URL形式暴露。如果scope配置为none则不暴露,如果服务未配置成remote,则本地暴露exportLocal,如果未配置成local,则注册服务registryProcotol。

    /**
     * 根据不同的协议将服务以URL形式暴露
     * @param protocolConfig 协议配置对象
     * @param registryURLs 注册中心链接对象数组
     */
    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        // @ sjt 1
        // 协议名 协议名为null是,缺省“dubbo”
        String name = protocolConfig.getName();
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }
        // 将 `side`,`dubbo`,`timestamp`,`pid` 参数,添加到 `map` 集合中。
        Map<String, String> map = new HashMap<String, String>();
        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);// ProviderConfig ,为 ServiceConfig 的默认属性,因此添加 `default` 属性前缀。
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
  • @sjt 1:Map 存储该协议的所有配置参数.包括:协议名称、Dubbo版本、当前系统时间戳、进程ID、application配置、module配置、默认服务提供者参数(ProviderConfig)、协议配置、服务提供 Dubbo:service的属性。
        // @ sjt 2
        // 将 MethodConfig 对象数组,添加到 `map` 集合中。
        //目的是将每个 MethodConfig 和其对应的 ArgumentConfig 对象数组,添加到 map 中。
        if (methods != null && !methods.isEmpty()) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                // 当 配置了 `MethodConfig.retry = false` 时,强制禁用重试
                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 对象数组,添加到 `map` 集合中。
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && !arguments.isEmpty()) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        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();
                                    // target the method, and get its signature
                                    if (methodName.equals(method.getName())) { // 找到指定方法
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) { // 指定单个参数的位置 + 类型
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                // 将 ArgumentConfig 对象,添加到 `map` 集合中。
                                                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 {
                                            // multiple callbacks in the method
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    // 将 ArgumentConfig 对象,添加到 `map` 集合中。
                                                    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());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {// 指定单个参数的位置
                            // 将 ArgumentConfig 对象,添加到 `map` 集合中。
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }


                    }
                }
            } // end of methods for
        }
  • @sjt 2: 如果 dubbo:servicedubbo:method 子标签,则 dubbo:method 以及其子标签的配置属性,都存入到 Map 中,属性名称加上对应的方法名作为前缀。dubbo:method 的子标签 dubbo:argument,其键为方法名.参数序号。
        //@ sjt 3
        //将 generic methods 【revision】 到 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);
            }


            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
            } else {
                map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
  • sjt 3: 添加 methods 键值对,存放 dubbo:service 的所有方法名,多个方法名用 , 隔开,如果是泛化实现,填充 genric=true,methods“*”
        // @ sjt 4
        //将 token 添加到 map 中。
        //From:[令牌验证](http://dubbo.apache.org/zh-cn/docs/user/demos/token-authorization.html)
        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(Constants.TOKEN_KEY, token);
            }
        }
        //@sjt 5
        //From:[本地调用](http://dubbo.apache.org/zh-cn/docs/user/demos/local-call.html)
        if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {//Constants.LOCAL_PROTOCOL='injvm'
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }
  • @sjt5: 如果协议为本地协议( injvm ),则设置 protocolConfig#register 属性为 false ,表示不向注册中心注册服务,在 map 中存储键为 notify,值为 false,表示当注册中心监听到服务提供者发生变化(服务提供者增加、服务提供者减少等)事件时不通知。
        // export service
        //@sjt 6
        // contextPath ,基础路径
        String contextPath = protocolConfig.getContextpath();
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }
  • @sjt6: 设置协议的 contextPath,如果未配置,默认为 /interfacename
        //@sjt 7
        //获得注册到注册中心的服务提供者 Host 。
        //From:[主机绑定](http://dubbo.apache.org/zh-cn/docs/user/demos/hostname-binding.html)
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        //得注册到注册中心的服务提供者 Port
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        //@sjt 8
        // 创建 Dubbo URL 对象
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
        
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }
  • @sjt8: 根据协议名称、协议 host、协议端口、contextPath、相关配置属性(applicationmoduleproviderprotocolConfigservice 及其子标签)构建服务提供者URI
        //@sjt 9
        String scope = url.getParameter(Constants.SCOPE_KEY);
        // don't export when none is configured
        if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) { //@sjt 9.1


            // export to local if the config is not remote (export to remote only when config is remote)
            if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) { //@sjt 9.2
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (registryURLs != null && !registryURLs.isEmpty()) { //@sjt 9.3
                    for (URL registryURL : registryURLs) {
                        url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));//@stj 9.4
                        URL monitorUrl = loadMonitor(registryURL); //@stj 9.5
                        if (monitorUrl != null) {
                            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);
                        }


                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));//@stj 9.6
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);


                        Exporter<?> exporter = protocol.export(wrapperInvoker);//@sjt 9.7
                        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);
                }
            }
        }
        this.urls.add(url);
    }
  • @sjt9: 获取 dubbo:service 标签的 scope 属性,其可选值为 none (不暴露)、local (本地)、remote (远程),如果配置为 none,则不暴露。默认为 local
    • @sjt9.1: 如果 scope 不为remote,则先在本地暴露(injvm ),具体暴露服务的具体实现,将在remote 模式中详细分析。
    • @sjt9.2: 如果 scope 不为 local,则将服务暴露在远程。
    • @sjt9.3: remote 方式,检测当前配置的所有注册中心,如果注册中心不为空,则遍历注册中心,将服务依次在不同的注册中心进行注册。
    • @sjt9.4: 如果 dubbo:servicedynamic 属性未配置, 尝试取 dubbo:registrydynamic 属性,该属性的作用是否启用动态注册,如果设置为 false,服务注册后,其状态显示为 disable,需要人工启用,当服务不可用时,也不会自动移除,同样需要人工处理,此属性不要在生产环境上配置。
    • @sjt9.5: 根据注册中心URL,构建监控中心的URL,如果监控中心URL不为空,则在服务提供者URL上追加 monitor,其值为监控中心URL(已编码)
    • @sjt9.6: 通过动态代理机制创建 InvokerDubbo的远程调用实现类。
    • @sjt9.7: 根据代码6的分析,将调用 RegistryProtocol#export 方法。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值