IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> dubbo之服务本地引用 -> 正文阅读

[网络协议]dubbo之服务本地引用

debug测试源码

写在前面

dubbo之服务本地暴露 文章中我们分析了本地暴露的过程,本文一起看下对应的服务消费者端是如何引用的,即本地引用。

一般的,当使用本地引用时,xml配置如下:

<fakeRoot>
    <dubbo:reference id="scopeLocalService" interface="dongshi.daddy.service.scopelocal.ScopeLocalService" scope="local"/>
</fakeRoot>

java代码如下:

class FakeCls {
    public class MyProviderScopeLocalMain {
        public static void main(String[] args) throws Exception {
            //加载xml配置文件启动
            ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext("META-INF/spring/scopelocal/provider-scope-local.xml");
            providerContext.start();
                    ClassPathXmlApplicationContext consumerContext = new ClassPathXmlApplicationContext("META-INF/spring/scopelocal/consumer-scope-local.xml");
            consumerContext.start();
            ScopeLocalService scopeLocalService = consumerContext.getBean("scopeLocalService", ScopeLocalService.class);
            scopeLocalService.sayHi("hello scope local!");
            System.in.read(); // 按任意键退出
        }
    }

}

其中的代码consumerContext.getBean("scopeLocalService", ScopeLocalService.class)就是获取用于本地调用服务提供者的代理类的,在dubbo中<dubbo:reference>对应的bean是一个工厂bean ,通过该工厂bean获取我们最终需要的代理类,之后用来调用服务提供者,如下debug:

在这里插入图片描述

从图中可到最终调用到com.alibaba.dubbo.config.ReferenceConfig#createProxy,接下来我们就从这个方法来开始分析吧!

1:createProxy

源码如下:

class FakeCls {
    // com.alibaba.dubbo.config.ReferenceConfig.createProxy
    // map:配置相关信息,如
    /*
    "owner" -> "dongshidaddy"
    "side" -> "consumer"
    "application" -> "dongshidaddy-consumer"
    "register.ip" -> "192.168.64.1"
    "methods" -> "sayHi"
    "scope" -> "local"
    "dubbo" -> "2.0.2"
    "pid" -> "29040"
    "interface" -> "dongshi.daddy.service.scopelocal.ScopeLocalService"
    "timestamp" -> "1644394930637"
    */
    private T createProxy(Map<String, String> map) {
        URL tmpUrl = new URL("temp", "localhost", 0, map);
        // 是否本地引用的标记
        final boolean isJvmRefer;
        // 1:isInjvm()方法已经标记为@Deprecated
        // 2:可以认为返回值为null,即只会进if
        if (isInjvm() == null) {
            // 如果是指定了url,则说明要使用注册中心,直接将isJvmRefer设置为false,代表不是本地引用
            if (url != null && url.length() > 0) {
                isJvmRefer = false;
            // 2022-02-09 18:08:29
            } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
                isJvmRefer = true;
            } else {
                isJvmRefer = false;
            }
        } else {
            isJvmRefer = isInjvm().booleanValue();
        }
        if (isJvmRefer) {
            // 构造url,如injvm://127.0.0.1/dongshi.daddy.service.scopelocal.ScopeLocalService?... 因为是本地调用所以是127.0.0.1
            // 本地调用为什么还需要url呢?我认为是为了和远程调用保持格式上的统一,这样做到最小的差异性,代码更加容易编写
            URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
            // 2022-02-15 10:02:41
            invoker = refprotocol.refer(interfaceClass, url);
            if (logger.isInfoEnabled()) {
                logger.info("Using injvm service " + interfaceClass.getName());
            }
        // 正常流程,一般是远程引用,本文分析本地引用,因此,该部分先不看
        } else {
            // 省略远程引用逻辑
        }
        // 服务检查
        Boolean c = check;
        if (c == null && consumer != null) {
            c = consumer.isCheck();
        }
        if (c == null) {
            c = true;
        }
        if (c && !invoker.isAvailable()) {
            initialized = false;
            throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
        }
        if (logger.isInfoEnabled()) {
            logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
        }
        // 创建Service代理对象,内部会调用invoker.invoke
        return (T) proxyFactory.getProxy(invoker);
    }
}

2022-02-09 18:08:29InjvmProtocol.getInjvmProtocol()处是获取InJvmProtocol扩展类的实例,2022-02-09 18:08:29isInjvmRefer,是根据协议判断是否是本地引用,具体参考1.1:isInjvmRefer2022-02-15 10:02:41处是获取调用本地服务提供者的Invoker,这里refprotocolProtocol$Adaptive,该类为动态生成的类,源码如下:

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

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
    
    // 获取服务引用调用的方法
    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    // 暴露服务引用调用的方法
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

看其中的方法refer,因为我们的url是injvm://127.0.0.1/dongshi.daddy.service.scopelocal.ScopeLocalService?...,所以最终调用的是Protocol扩展类是com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol,再考虑wrapper的话,调用顺序是Protocol$Adaptive => ProtocolFilterWrapper => ProtocolListenerWrapper => InjvmProtocol,具体参考2:Protocol

1.1:isInjvmRefer

源码如下:

class FakeCls {
    // com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol.isInjvmRefer
    // url此时值:temp://localhost?...&scope=local&...
    public boolean isInjvmRefer(URL url) {
        final boolean isJvmRefer;
        // temp://localhost?...&scope=local&... -> local
        String scope = url.getParameter(Constants.SCOPE_KEY);
        // url.getProtocol():temp
        // public static final String LOCAL_PROTOCOL = "injvm"; 
        if (Constants.LOCAL_PROTOCOL.toString().equals(url.getProtocol())) {
            isJvmRefer = false;
        // public static final String SCOPE_LOCAL = "local"; 条件Constants.SCOPE_LOCAL.equals(scope) 满足,进入该判断,结果为true
        } else if (Constants.SCOPE_LOCAL.equals(scope) || (url.getParameter("injvm", false))) {
            isJvmRefer = true;
        // public static final String SCOPE_REMOTE = "remote";
        } else if (Constants.SCOPE_REMOTE.equals(scope)) {
            isJvmRefer = false;
        // 泛化调用
        } else if (url.getParameter(Constants.GENERIC_KEY, false)) {
            isJvmRefer = false;
        // 2022-02-14 17:49:09
        // 从本地获取服务提供者对应的Exporter
        } else if (getExporter(exporterMap, url) != null) {
            isJvmRefer = true;
        } else {
            isJvmRefer = false;
        }
        return isJvmRefer;
    }
}

2022-02-14 17:49:09处是尝试获取服务提供者对应的Exporter,如果是获取到则说明是本地引用,具体参考1.1.1:getExporter

1.1.1:getExporter

源码如下:

class FakeCls {
    // com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol.getExporter
    // map: dongshi.daddy.service.scopelocal.ScopeLocalService -> {InjvmExporter@2488} "interface dongshi.daddy.service.scopelocal.ScopeLocalService -> injvm://127.0.0.1/dongshi.daddy.service.scopelocal.ScopeLocalService?..."
    // key: injvm://127.0.0.1/dongshi.daddy.service.scopelocal.ScopeLocalService?...
    static Exporter<?> getExporter(Map<String, Exporter<?>> map, URL key) {
        Exporter<?> result = null;
        // key.getServiceKey() -> 组/xxx.yyy.AService:版本号
        if (!key.getServiceKey().contains("*")) {
            // 从已暴露map中获取Exporte,这里正常获取的是InjvmExporter
            result = map.get(key.getServiceKey());
        } else {
            // ...
        }
        // 没有获取到肯定为远程调用,直接返回null
        if (result == null) {
            return null;
        // 泛化调用也是远程调用,所以也返回null
        } else if (ProtocolUtils.isGeneric(
                result.getInvoker().getUrl().getParameter(Constants.GENERIC_KEY))) {
            return null;
        } else {
            return result;
        }
    }
}

2:Protocol

2.1:ProtocolFilterWrapper

class FakeCls {
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        // registry://协议,远程暴露时会走这里
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        // 2022-02-15 10:57:02
        return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
    }
}

2022-02-15 10:57:02处是先继续向下调用,然后将最终返回的Invoker添加Filter调用链,buildInvokerChain获取的是public static final String CONSUMER = "consumer";group=consumer的Filter,如下就会获取到:

@Activate(group = Constants.CONSUMER)
public class DubboHystrixFilter implements Filter {}

2.2:ProtocolFilterWrapper

包装原始Invoker,增加InvokerListener监听器们,监听暴露和取消暴露事件,源码如下:

class FakeCls {
    // com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper.refer
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        // registry://协议,远程引用才会走这里
        if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        // 2022-02-15 14:03:50
        // 将获取的Invoker和InvokerListener,并返回包装类ListenerInvokerWrapper
        return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
                Collections.unmodifiableList(
                        ExtensionLoader.getExtensionLoader(InvokerListener.class)
                                .getActivateExtension(url, Constants.INVOKER_LISTENER_KEY)));
    }
}

2022-02-15 14:03:50处ListenerInvokerWrapper是Invoker的包装类,主要源码如下:

// 实现了Invoker接口,保证被装饰类有相同的行为
public class ListenerInvokerWrapper<T> implements Invoker<T> {
    private static final Logger logger = LoggerFactory.getLogger(ListenerInvokerWrapper.class);
    // 被装饰的invoker
    private final Invoker<T> invoker;
}

这其实就是一种装饰设计模式 的应用。

2.3:InjvmProtocol

源码如下:

class FakeCls {
    // com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol.refer
    @Override
    public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {    
        // 2022-02-15 13:19:45
        return new InjvmInvoker<T>(serviceType, url, url.getServiceKey(), exporterMap);
    }
}

2022-02-15 13:19:45处是直接封装InjvmInvoker,其中exporterMap中包含了所有的服务提供者类,如下可能配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- omit other configuration -->
    <dubbo:service interface="dongshi.daddy.service.scopelocal.ScopeLocalService" ref="scopeLocalService" scope="local"/>
    <!--Bean bean定义-->
    <bean id="scopeLocalService" class="dongshi.daddy.service.scopelocal.ScopeLocalServiceImpl"/>
    <dubbo:service interface="dongshi.daddy.service.scopelocal.ScopeLocalService1" ref="scopeLocalService1" scope="local"/>
    <!--Bean bean定义-->
    <bean id="scopeLocalService1" class="dongshi.daddy.service.scopelocal.ScopeLocalServiceImpl1"/>
</beans>

exporterMap中就会有2个元素,如下:

在这里插入图片描述

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-02-16 13:30:42  更:2022-02-16 13:32:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 9:33:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码