Dubbo源码深度解析(三)

接上篇博客《Dubbo源码深度解析(二)》继续讲。上篇博客主要讲的是Dubbo的SPI机制,以及Dubbo启动流程的初始化中的部分方法,本篇博客将继续讲剩下的方法,以及服务是如何发布出去的,先看看初始化方法中剩下的几个方法,如下:

③ DubboBootstrap#loadRemoteConfigs()方法:

这两个方法,就是从配置中心获取的配置中,找出前缀为:dubbo.registries. 和 dubbo.protocols. ,如供你没有配置,那请忽略,如果你配置了,当然可以获取到,并保存起来,保存逻辑为:

可以知道,最终所有属性都是配置在ConfigManager的configsCache属性中。断点看看结果:

但是断点中看到的结果,为什么key为registry和protocol的不止一个呢?因为我在配置中心的dubbo.properties做了修改,修改如下:

这是为了测试前面说的前缀为 dubbo.registries. 和 dubbo.protocols. 的情况,说白了就是配置多个注册中心和多个协议,这是Dubbo支持的。当然,仅仅在配置中心这样配置还不够,因为这里涉及到了hessian协议,需要在service-provider模块中引入相关的依赖,如下:

XML 复制代码
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-hessian</artifactId>
            <version>2.7.10</version>
        </dependency>

dubbo协议的依赖我在之前已经引入了,因此无需添加它的依赖。同时,由于在配置中心中灵配置了另一个nacos配置中心的地址,因此需要再开一个nacos服务,端口为:8849,Idea配置如下:

重新启动服务提供方,发现在两个nacos服务上,在服务管理>服务列表,都可以查到,服务提供方的服务,先看看端口是8848的nacos服务,如下:

查看详情,结果为:

再看看端口是8849的nacos服务,如下:

查看详情,结果为:

我发现,当前版本的Dubbo,如果使用hessian协议的话,服务无法使用netty4?看了下HessianProtocol,发现绑定服务的是这个属性,代码如下:

只能使用Jetty、Tomcat或者Servlet,看看接口全限定名的文件,也是如此,如下:

那我们现在测一下,由于添加了hessian协议,因此在service-comsumer模块中也要加相关依赖,如下:

XML 复制代码
 <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-hessian</artifactId>
            <version>2.7.10</version>
        </dependency>

然后指定要使用的协议,再启动服务消费方,调用一个对外暴露的接口:http://localhost:9000/hello/getName/Tim,结果如下:

如果使用dubbo协议呢?跟上面类似,如下:

虽然是报500,原因是我断点导致的超时,不打断点正常调用,是没问题的。至于为什么会调用到这里来,后面详细讲,因为这才是我要讲的重点。

④ DubboBootstrap#checkGlobalConfigs()方法,校验的逻辑,非核心流程,跳过。

⑤ DubboBootstrap#startMetadataCenter()方法:

说白了就是获取元数据的配置信息,通过调用MetadataReportFactory#getMetadataReport()方法,获取MetadataReport对象,并放入MetadataReportInstance的静态属性metadataReports,缓存起来,供后续使用。

元数据中心是dubbo2.7版本之后新增的功能,主要是为了减轻注册中心的压力,将部分存储在注册中心的内容放到元数据中心。元数据中心的数据只是给自己使用的,改动不需要告知对端,比如服务端修改了元数据,不需要通知消费端。这样注册中心存储的数据减少,同时大大降低了因为配置修改导致注册中心频繁通知监听者,从而大大减轻注册中心的压力。因此也可以在元数据中配置注册中心中心。当然要看到其效果,要先注释掉注册中心相关的配置,代码如下:

除此之外,还要加上依赖,如下:

XML 复制代码
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-metadata-report-nacos</artifactId>
            <version>2.7.10</version>
        </dependency>

然后启动服务提供方服务,看看结果:

⑥ DubboBootstrap#startMetadataCenter()方法:

⑦ DubboBootstrap#initEventListener()方法:

到这里为止,DubboBootstrap#initialize()方法算是讲完了,回到DubboBootstrap#start()方法,代码如下:

再看看DubboBootstrap#exportServices()方法,代码如下:

从ConfigManager中取出所有ServiceConfigBase对象,也就是ServiceBean,看看类的继承层级:

ServiceBean是什么时候被放入ConfigManager中的呢?也就是我前面说的,在这里被放入的,代码如下:

回到DubboBootstrap#exportServices()方法,遍历获取到的所有ServiceConfig,调用ServiceConfig#export()方法,代码如下:

看看ServiceConfig#checkAndUpdateSubConfigs()方法,代码如下(代码较长,只截取核心):

到这里为止,已经给ServiceConfig对象设置好了协议的配置和注册中心的配置,这部分配置也是我在DubboConfiguration中配置好了。当然也包括接口,也设置好了,主要是通过interfaceName属性,基于类加载器加载并设置的,而interfaceName属性也是之前在ServiceClassPostProcessor中设置的,具体代码为:

再看看ServiceBean的 ref属性是如何赋值的,这就需要回到ServiceClassPostProcessor#buildServiceBeanDefinition()方法,代码如下:

重点就是给ref设置的属性为RuntimeBeanReference对象,并且名字为"helloServiceImpl",RuntimeBeanReference是Spring提供的,最终给ref属性注入的对象那个肯定不是RuntimeBeanReference,而是在这里做了处理,代码如下(Spring属性注入的部分源码):

