精华内容
下载资源
问答
  • 暴露接口和调用接口的原理
    千次阅读
    2021-03-18 11:23:26

    很多简单的接口就是直接一个URl的形式,

    怎么调用?

    HttpClient httpclient=null;

    PostMethod post=null;

    try{

    httpclient = new HttpClient();

    post = new PostMethod(SendUrl);

    //设置编码方式

    post.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET,"gbk");

    //添加参数

    post.addParameter("LoginName",LoginName);

    post.addParameter("Password", Password);

    //执行

    httpclient.executeMethod(post);

    //接口返回信息

    String info = new String(post.getResponseBody(),CHARSET);

    System.out.println(info);

    }catch (Exception e) {

    e.printStackTrace();

    }finally {

    //关闭连接,释放资源

    post.releaseConnection();

    ((SimpleHttpConnectionManager)httpclient.getHttpConnectionManager()).shutdown();

    }

    到此为止,主要为自己做备忘,同时方便有需要的人。呵呵!

    调用URL 接口服务

    1.Net调用URL 接口服务 using System; using System.Collections; using System.Configuration; using System.Dat ...

    【转】java通用URL接口地址调用方式GET和POST方式

    java通用URL接口地址调用方式GET和POST方式,包括建立请求和设置请求头部信息等等......... import java.io.ByteArrayOutputStream; import ...

    Java调用webservice接口方法

    java调用webservice接口   webservice的 发布一般都是使用WSDL(web service descriptive langu ...

    Java 调用http接口(基于OkHttp的Http工具类方法示例)

    目录 Java 调用http接口(基于OkHttp的Http工具类方法示例) OkHttp3 MAVEN依赖 Http get操作示例 Http Post操作示例 Http 超时控制 工具类示例 Ja ...

    (二)通过JAVA调用SAP接口 (增加一二级参数)

    (二)通过JAVA调用SAP接口 (增加一二级参数) 一.建立sap连接 请参考我的上一篇博客 JAVA连接SAP 二.测试项目环境准备 在上一篇操作下已经建好的环境后,在上面的基础上新增类即可 三. ...

    Java调用第三方接口工具类(json、form)

    1.JSON值访问 /** * 调用对方接口方法 * @param path 对方或第三方提供的路径 * @param data 向对方或第三方发送的数据,大多数情况下给对方发送JSON数据让对方解析 ...

    Java调用第三方接口示范

    在项目开发中经常会遇到调用第三方接口的情况,比如说调用第三方的天气预报接口. 使用流程[1]准备工作:在项目的工具包下导入HttpClientUtil这个工具类,或者也可以使用Spring框架的res ...

    三种方法实现java调用Restful接口

    1,基本介绍 Restful接口的调用,前端一般使用ajax调用,后端可以使用的方法比较多, 本次介绍三种: 1.HttpURLConnection实现 2.HttpClient实现 3.Spring ...

    Java调用webservice接口方法(SOAP message、xfire、axis)

    webservice的 发布一般都是使用WSDL(web service descriptive language)文件的样式来发布的,在WSDL文件里面,包含这个webservice暴露在外面可供使 ...

    随机推荐

    (Python) 安装、基本语法

    从今天起,我将开启python学习模式,并用博客记录学习的过程和相关知识点 1.Python下载安装 可以在官网:https://www.python.org/downloads/ 中下载各种版本的P ...

    js代码的一些小技巧

    1. 数组中通过赋值语句来改变值 var a = 1; var msg = ["value0","value1"]; for(var i = 0;i<10 ...

    jQuery Ajax&lpar;load&comma;post&comma;get&comma;ajax&rpar;用法与详解

    今天看到群里面有网友们问到Jquery Ajax的(load,post,get,ajax)之间的区别,现在整理了一篇文章出来,希望可以帮到网友们,首先我们先来看一些简单的方法, 这些方法都是对jQue ...

    oracle安装报错2

    [oracle@centos1 database]$ ./runInstaller Starting Oracle Universal Installer... Checking installer ...

    paip&period;java UrlRewrite 的原理and实现 htaccess正則表達式转换

    paip.java UrlRewrite 的原理and实现 htaccess正則表達式转换 #---KEYWORD #-正則表達式 正則表達式 表示 非指定字符串开头的正则 排除指定文件夹.. 作者 ...

    Java中Json解析

    首先准备一个JSON格式的字符串 * String JsonStr = "{object:{persons:" + "[{name:'呵呵',image:'http:// ...

    NMEA0183

    NMEA简介 NMEA是全国海洋电子协会(National Marine Electronics Association):国际海上电子协会(National Marine Electronics A ...

    python3 dict(字典)

    clear(清空字典内容) stu = { 'num1':'Tom', 'num2':'Lucy', 'num3':'Sam', } print(stu.clear()) #输出:None copy( ...

    003&lowbar;生成器&lpar;generator&rpar;内部解析

    #http://kb.cnblogs.com/page/87128/(未看完)

    【LOJ&num;2542】&lbrack;PKUWC2018&rsqb;随机游走(min-max容斥,动态规划)

    [LOJ#2542][PKUWC2018]随机游走(min-max容斥,动态规划) 题面 LOJ 题解 很明显,要求的东西可以很容易的进行\(min-max\)容斥,那么转为求集合的\(min\). ...

    更多相关内容
  • Dubbo接口测试原理及多种方法

    千次阅读 2021-03-04 08:36:23
    当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。 垂直应用架构 当访问量逐渐增大,单一应用增加机器带来的加速度...

    1、什么是Dubbo

    最开始是应用于淘宝网,由阿里巴巴开源的一款优秀的高性能服务框架,由Java开发,后来贡献给了Apache组织

    下面以官网的一个说明来了解一下架构的演变过程,从而了解dubbo的诞生原因:
    在这里插入图片描述

    • 单一应用架构
      当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

    • 垂直应用架构
      当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

    • 分布式服务架构
      当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

    • 流动计算架构
      当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

    2、Dubbo架构简介

    在这里插入图片描述

    节点角色说明实例对应
    Provider暴露服务的服务提供方开发的代码
    Consumer调用远程服务的服务消费方调用方代码、Telnet、Jmeter
    Registry服务注册与发现的注册中心zookeeper
    Monitor统计服务的调用次数和调用时间的监控中心dubbo-monitor
    Container服务运行容器dubbo内置容器、Jetty

    Dubbo比较有特点的地方就是这个注册中心,平常我们测试较多的HTTP接口,直接请求接口,调用后端服务即可;而Dubbo是要先走注册中心获取服务的位置,下面来举个现实生活中的例子来说明

    • 现实举例
      好比大家平常约个同事朋友一起出去吃饭,吃什么呢?今天我们就吃个川菜吧,听说赠李白(没收广告费~)不错,于是就决定去赠李白吃饭。

      然后我们需要去找这家饭店在哪,这个时候我们打开了我们的小蓝或小黄APP去查这家店的信息,找到了之后知道了具体的地址,在哪家商场,哪层的几号;

      接着我们就根据地址出发,至于是走路,打车还是骑车,就随意了
      最后,找到了店铺,开始消费,开始长胖~

      而这里APP就相当于注册中心(Registry),我们这群吃货就是消费者(Consumer),商家,也就是赠李白属于生产者(Provider)。商家把自己的信息注册在APP上,消费者根据APP查询到商家的信息,再根据信息找到商家进行消费。

    2.1、zookeeper简介

    之前经常有小伙伴问我zk是啥?干啥的?怎么干的?下面我们来简单了解一哈

    ZK,全称就是zookeeper,wikipedia上的解释是这样的:Apache软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册

    下面的图示也可以清晰的说明zk的部署和工作的一些方式(具体的技术细节需要的话可以针对zk专门搜索学习):
    在这里插入图片描述

    • Leader:集群工作的核心,事务请求的唯一调度和处理者,保证事务处理的顺序性。对于有写操作的请求,需统一转发给Leader处理。Leader需决定编号执行操作。
    • Follower:处理客户端非事务请求,转发事务请求转发给Leader,参与Leader选举。
    • Observer观察者:进行非事务请求的独立处理,对于事务请求,则转发给Leader服务器进行处理.不参与投票。

    3、什么是Dubbo接口?

    所谓的Dubbo接口,其实就是一个个Dubbo服务中的方法,而测试Dubbo接口就相当于我们测试人员充当消费者或者创造消费者去"消费"这个方法

    具体的方式有很多,代码、工具、命令皆可,下面来一一演示

    4、Dubbo接口测试(创造消费者)

    以下我将以本地的一个简单的dubbo服务demo为例,演示dubbo测试的各种方法。

    interface只有两个,分别是OrderServiceUserService
    在这里插入图片描述
    OrderService:

    package com.qinzhen.testmall.service;
    
    import com.qinzhen.testmall.bean.UserAddress;
    import java.util.List;
    
    public interface OrderService {
    
        /**
         * 初始化订单
         * @param userID
         */
        public List<UserAddress> initOrder(String userID);
    }
    

    UserService:

    package com.qinzhen.testmall.service;
    
    import com.qinzhen.testmall.bean.UserAddress;
    import java.util.List;
    
    /**
     * 用户服务
     */
    public interface UserService {
    
    
        /**
         * 按照userId返回所有的收获地址
         * @param userId
         * @return
         */
        public List<UserAddress> getUserAddressList(String userId);
    
    
        /**
         * 返回所有的收获地址
         * @param 
         * @return
         */
        public List<UserAddress> getUserAddressList();
    }
    

    JavaBean对象UserAddress如下:

    package com.qinzhen.testmall.bean;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import java.io.Serializable;
    
    @AllArgsConstructor
    @Data
    public class UserAddress implements Serializable {
    
        private Integer id;
        private String userAddress; //用户地址
        private String userId; //用户ID
        private String consignee; //收货人
        private String phoneNum; //电话号码
        private String isDefault; //是否为默认地址 Y-是  N-否
    
        public UserAddress(){
    
        }
    }
    

    创建一个provider来实现UserServiceInterface
    在这里插入图片描述
    实现方法中,根据id返回对应的用户地址信息即可:

    package com.qinzhen.testmall.bootuserserviceprovider.service.impl;
    
    import com.alibaba.dubbo.config.annotation.Service;
    import com.qinzhen.testmall.bean.UserAddress;
    import com.qinzhen.testmall.service.UserService;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    
    @Component
    @Service  //暴露服务
    public class UserServiceImpl implements UserService {
    
        private UserAddress userAddress1 = new UserAddress(1, "杭州市西湖区XX公司", "1", "qz", "12345678", "Y");
        private UserAddress userAddress2 = new UserAddress(2, "杭州市西湖区花园", "2", "qz", "12345678", "N");
    
        @Override
        public List<UserAddress> getUserAddressList(String userId) {
            if (userId.equals("1")){
                return Collections.singletonList(userAddress1);
            }
            else if (userId.equals("2")){
                return Collections.singletonList(userAddress2);
            }
            else {
                return Arrays.asList(userAddress1, userAddress2);
            }
        }
    
        @Override
        public List<UserAddress> getUserAddressList(){
            return Arrays.asList(userAddress1, userAddress2);
        }
    }
    

    4.1 Java consumer代码

    下面我们编写consumer代码,让服务消费者去注册中心订阅服务提供者的服务地址,以RPC方式,获取远程服务代理,从而执行远程方法,代码也很简单,如下:

    • 代码结构:
      在这里插入图片描述
      实现场景就是实现OrderService中的initOrder()方法,初始化订单,初始化中直接调用userServicegetUserAddressLis(java.lang.String)方法,具体代码如下:
    package com.qinzhen.testmall.service.impl;
    
    import com.qinzhen.testmall.bean.UserAddress;
    import com.qinzhen.testmall.service.OrderService;
    import com.qinzhen.testmall.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.util.List;
    
    /**
     * 1、讲服务提供者注册到注册中心(暴露服务)
     *          1)导入dubbo依赖:操作zookeeper的客户端(curator)
     * 2、让服务消费者去注册中心订阅服务提供者的服务地址
     */
    @Service
    public class OrderServiceImpl implements OrderService {
    
        @Autowired
        UserService userService;
    
        public List<UserAddress> initOrder(String userId) {
            //1.查询用户的收货地址
            System.out.println("用户ID为:" + userId);
            List<UserAddress> userAddressList = userService.getUserAddressList(userId);
            return userAddressList;
        }
    }
    
    • consumer MainApplication
    package com.qinzhen.testmall;
    
    import com.qinzhen.testmall.bean.UserAddress;
    import com.qinzhen.testmall.service.OrderService;
    import com.qinzhen.testmall.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.stereotype.Service;
    
    import java.io.IOException;
    import java.util.List;
    
    
    /**
     * 1、将服务提供者注册到注册中心(暴露服务)
     *          1)导入dubbo依赖:操作zookeeper的客户端(curator)
     * 2、让服务消费者去注册中心订阅服务提供者的服务地址
     */
    @Service
    public class MainApplication {
        public static void main(String[] args) throws IOException {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"consumer.xml"});
            context.start();
            OrderService orderService = context.getBean(OrderService.class); // 获取远程服务代理
            List<UserAddress> userAddresses = orderService.initOrder("3");// 执行远程方法
            System.out.println(userAddresses);
            System.out.println("调用完成。。。");
            System.in.read();
        }
    }
    
    • consumer.xml:
    <?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"
           xmlns:context="http://www.springframework.org/schema/context"
           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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:component-scan base-package="com.qinzhen.testmall.service.impl"></context:component-scan>
    
        <dubbo:application name="order-service-comsumer"></dubbo:application>
    
        <dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>
    
        <!--声明需要远程调用远程服务的接口,生成远程服务代理-->
        <dubbo:reference interface="com.qinzhen.testmall.service.UserService" id="userService"></dubbo:reference>
    
    </beans>
    
    • 实例演示:
      首先确保provider已启动
      在这里插入图片描述
      运行consumer,可以看到成功调用到dubbo方法,获取地址列表信息:
      在这里插入图片描述

    4.2 telnet+invoke

    我们使用telnet命令可以直接访问对应的服务,但是前提是你需要知道服务对应的ip+端口

    如下配置文件,我们可以知道服务暴露在本地的20880端口

    dubbo.application.name=boot-user-service-provider
    dubbo.registry.address=127.0.0.1:2181
    dubbo.registry.protocol=zookeeper
    dubbo.protocol.name=dubbo
    dubbo.protocol.port=20880
    

    使用telnet命令进行访问,如下出现dubbo字样时说明连接成功:

    % telnet localhost 20880
    Trying ::1...
    Connected to localhost.
    Escape character is '^]'.
    
    dubbo>
    

    dubbo 内建的 telnet 命令的说明和用法如下

    • ls
      • ls: 显示服务列表
      • ls -l: 显示服务详细信息列表
      • ls XxxService: 显示服务的方法列表
      • ls -l XxxService: 显示服务的方法详细信息列表
    dubbo>ls
    com.qinzhen.testmall.service.UserService
    
    dubbo>ls -l
    com.qinzhen.testmall.service.UserService -> dubbo://192.168.2.xxx:20880/com.qinzhen.testmall.service.UserService?anyhost=true&application=boot-user-service-provider&bind.ip=192.168.2.xxx&bind.port=20880&dubbo=2.6.2&generic=false&interface=com.qinzhen.testmall.service.UserService&methods=getUserAddressList&pid=55472&qos.enable=false&side=provider&timestamp=1615088321885
    
    dubbo>dubbo>ls com.qinzhen.testmall.service.UserService
    getUserAddressList
    getUserAddressList
    
    dubbo>dubbo>ls -l com.qinzhen.testmall.service.UserService
    java.util.List getUserAddressList(java.lang.String)
    java.util.List getUserAddressList()
    
    • invoke
      • invoke XxxService.xxxMethod(1234, "abcd", {"prop" : "value"}): 调用服务的方法
      • invoke com.xxx.XxxService.XxxService.xxxMethod(1234, "abcd", {"prop" : "value"}): 调用全路径服务的方法
      • invoke xxxMethod(1234, "abcd", {"prop" : "value"}): 调用服务的方法(自动查找包含此方法的服务)
      • invoke xxxMethod({"name":"zhangsan","age":12,"class":"org.apache.dubbo.qos.legacy.service.Person"}) :当有参数重载,或者类型转换失败的时候,可以通过增加class属性指定需要转换类
      • 当参数为Map<Integer,T>key的类型为Integer时,建议指定类型。例如invoke com.xxx.xxxApiService({"3":0.123, "class":"java.util.HashMap"})

    然后我们使用invoke 命令对dubbo方法getUserAddressList()进行调用,如下:

    dubbo>invoke getUserAddressList()
    [{"consignee":"qz","id":1,"isDefault":"Y","phoneNum":"12345678","userAddress":"杭州市西湖区xx公司","userId":"1"},{"consignee":"qz","id":2,"isDefault":"N","phoneNum":"12345678","userAddress":"杭州市西湖区xx花园","userId":"2"}]
    
    dubbo>invoke getUserAddressList("1")
    [{"consignee":"qz","id":1,"isDefault":"Y","phoneNum":"12345678","userAddress":"杭州市西湖区xx公司","userId":"1"}]
    elapsed: 14 ms.
    

    学习链接
    其他Telnet命令相关操作,需要可参考dubbo官网:
    https://dubbo.apache.org/zh/docs/v2.7/user/references/telnet/

    4.3 Jmeter

    对于Jmeter测试dubbo接口的方法,可参考文档:
    基于Jmeter完成Dubbo接口的测试

    4.4 dubbo-admin

    对于dubbo-admin的安装调试,可参考文档:
    dubbo-admin+zookeeper的环境搭建实操与Could not extract archive报错踩坑

    4.5 泛化调用

    测试dubbo服务的时候,我们需要服务端的同学给我们提供API,没有这个API我们是测不了的,而为了解决这个问题,dubbo官方又给我们提供了另外一个方法,就是泛化调用,来看看官方的解释:

    在这里插入图片描述

    4.5.1 泛化调用的使用

    dubbo给我们提供了一个接口GenericService,这个接口只有一个方法,就是$invoke,它接受三个参数,分别为方法名方法参数类型数组参数值数组

    下面我们直接上代码演示:

    import com.alibaba.dubbo.config.ApplicationConfig;
    import com.alibaba.dubbo.config.ReferenceConfig;
    import com.alibaba.dubbo.config.RegistryConfig;
    import com.alibaba.dubbo.rpc.service.GenericService;
    import org.junit.jupiter.api.Test;
    
    public class TestDemo {
    
        @Test
        void testDubboGenericService(){
            // 引用远程服务
            // 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
            ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
            // 弱类型接口名
            reference.setApplication(new ApplicationConfig("order-service-consumer"));
            reference.setInterface("com.qinzhen.testmall.service.UserService");
            reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
    
            // 声明为泛化接口
            reference.setGeneric(true);
    
            // 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用
            GenericService genericService = reference.get();
    
            Object result = genericService.$invoke("getUserAddressList", new String[] {"java.lang.String"}, new Object[] {"2"});
            System.out.println(result);
    
        }
    }
    

    运行后我们来看看结果,咦~也成功访问了:
    在这里插入图片描述

    4.5.1 泛化调用的原理

    我们通过debug跟入dubbo的源码中,可以得到如下的调用链:

    • 服务消费端:
      在这里插入图片描述
    • 服务提供端:
      在这里插入图片描述

    从上面的调用链可以知道完成一次泛化调用,dubbo框架经历了很多过滤器,我们分别选取两端链路中的最后一步的Filter来简单(难了我也不会~)了解一下泛化调用做了哪些事

    简化后的调用关系就如下:
    在这里插入图片描述
    先来看consumer端的GenericImplFilter,大概看下核心的处理步骤:

    // 判断是否为泛化调用
    if (invocation.getMethodName().equals(Constants.$INVOKE)
                    && invocation.getArguments() != null
                    && invocation.getArguments().length == 3
                    && ProtocolUtils.isGeneric(generic)) {
    			// 获取泛化调用参数
                Object[] args = (Object[]) invocation.getArguments()[2];
                // 判断是否为nativejava方式
                if (ProtocolUtils.isJavaGenericSerialization(generic)) {
    
                    for (Object arg : args) {
                        if (!(byte[].class == arg.getClass())) {
                            error(byte[].class.getName(), arg.getClass().getName());
                        }
                    }
                // 判断是否为bean方式
                } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                    for (Object arg : args) {
                        if (!(arg instanceof JavaBeanDescriptor)) {
                            error(JavaBeanDescriptor.class.getName(), arg.getClass().getName());
                        }
                    }
                }
    			// 设置为泛化调用方式
                ((RpcInvocation) invocation).setAttachment(
                        Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
            }
            // 发起远程调用
            return invoker.invoke(invocation);
    

    再来看provider端的GenericFilter,大概的核心处理步骤如下:

    package com.alibaba.dubbo.rpc.filter;
    
    import ...
    
    /**
     * GenericInvokerFilter.
     */
    @Activate(group = Constants.PROVIDER, order = -20000)
    public class GenericFilter implements Filter {
    
        @Override
        public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
        	// 判断是否为泛化请求
            if (inv.getMethodName().equals(Constants.$INVOKE)
                    && inv.getArguments() != null
                    && inv.getArguments().length == 3
                    && !ProtocolUtils.isGeneric(invoker.getUrl().getParameter(Constants.GENERIC_KEY))) {
                // 获取参数名称、参数类型、参数值
                String name = ((String) inv.getArguments()[0]).trim();
                String[] types = (String[]) inv.getArguments()[1];
                Object[] args = (Object[]) inv.getArguments()[2];
                try {
                	// 使用反射获取调用方法
                    Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
                    Class<?>[] params = method.getParameterTypes();
                    if (args == null) {
                        args = new Object[params.length];
                    }
                    // 获取泛化引用方式使用的泛化类型
                    String generic = inv.getAttachment(Constants.GENERIC_KEY);
                    // 泛化类型为空的话就使用generic=true的方式
                    if (StringUtils.isEmpty(generic)
                            || ProtocolUtils.isDefaultGenericSerialization(generic)) {
                        args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
                    // 判断是否为generic=nativejava方式
                    } else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
                        for (int i = 0; i < args.length; i++) {
                            if (byte[].class == args[i].getClass()) {
                                try {
                                    UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i]);
                                    args[i] = ExtensionLoader.getExtensionLoader(Serialization.class)
                                            .getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
                                            .deserialize(null, is).readObject();
                                } catch (Exception e) {
                                    。。。
                                }
                            } else {
                                。。。
                            }
                        }
                      // 判断是否为generic=bean方式
                    } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                        for (int i = 0; i < args.length; i++) {
                            if (args[i] instanceof JavaBeanDescriptor) {
                                args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);
                            } else {
                                。。。
                            }
                        }
                    }
                    // 传递请求,执行服务
                    Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
                    。。。
    }
    

    上面的代码很多,着重来提取一小段看一下:

    // 使用反射获取调用方法
    Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
    Class<?>[] params = method.getParameterTypes();
    

    从上面的代码中我们便可以得知原来泛化调用中使用了Java的反射技术来获取对应的方法信息完成调用的

    4.6 用Python来测试Dubbo

    我们现在知道了Dubbo是个Java项目,测试Dubbo就是模拟消费者去调用Dubbo的Java方法,那显而易见,用Python是肯定没法去直接调用Java的,但是在日常的工作中,很多小伙伴可能是pyhton技术栈的,或者因为一些测试条件限制亦或历史原因,必须要将Dubbo测试用Python实现以满足各种接口测试的一个组合

    4.6.1 python-hessian库

    Dubbo是支持hessian+http协议调用的,hessian是一种二进制序列化的方式。

    了解到可以通过这种方式实现,具体没有尝试过,还需要开发在项目中将序列化的方式改为hessian,并且需要知道URL,有兴趣的小伙伴可以去了解一下

    4.6.2 telnetlib库

    telnetlib是Python3自带的一个库,可以调用telnet命令,其实也就相当于上面说到的使用telnet方式访问dubbo的方法

    4.6.3 开发dubbo测试服务

    我们可以使用Java来开发一个dubbo测试的web服务,实现上就可以使用dubbo的泛化调用,然后我们再用HTTP访问的形式去访问这个服务,将我们的测试参数信息传过去,剩下的就交给Java去处理就好了。

    这样经过封装设计后,可以实现Python端的使用者在访问dubbo时就像在测试HTTP接口一样(例如Python的request库);另外服务的IP、端口、注册中心等信息都不用出现在测试的工程中,只需要用环境标签做区分,在服务端进行请求转发即可,也保证了一定安全性。

    大体上的思路流程如下:
    在这里插入图片描述

    展开全文
  • 想要在一个JVM里调用另一个JVM里的的方法,或许你会想到如下的姿势: HttpClient RestTemplate WebService ServerSocket/Socket RMI — 可以参看我的上篇文章《【dubbo源码解析~番外篇】— JDKSpring的RMI...

    本文对应源码地址:https://github.com/nieandsun/dubbo-study



    1 开篇

    想要在一个JVM里调用另一个JVM里的的方法,或许你会想到如下的姿势:

    但是在真正进行方法调用时,你不仅要考虑传什么参数、调用哪个方法,还得考虑对方服务的暴露地址等。
    而用过dubbo的肯定都知道,它可以做到:在一个JVM(消费端)里调用另一个JVM(服务端)的方法,就如同你在另一个JVM(服务端)直接调自己的方法一样 —> 这究竟是如何做到的呢,本篇文章将主要来探索一下这个问题。


    2 dubbo服务暴露过程底层原理探秘


    2.1 spring环境下dubbo服务暴露过程的前置知识


    2.1.1【spring解析要暴露服务的bean —> 进行服务暴露】 整体过程概览

    无论是使用注解,还是xml配置的方式,大家必须要有的前置知识是,dubbo中一个要向外暴露的服务(service),会先被spring解析并包装为一个ServiceBean,然后再拿着该ServiceBean进行真正的服务暴露。在dubbo中真正进行服务暴露的源码如下:
    在这里插入图片描述
    其实在该段代码中还涉及到了几个重要的概念:

    • (1)Invoker

    • (2)ProxyFactory — 代理工厂,主要有两个作用:
      • ① 生成Invoker
        • 注意1:在provider端生成的Invoker(实际为AbstractProxyInvoker)里包含了【具体要暴露的服务(即ref)】【服务的接口】【要暴露服务的url】
        • 注意2:在consumer端生成的Invoker(实际为AbstractInvoker)里至少包含了【服务的接口】【要暴露服务的url】
      • ②生成服务的代理类
        • 如果代理工厂选择的为JavassistProxyFactory,则为服务生成静态代理类
        • 如果代理工厂选择的为JdkProxyFactory,则为服务生成动态代理类

    • (3)Protocol — 协议,也主要有两个作用:
      • ① 拿着Invoker中的【url】 、【服务的接口】、和【根据该Invoker 用ProxyFactory生成的代理类】进行服务暴露
      • ② 接收到消费者的请求后,直接将请求参数信息交给代理类 —> 然后代理类会将请求信息转给(2)中生成的Invoker,并在Invoker里调用具体的服务 —> 这里后面会继续深化!!!

    2.1.2 ProxyFactory和Protocol接口简介

    同时还需要注意 ProxyFactoryProtocol 都是dubbo的SPI扩展点, 在dubbo源码中获取PROTOCOL和PROXY_FACTORY的姿势如下:
    在这里插入图片描述
    看过我《【dubbo源码解析】 — dubbo spi 机制(@SPI、@Adaptive)详解》这篇文章的你,肯定会想翻一下 ProxyFactoryProtocol 的源码,这里我们简单看一下:

    • Protocol接口的源码
    @SPI("dubbo") //dubbo默认使用的协议为dubbo协议
    public interface Protocol {
    
        int getDefaultPort();
    
        @Adaptive //在方法上标注该注解,说明会静态代理Protocol 实现类的该方法 ---> 服务暴露的方法
        <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    
        @Adaptive//在方法上标注该注解,说明会静态代理Protocol 实现类的该方法 ---> 根据class类型和URL获取Invoker【消费端的逻辑】
        <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
       
        void destroy();
       
        default List<ProtocolServer> getServers() {
            return Collections.emptyList();
        }
    }
    
    • ProxyFactory接口的源码
    @SPI("javassist")//dubbo默认使用的代理工厂为静态代理工厂
    public interface ProxyFactory {
    	//PROXY_KEY其实指代的就是字符串“proxy”
        @Adaptive({PROXY_KEY}) //在方法上标注该注解,说明会静态代理ProxyFactory 实现类的该方法 ---> 获取要暴露的服务的代理类
        <T> T getProxy(Invoker<T> invoker) throws RpcException;
    
        @Adaptive({PROXY_KEY})//在方法上标注该注解,说明会静态代理ProxyFactory 实现类的该方法 ---> 获取服务的代理类
        <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException;
    
        @Adaptive({PROXY_KEY})//在方法上标注该注解,说明会静态代理ProxyFactory 实现类的该方法 ---> 其实这里是生成Invoker
        <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
    

    通过上面的源码分析,你会想到什么呢??? ---》 这里请大家自己先开脑洞。。。


    2.2 实现一个自己的dubbo服务端

    有了上面的知识后,其实我们就可以仿照dubbo的源码,自己实现一个服务端了。当然本文仅仅是探索RPC远程调用的原理,所以这里的Serive,我们大可以直接new一个出来。

    仿照dubbo源码自己写的dubbo服务端:

    ExtensionLoader<ProxyFactory> proxyLoader = ExtensionLoader.getExtensionLoader(ProxyFactory.class);
    //支持的协议:dubbo、http、hessian、rmi等
    ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
    
    //URL protocol_url = URL.valueOf("dubbo://127.0.0.1:9300/" + DemoService.class.getName());//静态代理
    URL protocol_url = URL.valueOf("dubbo://127.0.0.1:9300/" + DemoService.class.getName() + "?proxy=jdk"); //动态代理
    
    
    @Test
    public void serverRpc() throws IOException {
        DemoService service = new DemoServiceImpl();
        //生成代理工厂
        //  --- 由URL确定到底是动态代理工厂(JdkProxyFactory)还是静态代理工厂(JavassistProxyFactory)
        //  --- 默认情况下为静态代理工厂
        ProxyFactory proxy = proxyLoader.getAdaptiveExtension();
        //由代理工厂生成Invoker对象
        //  --- Invoker对象里包含了具体的服务(service,在dubbo源码里服务端称为ref)、服务的接口和要暴露服务的url
        //  --- 但值得注意的是这里返回的Invoker对象,是用Invoker接口进行接收的,也就是说通过下面的serviceInvoker,只能获取到service的接口
        Invoker<DemoService> serviceInvoker = proxy.getInvoker(service, DemoService.class, protocol_url);
    
        //获取具体的协议
        //  ---由URL确定到底使用哪个协议,默认情况下使用dubbo协议
        Protocol protocol = protocolLoader.getAdaptiveExtension();
        
        //利用protocol暴露服务
        //  --- 暴露服务的具体流程为
        //      --- (1) 拿到服务的【动态代理类或者静态代理类】、【接口】、以及【url】
        //      --- (2) 拿着(1)中获取到的三个内容进行真正的服务暴露
        Exporter<DemoService> exporter = protocol.export(serviceInvoker);
        System.out.println("server 启动协议:" + protocol_url.getProtocol());
        // 保证服务一直开着
        System.in.read();
        exporter.unexport();
    }
    

    这里简单提一下dubbo的jar包的引入问题。
    如果你想了解dubbo具体的协议,可以单独引入各个协议的jar包,比如说上面的代码如果想支持dubbo、http、hessian、rmi四种协议,需要引入的jar包如下:

    	<!--dubbo协议,该包里同时含有dubbo的Common等包-->
    	<dependency>
    	    <groupId>com.alibaba</groupId>
    	    <artifactId>dubbo-rpc-dubbo</artifactId>
    	    <version>${dubbo.version}</version>
    	</dependency>	
    	
    	<!--dubbo协议底层用的netty,注意这里要用netty4-->
    	<dependency>
    	    <groupId>com.alibaba</groupId>
    	    <artifactId>dubbo-remoting-netty4</artifactId>
    	    <version>${dubbo.version}</version>
    	</dependency>
    	
    	<!--dubbo协议底层序列化用的hessian2-->
    	<dependency>
    	    <groupId>com.alibaba</groupId>
    	    <artifactId>dubbo-serialization-hessian2</artifactId>
    	    <version>${dubbo.version}</version>
    	</dependency>
    	
    	<!--http协议 该包里同时含有dubbo的Common等包-->
    	<dependency>
    	    <groupId>com.alibaba</groupId>
    	    <artifactId>dubbo-rpc-http</artifactId>
    	    <version>${dubbo.version}</version>
    	</dependency>
    	
    	<!--rmi协议 该包里同时含有dubbo的Common等包-->
    	<dependency>
    	    <groupId>com.alibaba</groupId>
    	    <artifactId>dubbo-rpc-rmi</artifactId>
    	    <version>${dubbo.version}</version>
    	</dependency>
    	
    	<!--hessian协议 该包里同时含有dubbo的Common等包-->
    	<dependency>
    	    <groupId>com.alibaba</groupId>
    	    <artifactId>dubbo-rpc-hessian</artifactId>
    	    <version>${dubbo.version}</version>
    	</dependency>
    

    2.3 dubbo服务暴露过程主要流程梳理

    在这里插入图片描述


    3 dubbo服务消费过程底层原理探秘


    3.1 spring环境下dubbo服务消费过程的前置知识

    以注解配置为例,大家必须要有的前置知识是:当dubbo项目启动时,遇到@Reference(新版本的dubbo为@DubboReference)注解,会创建一个 ReferenceBean实例。该实例实现了InitializingBean 接口, 在其调用的afterPropertiesSet 方法中, 会为服务调用方 — 也就是@Reference标注的那个bean,创建一个远程代理对象。

    创建过程中比较重要的两步源代码如下:


    【1】获取可以调用服务端的服务,并将其和url、interface一起封装成一个Invoker
    dubbo源码(1):消费端直连服务端的情况(在@Reference注解里可以配置直连的url)
    在这里插入图片描述

    dubbo源码(2):从注册中心获取服务端URL的情况
    在这里插入图片描述
    这里需要注意的是:其实这一步已经获取到可以调用服务端的服务了,也就是说这个Invoker里封装了【可以调用服务端的服务】、【服务端的url】和【interface】。

    获取【可以调用服务端的服务】的大致流程如下:
    跟踪源码可以发现,无论是走http协议、rmi协议还是hessian协议【其他的协议我没具体研究】,它最终都会调用一个doRefer方法。我们这里简单看一下RMI协议的doRefer方法的源码:
    在这里插入图片描述
    对比一下我的上篇文章《【dubbo源码解析~番外篇】— JDK和Spring的RMI使用姿势简介》,你就会豁然开朗了,原来这就是通过【服务端url】和【interface】查找到服务端暴露的服务啊。

    当然这里我还是保留上篇文章第4部分的观点:

    消费端咋就找到了服务端的服务,并且为啥在消费端一调用,就会直接调用到服务端的方法这一过程,无需过度探索。


    【2】代理工厂拿着协议对象创建的Invoker,创建实际的代理类,该代理类最终就会成为@Reference注解标注的那个实例bean
    在这里插入图片描述


    3.2 实现一个自己的dubbo消费端

    有了上面的知识后,其实我们也可以仿照dubbo的源码,自己实现一个消费端了。当然还是那句话本文仅仅是探索RPC远程调用的原理,所以一切从简。

    仿照dubbo源码自己写的dubbo消费端:

    @Test
    public void clientRpc() {
        //获取具体的协议
        //  ---由URL确定到底使用哪个协议,默认情况下使用dubbo协议
        Protocol protocol = protocolLoader.getAdaptiveExtension();
    
        //由代理工厂生成Invoker对象
        //  --- Invoker对象里包含了服务的接口和要暴露服务的url
        //  --- 但值得注意的是这里返回的Invoker对象,是用Invoker接口进行接收的,也就是说通过下面的serviceInvoker,只能获取到service的接口
        Invoker<DemoService> referInvoker = protocol.refer(DemoService.class, protocol_url);
    
        //生成代理工厂
        //  --- 由URL确定到底是动态代理工厂(JdkProxyFactory)还是静态代理工厂(JavassistProxyFactory)
        //  --- 默认情况下为静态代理工厂
        ProxyFactory proxy = proxyLoader.getAdaptiveExtension();
    
        //生成DemoService的代理类
        DemoService service = proxy.getProxy(referInvoker);
    
        Map<String, String> info = new HashMap();
        info.put("target", "orderService");
        info.put("methodName", "getOrderInfo");
        info.put("arg", "1");
        Map<String, String> result = service.getHelloInfo(info);
        System.out.println(result);
    }
    

    3.3 dubbo消费端调用服务的主要流程梳理

    在这里插入图片描述


    4 简单测试我们自己写的服务端和消费端

    启用服务端【我提供的代码可以测试的协议有http、rmi、dubbo、hessian】
    在这里插入图片描述
    启用消费端调用服务端的服务

    • (1)可以看到已经获取到了服务端返回的结果
      在这里插入图片描述
    • (2)同时可以看到确实调用到了服务端的服务在这里插入图片描述

    5 我心中Dubbo里最核心的概念: SPI/Invoker

    • (1)SPI
      通过本文的分析,相信你已经看到:【dubbo若想切换底层通讯协议】,【若想切换到底使用动态代理工厂还是静态代理工厂】是多么的easy!!! —> 只需要修改一个URL的参数,就搞定了,是不是很爽!!!

    这里需要再提一下的是:

    看完本文2.1.2的源码分析后,你会不会想到,其实dubbo使用注册中心的玩法,就是扩展了一个Protocol—>RegistryProtocol,然后只要你修改一下URL的参数【表现在dubbo实际使用中,就是修改一下配置文件+引入一下注册中心的jar】,就可以指向那个Protocol了 —》由此dubbo的核心源码还是本文提到的这些,但却可以将注册中心引入进来了—> 代码的扩展性多么好!!!

    • (2)Invoker
      其实Dubbo中的Invoker 概念, 作用不仅仅于此, 它统一了 dubbo 中各组件间相互交流的规范, 大家统一都用 invoker 进行粘合(书同文、 车同轴) 。 后续应该会继续展示 Invoker 更高一层的作用。。。

    2020-07-15 夜

    展开全文
  • 关于服务暴露,首先需要了解:ServiceBean.java 此类实现了几个Spring中比较重要的接口:如InitializingBean、ApplicationContextAware、ApplicationListener等,查看该类结构图: 接下来分析一下该类的一些主要...

    一、服务暴露

    先看一张Dubbo服务暴露的流程图:
    在这里插入图片描述
    关于服务暴露,首先需要了解:ServiceBean.java
    此类实现了几个Spring中比较重要的接口:如InitializingBean、ApplicationContextAware、ApplicationListener等,查看该类结构图:
    在这里插入图片描述

    接下来分析一下该类的一些主要方法:

    ServiceBean.afterPropertiesSet()方法中,就是将配置文件的各项属性信息都配置到该Bean中。最后通过一个ServiceConfig.export();方法暴露服务。

     @SuppressWarnings({ "unchecked", "deprecation" })
        public void afterPropertiesSet() throws Exception {
            if (getProvider() == null) {
                Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null  : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);
                if (providerConfigMap != null && providerConfigMap.size() > 0) {
                    Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null  : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
                    if ((protocolConfigMap == null || protocolConfigMap.size() == 0)
                            && providerConfigMap.size() > 1) { // 兼容旧版本
                        List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
                        for (ProviderConfig config : providerConfigMap.values()) {
                            if (config.isDefault() != null && config.isDefault().booleanValue()) {
                                providerConfigs.add(config);
                            }
                        }
                        if (providerConfigs.size() > 0) {
                            //
                            setProviders(providerConfigs);
                        }
                    } else {
                        ProviderConfig providerConfig = null;
                        for (ProviderConfig config : providerConfigMap.values()) {
                            if (config.isDefault() == null || config.isDefault().booleanValue()) {
                                if (providerConfig != null) {
                                    throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);
                                }
                                providerConfig = config;
                            }
                        }
                        if (providerConfig != null) {
                            setProvider(providerConfig);
                        }
                    }
                }
            }
            if (getApplication() == null
                    && (getProvider() == null || getProvider().getApplication() == null)) {
                Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
                if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
                    ApplicationConfig applicationConfig = null;
                    for (ApplicationConfig config : applicationConfigMap.values()) {
                        if (config.isDefault() == null || config.isDefault().booleanValue()) {
                            if (applicationConfig != null) {
                                throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
                            }
                            applicationConfig = config;
                        }
                    }
                    if (applicationConfig != null) {
                        setApplication(applicationConfig);
                    }
                }
            }
            if (getModule() == null
                    && (getProvider() == null || getProvider().getModule() == null)) {
                Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
                if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
                    ModuleConfig moduleConfig = null;
                    for (ModuleConfig config : moduleConfigMap.values()) {
                        if (config.isDefault() == null || config.isDefault().booleanValue()) {
                            if (moduleConfig != null) {
                                throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
                            }
                            moduleConfig = config;
                        }
                    }
                    if (moduleConfig != null) {
                        setModule(moduleConfig);
                    }
                }
            }
            if ((getRegistries() == null || getRegistries().size() == 0)
                    && (getProvider() == null || getProvider().getRegistries() == null || getProvider().getRegistries().size() == 0)
                    && (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().size() == 0)) {
                Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
                if (registryConfigMap != null && registryConfigMap.size() > 0) {
                    List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
                    for (RegistryConfig config : registryConfigMap.values()) {
                        if (config.isDefault() == null || config.isDefault().booleanValue()) {
                            registryConfigs.add(config);
                        }
                    }
                    if (registryConfigs != null && registryConfigs.size() > 0) {
                        super.setRegistries(registryConfigs);
                    }
                }
            }
            if (getMonitor() == null
                    && (getProvider() == null || getProvider().getMonitor() == null)
                    && (getApplication() == null || getApplication().getMonitor() == null)) {
                Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
                if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
                    MonitorConfig monitorConfig = null;
                    for (MonitorConfig config : monitorConfigMap.values()) {
                        if (config.isDefault() == null || config.isDefault().booleanValue()) {
                            if (monitorConfig != null) {
                                throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
                            }
                            monitorConfig = config;
                        }
                    }
                    if (monitorConfig != null) {
                        setMonitor(monitorConfig);
                    }
                }
            }
            if ((getProtocols() == null || getProtocols().size() == 0)
                    && (getProvider() == null || getProvider().getProtocols() == null || getProvider().getProtocols().size() == 0)) {
                Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null  : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
                if (protocolConfigMap != null && protocolConfigMap.size() > 0) {
                    List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
                    for (ProtocolConfig config : protocolConfigMap.values()) {
                        if (config.isDefault() == null || config.isDefault().booleanValue()) {
                            protocolConfigs.add(config);
                        }
                    }
                    if (protocolConfigs != null && protocolConfigs.size() > 0) {
                        super.setProtocols(protocolConfigs);
                    }
                }
            }
            if (getPath() == null || getPath().length() == 0) {
                if (beanName != null && beanName.length() > 0 
                        && getInterface() != null && getInterface().length() > 0
                        && beanName.startsWith(getInterface())) {
                    setPath(beanName);
                }
            }
            if (! isDelay()) {
                //此方法最为关键,即服务暴露的动作从此时触发
                export();
            }
        }
    
    

    再来看看一下ServiceConfig.export()这个方法:

    public class ServiceConfig<T> extends AbstractServiceConfig {
    
    //...
    
    public synchronized void export() {
            if (provider != null) {
                if (export == null) {
                    export = provider.getExport();
                }
                if (delay == null) {
                    delay = provider.getDelay();
                }
            }
            if (export != null && ! export.booleanValue()) {
                return;
            }
            if (delay != null && delay > 0) {
                Thread thread = new Thread(new Runnable() {
                    public void run() {
                        try {
                            Thread.sleep(delay);
                        } catch (Throwable e) {
                        }
                        //主要核心操作在于doExport()
                        doExport();
                    }
                });
                thread.setDaemon(true);
                thread.setName("DelayExportServiceThread");
                thread.start();
            } else {
                doExport();
            }
        }
    }
    

    接下来看看ServiceConfig.doExport()方法:

    public class ServiceConfig<T> extends AbstractServiceConfig {
    
    //...
    
    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!");
            }
            checkDefault();
            if (provider != null) {
                if (application == null) {
                    application = provider.getApplication();
                }
                if (module == null) {
                    module = provider.getModule();
                }
                if (registries == null) {
                    registries = provider.getRegistries();
                }
                if (monitor == null) {
                    monitor = provider.getMonitor();
                }
                if (protocols == null) {
                    protocols = provider.getProtocols();
                }
            }
            if (module != null) {
                if (registries == null) {
                    registries = module.getRegistries();
                }
                if (monitor == null) {
                    monitor = module.getMonitor();
                }
            }
            if (application != null) {
                if (registries == null) {
                    registries = application.getRegistries();
                }
                if (monitor == null) {
                    monitor = application.getMonitor();
                }
            }
            if (ref instanceof GenericService) {
                interfaceClass = GenericService.class;
                generic = true;
            } else {
                try {
                    interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                            .getContextClassLoader());
                } catch (ClassNotFoundException e) {
                    throw new IllegalStateException(e.getMessage(), e);
                }
                checkInterfaceAndMethods(interfaceClass, methods);
                checkRef();
                generic = false;
            }
            if(local !=null){
                if(local=="true"){
                    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 implemention class " + localClass.getName() + " not implement interface " + interfaceName);
                }
            }
            if(stub !=null){
                if(stub=="true"){
                    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 implemention class " + stubClass.getName() + " not implement interface " + interfaceName);
                }
            }
            checkApplication();
            checkRegistry();
            checkProtocol();
            appendProperties(this);
            checkStubAndMock(interfaceClass);
            if (path == null || path.length() == 0) {
                path = interfaceName;
            }
            //暴露url操作
            doExportUrls();
        }
    
    

    再来看看上述代码中暴露url的代码doExportUrls()(比较烦躁,坚持啊)。

     @SuppressWarnings({ "unchecked", "rawtypes" })
        private void doExportUrls() {
            List<URL> registryURLs = loadRegistries(true);
            for (ProtocolConfig protocolConfig : protocols) {
                //获取注册中心的信息,根据协议暴露对应的url
                doExportUrlsFor1Protocol(protocolConfig, registryURLs);
            }
        }
    

    ServiceConfig.doExportUrlsFor1Protocol(protocolConfig, registryURLs)源码如下,进行Url暴露的细节:

    
    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
            String name = protocolConfig.getName();
            if (name == null || name.length() == 0) {
                name = "dubbo";
            }
    
            String host = protocolConfig.getHost();
            if (provider != null && (host == null || host.length() == 0)) {
                host = provider.getHost();
            }
            boolean anyhost = false;
            if (NetUtils.isInvalidLocalHost(host)) {
                anyhost = true;
                try {
                    host = InetAddress.getLocalHost().getHostAddress();
                } catch (UnknownHostException e) {
                    logger.warn(e.getMessage(), e);
                }
                if (NetUtils.isInvalidLocalHost(host)) {
                    if (registryURLs != null && registryURLs.size() > 0) {
                        for (URL registryURL : registryURLs) {
                            try {
                                Socket socket = new Socket();
                                try {
                                    SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
                                    socket.connect(addr, 1000);
                                    host = socket.getLocalAddress().getHostAddress();
                                    break;
                                } finally {
                                    try {
                                        socket.close();
                                    } catch (Throwable e) {}
                                }
                            } catch (Exception e) {
                                logger.warn(e.getMessage(), e);
                            }
                        }
                    }
                    if (NetUtils.isInvalidLocalHost(host)) {
                        host = NetUtils.getLocalHost();
                    }
                }
            }
    
            Integer port = protocolConfig.getPort();
            if (provider != null && (port == null || port == 0)) {
                port = provider.getPort();
            }
            final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
            if (port == null || port == 0) {
                port = defaultPort;
            }
            if (port == null || port <= 0) {
                port = getRandomPort(name);
                if (port == null || port < 0) {
                    port = NetUtils.getAvailablePort(defaultPort);
                    putRandomPort(name, port);
                }
                logger.warn("Use random available port(" + port + ") for protocol " + name);
            }
    
            Map<String, String> map = new HashMap<String, String>();
            if (anyhost) {
                map.put(Constants.ANYHOST_KEY, "true");
            }
            map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
            map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
            map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
            if (ConfigUtils.getPid() > 0) {
                map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
            }
            appendParameters(map, application);
            appendParameters(map, module);
            appendParameters(map, provider, Constants.DEFAULT_KEY);
            appendParameters(map, protocolConfig);
            appendParameters(map, this);
            if (methods != null && methods.size() > 0) {
                for (MethodConfig method : methods) {
                    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 (arguments != null && arguments.size() > 0) {
                        for (ArgumentConfig argument : arguments) {
                            //类型自动转换.
                            if(argument.getType() != null && argument.getType().length() >0){
                                Method[] methods = interfaceClass.getMethods();
                                //遍历所有方法
                                if(methods != null && methods.length > 0){
                                    for (int i = 0; i < methods.length; i++) {
                                        String methodName = methods[i].getName();
                                        //匹配方法名称,获取方法签名.
                                        if(methodName.equals(method.getName())){
                                            Class<?>[] argtypes = methods[i].getParameterTypes();
                                            //一个方法中单个callback
                                            if (argument.getIndex() != -1 ){
                                                if (argtypes[argument.getIndex()].getName().equals(argument.getType())){
                                                    appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                                }else {
                                                    throw new IllegalArgumentException("argument config error : the index attribute and type attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());
                                                }
                                            } else {
                                                //一个方法中多个callback
                                                for (int j = 0 ;j<argtypes.length ;j++) {
                                                    Class<?> argclazz = argtypes[j];
                                                    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 attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }else if(argument.getIndex() != -1){
                                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 (generic) {
                map.put("generic", String.valueOf(true));
                map.put("methods", 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("methods", Constants.ANY_VALUE);
                }
                else {
                    map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
                }
            }
            if (! ConfigUtils.isEmpty(token)) {
                if (ConfigUtils.isDefault(token)) {
                    map.put("token", UUID.randomUUID().toString());
                } else {
                    map.put("token", token);
                }
            }
            if ("injvm".equals(protocolConfig.getName())) {
                protocolConfig.setRegister(false);
                map.put("notify", "false");
            }
            // 导出服务
            String contextPath = protocolConfig.getContextpath();
            if ((contextPath == null || contextPath.length() == 0) && provider != null) {
                contextPath = provider.getContextpath();
            }
            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);
            }
    
            String scope = url.getParameter(Constants.SCOPE_KEY);
            //配置为none不暴露
            if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
    
                //配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)
                if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                    exportLocal(url);
                }
                //如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务)
                if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
                    if (logger.isInfoEnabled()) {
                        logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                    }
                    if (registryURLs != null && registryURLs.size() > 0
                            && url.getParameter("register", true)) {
                        for (URL registryURL : registryURLs) {
                            url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
                            URL monitorUrl = loadMonitor(registryURL);
                            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);
                            }
                            /**
                             * 暴露远程服务:<br>
                             * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
                             * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
                             * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
                             * 
                             * @param <T> 服务的类型
                             * @param invoker 服务的执行体
                             * @return exporter 暴露服务的引用,用于取消暴露
                             * @throws RpcException 当暴露服务出错时抛出,比如端口已占用
                             */
                            Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
    
                            Exporter<?> exporter = protocol.export(invoker);
                            exporters.add(exporter);
                        }
                    } else {
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                        /**
                         * 暴露远程服务:<br>
                         * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
                         * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
                         * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
                         * 
                         * @param <T> 服务的类型
                         * @param invoker 服务的执行体
                         * @return exporter 暴露服务的引用,用于取消暴露
                         * @throws RpcException 当暴露服务出错时抛出,比如端口已占用
                         */
                        Exporter<?> exporter = protocol.export(invoker);
                        exporters.add(exporter);
                    }
                }
            }
            this.urls.add(url);
        }
    
    

    然后通过protocol.export(invoker)方法把服务暴露出去(会根据当前的协议做相应的暴露操作),最后将Url和对应的执行器存起来,存放在注册表中,以供消费者调用。
    在这里插入图片描述

    这就是一个简洁的Dubbo服务暴露的过程了。

    二、服务引用

    如下为服务引用的流程图,可以参考服务暴露的过程进行源码调试。
    其过程简而言之通过一系列的信息,获取到接口对应的代理对象,存放于注册表中。
    在这里插入图片描述

    三、调用流程

    Dubbo的调用流程,可以参考官网的一张服务调用流程图来对源码进行一步步的调试,便于理解(防止文章过长此处就不贴源码了):
    在这里插入图片描述

    更多原理了解,可以到官网查看Dubbo源码导读

    展开全文
  • dubbo服务暴露、引入、调用过程

    千次阅读 2021-12-29 10:52:59
    proxy:服务提供者/消费者都会生成一个代理类,由代理进行远程调用和返回结果; register:注册层,封装了服务注册与发现; cluster:路由层,负责具体调用的节点以及调用失败的容错; monitor:监控层,监控调用...
  • springboot实现远程服务暴露调用

    千次阅读 2019-02-26 10:58:02
    服务端暴露接口: 1、定义接口 public interface DemoInterface { BaseResponse get(QueryDTO dto); } 2、接口实现类 @Slf4j @Service public class DemoInterfaceImpl implements DemoInterface { @...
  • dubbo的调用原理及泛化调用

    千次阅读 2020-06-20 17:59:08
    dubbo是阿里开源出来的一个rpc框架,主要是用于微服务分布式项目的远程调用,它提供了三大核心能力:面向接口的远程方法调用,智能容错负载均衡,以及服务自动注册发现,下面是调用原理图: dubbo框架的整体...
  • 一、RPC原理 一次完整的RPC调用流程(同步调用,异步另说)如下: 1)服务消费方(client)调用以本地调用方式调用服务; 2)client stub【客户端代理】接收到调用后负责将方法、参数等组装成...
  • 调用别人接口如何跳过登陆

    千次阅读 2019-07-10 17:53:21
    背景 有时候在接口对接数据时,常常会碰到这样的问题,就是请求接口的时候,他没有返回...利用Cookie跳过登录原理就是登录过一次,查看登录后的headercookie信息,将其关键信息拿下来,然后将这些信息加入你的接...
  • 其实从这里我们就可以想到,实现的原理就是用到了动态代理.也就是实际调用的是spring帮我们生成的代理对象. 好像就是这么回事?但是这样显得浅尝辄止,如何生成代理对象,以及生成了怎样的代理对象的才是我们应该知道的...
  • Java远程调用WebService接口

    千次阅读 2021-03-09 09:23:24
    简单的说,WebService就是一种跨编程语言跨操作系统平台的远程调用技术。所谓跨编程语言跨操作平台,就是说服务端程序采用java编写,客户端程序则可以采用其他编程语言编写,反之亦然。跨操作系统平台则是指...
  • 前端代码中如何避免暴露后端接口

    千次阅读 2021-04-16 15:12:36
    在日常开发中,后端同事会给前端一个地址,这个地址我们不想暴露出来,具体操作该怎么做? 这里已vue3 的项目为例子。react 原理是一样的 第一步前端配置 .env.production文件 VUE_APP_NODE_ENV = "'production'" ...
  • 如何把项目打jar包,然后暴露接口给第三方应用提供服务【实战讲解】 下面这个例子,是我在开源项目CR949中使用到的部分代码,作为讲解,发布到这里。 jar包中的controller,如何对外暴露接口。 这样一个场景:比如...
  • Feign是声明式Web Service客户端,它让微服务之间的调用变得更简单,类似controller调用service。SpringCloud集成了RibbonEureka,可以使用Feigin提供负载均衡的http客户端 只需要创建一个接口,然后添加注解即可...
  • 做后端开发,避免不了要写接口,最开始我们写接口是为了满足实现具体的功能,能在客户端正常调用就行了,这种接口称为裸接口,就跟一个人没有穿衣服一样,我们在家的时候当然可以这么做,肆意放荡,这没关系,但是人...
  • 微服务框架学习二:Http调用

    千次阅读 2021-03-15 02:25:06
    1. HTTP接口的意义二进制接口使用的是java/hessian序列化协议,不能很好的与其他语言通信,虽然hessian也是一种跨语言的通用协议,...2. 原理简图描述:通过Netty暴露http服务端口,接收到http请求,通过HttpDecoder...
  • Dubbo——远程(RPC)调用原理

    千次阅读 2021-04-01 12:04:09
    并将原理和源码进行分析和测试。随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。 单一应用架构:当...
  • 一行代码搞定Dubbo接口调用

    千次阅读 2018-09-11 11:53:35
    本文来自网易云社区作者:吕彦峰在工作中我们经常遇到关于接口测试的问题,无论是对于QA同学还是开发同学都会有远程接口调用的需求。针对这种问题我研发了一个工具包,专门用于远程Dubbo调用,下面就让我们一起来...
  • 接口实例:系统与系统间的接口调用,作用:实现了两个或多个独立系统或模块间的通信数据交换能力。 常见的Web接口类型 REST接口——通过HTTP的getpost方式得到数据,返回报文json格式 SOAP...
  • OpenFeign调用服务的核心原理解析

    千次阅读 2022-03-18 11:19:32
    但是其RPC在实现上还是不太一样,不一样的地方主要还是调用的方式,OpenFeign是内部实现了rest服务调用,从而一个本地服务调用远程服务的接口时,主要还是通过rest服务调用的方式,那么对于服务端的要求就是其服务...
  • 我们将系统分为两个,一个称为服务端,另一个称为客户端,原理是,服务端进行配置,用于暴露接口,定义链接接口的规则.客户端进行配置,寻找有相同规则且被暴露的接口,网上的hessian教程很多,博主也是参考众家后终于写成了...
  • Dubbo 服务调用原理浅析

    千次阅读 2017-09-12 19:16:22
    dubbo概念dubbo原理dubbo应用场景 Dubbo概念: Dubbo是一个分布式服务框架,致力于提供高性能透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是...
  • 一、接口编程的引入 在mybatis引入接口编程之前,我们可以回顾一下mybatis编程开始的几大步骤: 1、通过SqlSessionFactoryBuilder创建SqlSessionFactory对象: 这里嵌套使用了建造者模式,主要涉及到两个建造者类...
  • 跨系统调用接口方案--WebService

    千次阅读 2019-03-04 09:58:07
    webservice是一个 SOA(面向服务的编程)的架构,它是不依赖于语言,不依赖于平台,可以实现不同的语言间的相互调用,通过 Internet 进行基于 Http 协议的网络应用间的交互。 1、异构系统(不同语言)的整合 2、不同...
  • 或者第三方模块是golang写的,则需要把第三方编译成so文件,然后再C代码汇总访问其暴露出来的接口。 比如这里我让golang代码提供函数功能,并且将自己编译成so动态链接库,在C代码中调用这个库中的接口。 1.1、 ...
  • 接口测试原理

    千次阅读 2020-04-08 10:53:04
    接口测试就是通过测试不同情况下的输入参数与之对应的输出结果来判断接口是否符合或满足相应的功能性、安全性要求. 简单的说,接口测试是就是代替前端或者第三方,来验证后端实现是否符合接口规范. 2.2 接口测试的...
  • IDEA利用wsdl文件生成WebService调用接口的方法 1、首先需要要调用的webservise服务器上访问wsdl的地址,如下: http:// IP地址 : 端口号/ormrpc/services 2、打开该地址如下所示: 如上图所示,那么访问的接口的...
  • 模块接口间三中调用方式

    千次阅读 2016-07-27 14:06:40
    软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调异步调用。同步调用是一种阻塞式调用调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是...
  • 前言 ...服务提供方的接口暴露方式是通过Controller暴露Restful,而在这个Controller的代码现实中大部分都是处理请求然后再调用Service中的方法,是一个比较模板化的功能,但是工作量确不少。本文...
  • 【COM原理和应用】2、COM对象和接口

    千次阅读 2015-02-04 17:56:45
    ②局部接口指针:在一个局部函数块中,接口指针一直有效,因此局部接口指针被赋值并调用接口成员函数不需要调用AddRefRelease。 ③全局接口指针:任何一个函数都可以访问全局接口指针,所以将全局接口指针传入...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 59,008
精华内容 23,603
热门标签
关键字:

暴露接口和调用接口的原理

友情链接: nodeTreeLibrary.zip