• 【Dubbo3高级特性】「框架与服务」Dubbo3客户端和服务端的泛化调用机制体系


    系列文章目录

    如果你看到了这里,那么接下来你将会认识Dubbo3的诞生将如何引领微服务领域更进一步,从而迈入云原生的领域,这当然不仅仅是Dubbo3,之前也介绍了Java生态另外一个云原生领域的技术Quarkus等技术,而本文内容侧重点去介绍Dubbo3迈向云原生
    的技术分析和探索,如果有不正确的地方,还需要大家多多指正。

    在这里插入图片描述



    Dubbo3的泛化技术而言主要是面向于外部接口调用抽象能力和抽象粒度的泛化提升,主要对于服务的提供者和消费组均有泛化实现的体系功能。

    实现泛化实现(服务端泛化)

    Dubbo3服务端泛接口实现方式主要用于服务器端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的远程服务框架,可通过实现RawTypeService接口处理所有服务请求。

    定义RawTypeService

    我们自己定义一个接口实现类机制命名为RawTypeService类,用于继承我们Dubbo框架默认的泛型标准接口:org.apache.dubbo.rpc.service.GenericService,GenericService的源码如下。

    package org.apache.dubbo.rpc.service;
    import java.util.concurrent.CompletableFuture;
    /**
     * Generic service interface
     * @export
     */
    public interface GenericService {
        /**
         * Generic invocation
         *
         * @param method         Method name, e.g. findPerson. If there are overridden methods, parameter info is
         *                       required, e.g. findPerson(java.lang.String)
         * @param parameterTypes Parameter types
         * @param args           Arguments
         * @return invocation return value
         * @throws GenericException potential exception thrown from the invocation
         */
        Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
        default CompletableFuture<Object> $invokeAsync(String method, String[] parameterTypes, Object[] args) throws GenericException {
            Object object = $invoke(method, parameterTypes, args);
            if (object instanceof CompletableFuture) {
                return (CompletableFuture<Object>) object;
            }
            return CompletableFuture.completedFuture(object);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    GenericService这个业务逻辑接口类是框架提供给我们的实现泛型话功能的调用机制体系,那么现在就来试一下如何定义一下我们自己的泛型RPC调用框架服务。

    定义协议接口(主要暴漏给客户端用)

    用于暴漏给客户端进行暴漏调用使用的接口服务实现类机制。

    public interface GenericTypeService {
        String process(String name);
        String execute(String name);
    }
    
    • 1
    • 2
    • 3
    • 4

    使用方式

    在Java代码中实现RawTypeService接口,主要就是实现GenericService这个类型的$invoke方法类的实现机制,我们看一看到这个方法的入参就能够得知,我们是可以利用这个方法调用实现任务体系功能的实现机制。

    参数解释
    • methodName:调用方法的名称
    • parameterTypes:调用方法的参数类(理解为形式参数表)
    • args:调用方法的实际参数列表
    public class RawTypeService implements GenericService {
        @Override
        public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
            if (method.equals("process")) {
                System.out.print("executing method1.");
                throw new RuntimeException("method1: throws exception");
            } else if (methodName.equals("execute")) {
                System.out.print("executing method2.");
                return CompletableFuture.completedFuture("method2: result" + args[0]);
            } else {
                try {
                    return defaultOperation(method, parameterTypes, args);
                } catch (Exception e) {
                    throw new GenericException(e);
                }
            }
        }
    
        private Object defaultOperation(String method, String[] parameterTypes, Object[] args) throws Exception {
            throw new UnsupportedOperationException("method does not exist.");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 实现类的方案就是:根据参数:methodName,进行判断是否进行实现选择对应的实现类的底层方法,我上面的案例只是单纯的实现了以下打印操作,实际情况可能会更加复杂。
    • 当发生methodName相同的场景下,那么我们就需要通过重载的机制,进行判断参数列表的类型,以作为我们的判断标准机制 ,进行匹配到不同的实现机制。

    通过Spring的XML方式暴露泛化实现

    在Spring的xml配置模式进行暴漏和注册服务的实现类体系。

    <bean id="rawTypeService" class="com.xx.RawTypeService" />
    <dubbo:service interface="com.xx.AnnotationService" ref="rawTypeService"/>
    
    • 1
    • 2

    其中的AnnotationService类代表着我们的定义的协议接口。

    通过原生API方式暴露泛化实现

    org.apache.dubbo.rpc.service.GenericService可以替代所有相关的业务接口实现控制

    GenericService rawTypeService = new RawTypeService(); 
    
    • 1

    ServiceConfig的实例很重,里面封装了所有与注册中心及服务提供方连接,如果使用APi模式,建议请缓存下来。

    ApplicationConfig applicationConfig = new ApplicationConfig();
    applicationConfig.setName("generic-impl-provider");
    RegistryConfig registryConfig = new RegistryConfig();
    registryConfig.setAddress(zookeeperAddress);
    ServiceConfig<GenericService> service = new ServiceConfig<>();
    service.setApplication(applicationConfig);
    service.setRegistry(registryConfig);
    service.setInterface("com.xx.AnnotationService");
    service.setRef(rawTypeService);
    service.setGeneric("true"); // 后面的版本貌似不用再手动指定了,因为后面的版本已经基本上可以服务自己判定。
    service.export();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过Spring的Annotation方式暴露泛化实现

    我们通过@DubboService注解进行修饰对应的实现类即可

    @DubboService(interfaceName=com.xx.AnnotationService")
    public class RawTypeService implements GenericService {
        @Override
        public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
            if (method.equals("method1")) {
                System.out.print("executing method1.");
                throw new RuntimeException("method1: throws exception");
            } else if (methodName.equals("method2")) {
                System.out.print("executing method2.");
                return CompletableFuture.completedFuture("method2: result" + args[0]);
            } else {
                try {
                    return defaultOperation(method, parameterTypes, args);
                } catch (Exception e) {
                    throw new GenericException(e);
                }
            }
        }
        private Object defaultOperation(String method, String[] parameterTypes, Object[] args) throws Exception {
            throw new UnsupportedOperationException("method does not exist.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    Spring的XML模式进行相关的配置消费

    即可实现相关的dubbo的暴漏消费接口实现机制。

    
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
           xmlns="http://www.springframework.org/schema/beans" 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://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
        <context:property-placeholder/>
        <dubbo:application name="generic-impl-consumer"/>
        <dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
        <dubbo:reference timeout="6000000" id="rawTypeService" check="true"
                         interface="com.xx.AnnotationService"/>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
     public static void main(String[] args) throws Exception {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/generic-impl-consumer.xml");
            context.start();
            AnnotationService annotationService = (AnnotationService) context.getBean("annotationService");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Spring的Annotation模式进行相关的配置消费

    @DubboReference(generic=true)
    private AnnotationService helloService;
    
    • 1
    • 2

    Spring的API模式进行相关的配置消费

    ApplicationConfig applicationConfig = new ApplicationConfig();
    applicationConfig.setName("generic-call-consumer");
    RegistryConfig registryConfig = new RegistryConfig();
    registryConfig.setAddress("zookeeper://127.0.0.1:2181");
    ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
    referenceConfig.setInterface("com.xx.AnnotationService");
    applicationConfig.setRegistry(registryConfig);
    referenceConfig.setApplication(applicationConfig);
    referenceConfig.setGeneric(true);
    referenceConfig.setTimeout(7000);
    genericService = referenceConfig.get();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    泛化调用实现(客户端泛化)

    特性说明

    客户端泛化调用是指在调用方没有服务方提供的API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果,当然也可能进行动态调用对应的方法和对应的服务接口。

    使用场景

    泛化调用主要用于实现一个通用的远程服务框架,可通过实现GenericService接口处理所有服务请求。比如如下场景:

    网关服务中心

    如果要搭建一个网关服务,那么服务网关要作为所有RPC服务的调用端。但是网关本身不应该依赖于服务提供方的接口API(这样会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以需要泛化调用的支持。

    测试Mock平台

    如果要搭建一个可以测试RPC调用的平台,用户输入分组名、接口、方法名等信息,就可以测试对应的RPC服务。那么由于同样的原因(即会导致每有一个新的服务发布,就需要修改网关的代码以及重新部署),所以平台本身不应该依赖于服务提供方的接口API。所以需要泛化调用的支持。

    给予之前AnnotationService接口

    服务实现类
    @Slf4j
    public class RawTypeService implements AnnotationService {
    	
    	@override
    	public String method1(String name){
    		log.info("method1:{}",name);	
    	}	
    
    	@override
    	public String method2(String name){
    		log.info("method2:{}",name);	
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    通过API使用泛化调用
    服务提供方
    1. 在设置ServiceConfig时,使用setGeneric(“true”)来开启泛化调用

    2. 在设置ServiceConfig时,使用setRef指定实现类时,要设置一个GenericService 的对象,而不是真正的服务实现类对象。

    3. 其他设置与正常Api服务启动一致即可

        public static void main(String[] args) throws Exception {
            new EmbeddedZooKeeper(2181, false).start();
            //创建ApplicationConfig
            ApplicationConfig applicationConfig = new ApplicationConfig();
            applicationConfig.setName("generic-impl-provider");
            //创建注册中心配置
            RegistryConfig registryConfig = new RegistryConfig();
            registryConfig.setAddress(zookeeperAddress);
            //新建服务实现类,注意要使用GenericService接收
            AnnotationService helloService = new RawTypeService();
            //创建服务相关配置
            ServiceConfig<AnnotationService> service = new ServiceConfig<>();
            service.setApplication(applicationConfig);
            service.setRegistry(registryConfig);
            service.setInterface("com.xx.AnnotationService");
            service.setRef(helloService);
            //重点:设置为泛化调用
            //注:不再推荐使用参数为布尔值的setGeneric函数
            service.setGeneric("true");
            service.export();
            System.out.println("dubbo service started");
            new CountDownLatch(1).await();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    泛化调用方
    步骤:
    1. 在设置 ReferenceConfig 时,使用 setGeneric(“true”) 来开启泛化调用

    2. 配置完 ReferenceConfig 后,使用 referenceConfig.get() 获取到 GenericService类的实例

    3. 使用其 $invoke 方法获取结果

    4. 其他设置与正常 Api 服务启动一致即可

     public static void main(String[] args) throws Exception {
            //创建ApplicationConfig
            ApplicationConfig applicationConfig = new ApplicationConfig();
            applicationConfig.setName("generic-call-consumer");
            //创建注册中心配置
            RegistryConfig registryConfig = new RegistryConfig();
            registryConfig.setAddress("zookeeper://127.0.0.1:2181");
            //创建服务引用配置
            ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
            //设置接口
            referenceConfig.setInterface("com.xx.AnnotationService");
            applicationConfig.setRegistry(registryConfig);
            referenceConfig.setApplication(applicationConfig);
            //重点:设置为泛化调用
            //注:不再推荐使用参数为布尔值的setGeneric函数
            //应该使用referenceConfig.setGeneric("true")代替
            referenceConfig.setGeneric(true);
            //设置超时时间
            referenceConfig.setTimeout(7000);
            //获取服务,由于是泛化调用,所以获取的一定是GenericService类型
            GenericService genericService = referenceConfig.get();
            //使用GenericService类对象的$invoke方法可以代替原方法使用
            //第一个参数是需要调用的方法名
            //第二个参数是需要调用的方法的参数类型数组,为String数组,里面存入参数的全类名。
            //第三个参数是需要调用的方法的参数数组,为Object数组,里面存入需要的参数。
            Object result = genericService.$invoke("method1", new String[]{"java.lang.String"}, new Object[]{"world"});
            //使用CountDownLatch,如果使用同步调用则不需要这么做。
            CountDownLatch latch = new CountDownLatch(1);
            //获取结果
            CompletableFuture<String> future = RpcContext.getContext().getCompletableFuture();
            future.whenComplete((value, t) -> {
                System.err.println("invoke(whenComplete): " + value);
                latch.countDown();
            });
            //打印结果
            System.err.println("invoke(return): " + result);
            latch.await();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    通过Spring使用泛化调用

    Spring中服务暴露与服务发现有多种使用方式,如xml,注解。这里以xml为例。 步骤:

    生产者端无需改动

    消费者端原有的 dubbo:reference 标签加上 generic=true 的属性。

    xml文件配置方式
    <dubbo:reference id="annotationService" generic = "true" interface="com.xx.AnnotationService"/>
    
    • 1
    annotation配置方式
    @DubboReference(interface="com.xx.AnnotationService")
    GenericService genericService;
    
    • 1
    • 2

    获取到 Bean 容器,通过 Bean 容器拿到 GenericService 实例。调用 $invoke 方法获取结果

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/generic-impl-consumer.xml");
    context.start();
    //服务对应bean的名字由xml标签的id决定
    GenericService genericService = context.getBean("annotationService");
    
    • 1
    • 2
    • 3
    • 4
    实际消费者进行动态化泛化调用
    //获得结果
    Object result = genericService.$invoke("method1", new String[]{"java.lang.String"}, new Object[]{"world"});
    
    • 1
    • 2
     @DubboReference(interfaceClass = CommonRpcApi.class)
     GenericService commonRpcApi2;
    
     @GetMapping("/generic/consumer")
        public ResponseEntity consumer(){
            return ResponseEntity.ok(commonRpcApi2.
                    $invoke("tokenAuth",
                    new String[]{RpcRequest.class.getName()},new Object[]{new RpcRequest(RpcContext.getContext().getAttachment("token"))}));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    [JavaScript]_[中级]_[动态创建表单并提交到新页面_实现后台内容预览]
    Matlab:在不同区域设置之间共享代码和数据
    【Shell篇二】Shell变量与字符串
    .NET程序配置文件操作(ini,cfg,config)
    视频编解码 — 帧内预测
    如何为你输出的allure测试报告—锦上添花
    MQ---第一篇
    SpringMVC Day 01:入门案例
    疫情中的低代码平台——为企业带来了什么便利?
    基于Java实现的绘图软件工具设计
  • 原文地址:https://blog.csdn.net/l569590478/article/details/127857113