有这个调用链路可知,最终获取的是RuntimeBeanReference的beanName,通过beanName从Spring容器中拿到HelloServiceImpl对象,通过反射,赋值给ServiceBean的ref属性。打断点看看,结果如下:

重点来了,也就是ServiceConfig#doExport()方法,代码如下:

回到ServiceConfig#doExportUrls()方法,遍历所有的协议,代码如下:

如果我配置了,是这样的效果:

针对group、version,是可以起到隔离服务的作用的,比如version,如果服务提供方有多个结点的话,可以在服务消费方指定version,服务消费反感发起请求,就会@DubboReference上配置的version找对应的节点,如果找到的节点有多个的话,则会通过负载均衡找到一个节点。再看看ServiceRepository#registerService()方法,代码如下:

再看看ServiceConfig#doExportUrlsFor1Protocol()方法(核心),代码如下(以打断点为主,涉及到的参数太多了,不可能一个个去看):

java 复制代码
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        String name = protocolConfig.getName();
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }
        // 将配置的参数设置到map中,没有配置的,设置默认值
        Map<String, String> map = new HashMap<String, String>();
        map.put(SIDE_KEY, PROVIDER_SIDE);

        ServiceConfig.appendRuntimeParameters(map);
        AbstractConfig.appendParameters(map, getMetrics());
        AbstractConfig.appendParameters(map, getApplication());
        AbstractConfig.appendParameters(map, getModule());
        // remove 'default.' prefix for configs from ProviderConfig
        // appendParameters(map, provider, Constants.DEFAULT_KEY);
        AbstractConfig.appendParameters(map, provider);
        AbstractConfig.appendParameters(map, protocolConfig);
        AbstractConfig.appendParameters(map, this);
        MetadataReportConfig metadataReportConfig = getMetadataReportConfig();
        if (metadataReportConfig != null && metadataReportConfig.isValid()) {
            map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
        }
        if (CollectionUtils.isNotEmpty(getMethods())) {
            for (MethodConfig method : getMethods()) {
                AbstractConfig.appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (CollectionUtils.isNotEmpty(arguments)) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (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())) {
                                                AbstractConfig.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())) {
                                                    AbstractConfig.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) {
                            AbstractConfig.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
        }

        if (ProtocolUtils.isGeneric(generic)) {
            map.put(GENERIC_KEY, generic);
            map.put(METHODS_KEY, ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }

            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }

        /**
         * Here the token value configured by the provider is used to assign the value to ServiceConfig#token
         */
        if (ConfigUtils.isEmpty(token) && provider != null) {
            token = provider.getToken();
        }

        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(TOKEN_KEY, token);
            }
        }
        //init serviceMetadata attachments
        serviceMetadata.getAttachments().putAll(map);

        // export service
        String host = findConfigedHosts(protocolConfig, registryURLs, map);  // 获取当前服务的ip地址
        Integer port = findConfigedPorts(protocolConfig, name, map);
        // dubbo://192.168.217.1:20880/com.szl.service.api.HelloService?anyhost=true&application=service-provider&bind.ip=192.168.217.1
        // &bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.szl.service.api.HelloService&methods=sayHello
        // &pid=5252&qos.enable=false&release=&server=netty4&side=provider&timestamp=1653840932266
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

        // You can customize Configurator to append extra parameters
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        String scope = url.getParameter(SCOPE_KEY);
        // don't export when none is configured
        if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

            // export to local if the config is not remote (export to remote only when config is remote)
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        //if protocol is only injvm ,not register
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            if (url.getParameter(REGISTER_KEY, true)) {
                                logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                            } else {
                                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                            }
                        }

                        // For providers, this is used to enable custom proxy to generate invoker
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }
                        // 生成 invoker对象
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    if (logger.isInfoEnabled()) {
                        logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                    }
                    // 生成代理对象
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                    exporters.add(exporter);
                }

                MetadataUtils.publishServiceDefinition(url);
            }
        }
        this.urls.add(url);
    }

我先给@DubboService注解添加以下信息,如下:

因此可以知道,如果不设置scope,则会暴露对外两个地址,一个是本地(测试用,无需调用远程服务),使用的是"injvm"协议;一个是远程,默认使用的是"dubbo"协议。继续往下看:

上图中漏了一点:在调用PROXY_FACTORY.getInvoker()方法中,又执行了:registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())方法,该方法就是将url进行编码,并将其加入到registryURL的export属性中,如下:

限于篇幅,先讲到这里,剩下的内容将在下一篇博客中继续讲解,如有错误,恳请指正,谢谢!

相关推荐
customer083 分钟前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
全栈开发圈5 分钟前
新书速览|Java网络爬虫精解与实践
java·开发语言·爬虫
WaaTong7 分钟前
《重学Java设计模式》之 单例模式
java·单例模式·设计模式
城南vision9 分钟前
计算机网络——TCP篇
网络·tcp/ip·计算机网络
面试鸭9 分钟前
离谱!买个人信息买到网安公司头上???
java·开发语言·职场和发展
Ciderw36 分钟前
块存储、文件存储和对象存储详细介绍
网络·数据库·nvme·对象存储·存储·块存储·文件存储
小白冲鸭40 分钟前
【报错解决】使用@SpringJunitConfig时报空指针异常
spring·java后端开发