dubbo泛化调用使用及原理示例解析

 更新时间:2023年09月11日 15:18:14   作者:土豆肉丝盖浇饭  
这篇文章主要为大家介绍了dubbo泛化调用使用及原理示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

什么是泛化调用

本文案例代码见 git@github.com:shengchaojie/dubbo_best_practise.git

通常我们想调用别人的dubbo服务时,我们需要在项目中引入对应的jar包。而泛化调用的作用是,我们无需依赖相关jar包,也能调用到该服务。

这个特性一般使用在网关类项目中,在业务开发中基本不会使用。

使用方式

假设我现在要调用下面的接口服务

package com.scj.demo.dubbo.provider.service.impl;
public interface ByeService {
    String bye(String name);
}

api

ReferenceConfig<GenericService> referenceConfig = new ReferenceConfig<>();
referenceConfig.setApplication(new ApplicationConfig("test"));
referenceConfig.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
referenceConfig.setInterface("com.scj.demo.dubbo.provider.service.impl.ByeService");
referenceConfig.setGeneric(true);
GenericService genericService = referenceConfig.get();
Object result = genericService.$invoke(
    "bye",
    new String[]{"java.lang.String"},
    new Object[]{"1234"});
System.out.println(result);

spring

在xml文件做以下配置

<dubbo:reference id="byeService" interface="com.scj.demo.dubbo.provider.service.impl.ByeService" generic="true" />

然后注入使用

@Service
public class PersonService {
    @Resource(name = "byeService")
    private GenericService genericService;
    public void sayBye(){
        Object result = genericService.$invoke(
                "bye",
                new String[]{"java.lang.String"},
                new Object[]{"1234"});
        System.out.println(result);
    }
}

在两种调用方式中,我们都需要使用被调用接口的字符串参数生成GenericService,通过GenericService的$invoke间接调用目标接口的接口。

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 Throwable potential exception thrown from the invocation
     */
    Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
}

$invoke的三个参数分别为,方法名,方法参数类型数组,方法参数数组。

方法入参构造

可以看到泛化调用的一个复杂性在于$invoke的第三个参数的组装,下面介绍几种复杂入参的调用方式

首先丰富提供者接口

public interface ByeService {
    String bye(String name);
    String bye(String name, Long age, Date date);
    String bye(Person person);
    String bye(List<String> names);
    String bye(String[] names);
    String byePersons(List<Person> persons);
    String byePersons(Person[] persons);
    @Data
    public static class Person{
        private String name;
        private Long age;
        private Date birth;
    }
    public static void main(String[] args) {
        System.out.println(Person.class.getName());
    }
}

多参数

    @Test
    public void testMultiParam(){
        result = genericService.$invoke(
                "bye",
                new String[]{"java.lang.String","java.lang.Long","java.util.Date"},
                new Object[]{"scj",12L,new Date()});
        System.out.println(result);
    }

POJO

    Map<String,Object> personMap = new HashMap<>();
    {
        personMap.put("name","scj");
        personMap.put("age","12");
        personMap.put("birth",new Date());
    }
    @Test
    public void testPOJO(){
        result = genericService.$invoke(
                "bye",
                new String[]{"com.scj.demo.dubbo.provider.service.impl.ByeService$Person"},
                new Object[]{personMap});
        System.out.println(result);
    }

Map

    @Test
    public void testMap(){
        result = genericService.$invoke(
                "bye",
                new String[]{"java.util.Map"},
                new Object[]{personMap});
        System.out.println(result);
    }

集合

    @Test
    public void testList(){
        List<String> names = Lists.newArrayList("scj1","scj2");
        result = genericService.$invoke(
                "bye",
                new String[]{"java.util.List"},
                new Object[]{names});
        System.out.println(result);
    }

数组

    @Test
    public void testArray(){
        String[] nameArray = new String[]{"scj1","scj3"};
        result = genericService.$invoke(
                "bye",
                new String[]{"java.lang.String[]"},
                new Object[]{nameArray});
        System.out.println(result);
    }

集合+POJO

    @Test
    public void testPOJOList(){
        result = genericService.$invoke(
                "byePersons",
                new String[]{"java.util.List"},
                new Object[]{Lists.newArrayList(personMap,personMap)});

        System.out.println(result);
    }

数组+POJO

    @Test
    public void testPOJOArray(){
        result = genericService.$invoke(
                "byePersons",
                new String[]{"com.scj.demo.dubbo.provider.service.impl.ByeService$Person[]"},
                new Object[]{Lists.newArrayList(personMap,personMap)});

        System.out.println(result);
    }

结果返回

与入参相似,虽然$invoke的返回定义为Object,实际上针对不同类型有不同的返回。

别想着转换为POJO,你都泛化调用了,搞不到接口,如何转换。当然自己定义一个完全一样的当然也行。

接口返回类型$invoke返回类型
基础类型基础类型
POJOHashMap
CollectionList返回ArrayList,Set返回HashSet
ArrayArray
组合类型根据上述映射组合返回

原理介绍

消费者端

泛化调用和直接调用在消费者者端,在使用上的区别是,我们调用服务时使用的接口为GenericService,方法为$invoker。在底层的区别是,消费者端发出的rpc报文发生了变化。

使用方式上的改变

在使用上,不管哪种配置方式,我们都需要配置generic=true

设置generic=true后,RefereceConfig的interfaceClass会被强制设置为GenericService

if (ProtocolUtils.isGeneric(getGeneric())) {
    //如果是泛化调用
    interfaceClass = GenericService.class;
} else {
    try {
        interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                                       .getContextClassLoader());
    } catch (ClassNotFoundException e) {
        throw new IllegalStateException(e.getMessage(), e);
    }
    checkInterfaceAndMethods(interfaceClass, methods);
}

这也使得我们的RefereanceBean返回的是GenericService类型的代理。

invoker = refprotocol.refer(interfaceClass, urls.get(0));

生成的代理是GenericService的代理只是我们使用方式上的变化,更为核心的是,底层发送的rpc报文发生了什么变化。

底层报文变化

Dubbo的rpc报文分为header和body两部分。我们这边只需要关注body部分。构造逻辑如下

    @Override
    protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
        RpcInvocation inv = (RpcInvocation) data;
        out.writeUTF(version);//dubbo版本号
        out.writeUTF(inv.getAttachment(Constants.PATH_KEY));//path 就是接口全限定名
        out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));// 接口版本号
        out.writeUTF(inv.getMethodName());//方法名
        out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes()));//方法参数类型
        Object[] args = inv.getArguments();
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                out.writeObject(encodeInvocationArgument(channel, inv, i));//方法参数
            }
        }
        out.writeObject(RpcUtils.getNecessaryAttachments(inv));//rpc上下文
    }

那么我们通过直接调用与泛化调用ByeService的bye方法在报文上有啥区别呢?

我一开始以为报文中的path是GenericeService,其实并没有,path就是我们调用的目标方法。

path来源???todo

而报文中的方法名,方法参数类型以及具体参数,还是按照GenericeService的$invoke方法入参传递的。

这么个二合一的报文,发送到提供者那边,它估计也会很懵逼,我应该怎么执行?

所以针对泛化调用报文还会把generic=true放在attchment中传递过去

具体逻辑在GenericImplFilter中。

GenericImplFilter中有很多其他逻辑,比如泛化调用使用的序列化协议,正常接口走泛化调用的模式,我们只需要设置attachment的那部分。

针对泛化调用,要进行2次序列化/反序列化。看下POJO的调用方式你就知道为啥了

((RpcInvocation) invocation).setAttachment(
    Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));

知道消费者端报文发生了什么变化,那么接下来就去看提供者端如何处理这个改造后的报文。

interfaceClass和interfaceName的区别

总结一下ReferenceConfig中interfaceClass和interfaceName的区别?(这道面试题好像不错)

interfaceClass用于指定生成代理的接口
interfaceName用于指定发送rpc报文中的path(告诉服务端我要调用那个服务)

提供者端

消费者泛化调用的rpc报文传递到提供者还不能直接使用,虽然path是对的,但是实际的方法名,参数类型,参数要从rpc报文的参数中提取出来。

GenericFilter就是用来做这件事情。

在提供者这边,针对泛化调用的逻辑全部封装到了GenericFilter,解耦的非常好。

GenericFilter逻辑分析

1. 是否是泛化调用判断

if (inv.getMethodName().equals(Constants.$INVOKE)
                &amp;&amp; inv.getArguments() != null
                &amp;&amp; inv.getArguments().length == 3
                &amp;&amp; !GenericService.class.isAssignableFrom(invoker.getInterface())){
    //...
}

注意第4个条件,一开始很疑惑,后来发现rpc报文中的path是目标接口的,这边invoker.getInterface()返回的肯定就是实际接口了

2. 方法参数提取

//从argument提取目标方法名 方法类型 方法参数
String name = ((String) inv.getArguments()[0]).trim();
String[] types = (String[]) inv.getArguments()[1];
Object[] args = (Object[]) inv.getArguments()[2];

3. 方法参数解析,进一步反序列化

//反射获取目标执行方法
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);
if (StringUtils.isBlank(generic)) {
    generic = RpcContext.getContext().getAttachment(Constants.GENERIC_KEY);
}
//一些反序列化
if (StringUtils.isEmpty(generic)
    || ProtocolUtils.isDefaultGenericSerialization(generic)) {
    args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
} 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) {
                throw new RpcException("Deserialize argument [" + (i + 1) + "] failed.", e);
            }
        } else {
            throw new RpcException(
                "Generic serialization [" +
                Constants.GENERIC_SERIALIZATION_NATIVE_JAVA +
                "] only support message type " +
                byte[].class +
                " and your message type is " +
                args[i].getClass());
        }
    }
} 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 {
            throw new RpcException(
                "Generic serialization [" +
                Constants.GENERIC_SERIALIZATION_BEAN +
                "] only support message type " +
                JavaBeanDescriptor.class.getName() +
                " and your message type is " +
                args[i].getClass().getName());
        }
    }
}

这边有个疑问,为什么这边还要再次反序列化一次,netty不是有decoder么??

嗯,你别忘了,针对一个POJO你传过来是一个Map,从Map转换为POJO需要这边进一步处理。

4. 调用目标服务

Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));

这边的invoker就是实际服务提供者的invoker,因为我们的path是正确的,invoker获取在DubboProtocl的requestHandler回调中

5. 异常处理

if (result.hasException()
    &amp;&amp; !(result.getException() instanceof GenericException)) {
    return new RpcResult(new GenericException(result.getException()));
}

这边需要注意一下!!针对接口的泛化调用,抛出的异常都会经过GenericException包装一下。

总结

从功能上来看,泛化调用提供了在没有接口依赖情况下进行的解决方案,丰富框架的使用场景。
从设计上来看,泛化调用的功能还是通过扩展的方式实现的,侵入性不强,值得学习借鉴。

以上就是dubbo泛化调用使用及原理示例解析的详细内容,更多关于dubbo泛化调用的资料请关注脚本之家其它相关文章!

相关文章

  • java开发RocketMQ生产者高可用示例详解

    java开发RocketMQ生产者高可用示例详解

    这篇文章主要为大家介绍了java开发RocketMQ生产者高可用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Mybatis resultMap标签继承、复用、嵌套方式

    Mybatis resultMap标签继承、复用、嵌套方式

    这篇文章主要介绍了Mybatis resultMap标签继承、复用、嵌套方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • springboot返回值转成JSONString的处理方式

    springboot返回值转成JSONString的处理方式

    这篇文章主要介绍了springboot返回值转成JSONString的处理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • spring Boot打包部署到远程服务器的tomcat中

    spring Boot打包部署到远程服务器的tomcat中

    这篇文章主要给大家介绍了关于spring Boot打包部署到远程服务器的tomcat中的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2017-12-12
  • SpringBoot配置的加载流程详细分析

    SpringBoot配置的加载流程详细分析

    了解内部原理是为了帮助我们做扩展,同时也是验证了一个人的学习能力,如果你想让自己的职业道路更上一层楼,这些底层的东西你是必须要会的,这篇文章主要介绍了SpringBoot配置的加载流程
    2023-01-01
  • iBatis习惯用的16条SQL语句

    iBatis习惯用的16条SQL语句

    iBatis 是apache 的一个开源项目,一个O/R Mapping 解决方案,iBatis 最大的特点就是小巧,上手很快.这篇文章主要介绍了iBatis习惯用的16条SQL语句的相关资料,需要的朋友可以参考下
    2016-10-10
  • 如何在Spring Boot中使用MQTT

    如何在Spring Boot中使用MQTT

    这篇文章主要介绍了如何在Spring Boot中使用MQTT,帮助大家更好的理解和学习使用Spring Boot,感兴趣的朋友可以了解下
    2021-04-04
  • Java中的Lombok使用详解

    Java中的Lombok使用详解

    这篇文章主要介绍了Java中的Lombok使用详解,Lombok是一个在Java开发过程中使用注解的方式,用于简化JavaBean的编写,避免冗余和样板式代码的插入,使类的编写更加简洁,需要的朋友可以参考下
    2023-08-08
  • Spring Boot 使用观察者模式实现实时库存管理的步骤

    Spring Boot 使用观察者模式实现实时库存管理的步骤

    在现代软件开发中,实时数据处理非常关键,本文提供了一个使用SpringBoot和观察者模式开发实时库存管理系统的详细教程,步骤包括创建项目、定义实体类、实现观察者模式、集成Spring框架、创建RESTful API端点和测试应用等,这将有助于开发者构建能够即时响应库存变化的系统
    2024-09-09
  • Springboot集成GraphicsMagick

    Springboot集成GraphicsMagick

    本文主要是教大家如何将GraphicsMagick命令行工具集成到Springboot项目中,便可以使用Java进行图片处理相关开发。
    2021-05-05

最新评论