Apache Dubbo系列:泛化调用

8 篇文章 1 订阅
7 篇文章 0 订阅

上一章,我们讲到了Dubbo的线程池策略,本章我们一起探讨,Dubbo如何实现泛化调用的。主要内容包括:

1、什么是泛化调用

2、泛化调用的三种方式

3、如何使用

4、源码分析

 

推荐阅读:

Apache Dubbo系列:集群容错整体架构

Apache Dubbo系列:Dubbo线程模型

Apache Dubbo系列:Dubbo的线程池策略

什么是泛化调用

我们基于Dubbo API搭建Dubbo服务时,服务消费端需要依赖于一个SDK二方包,其中存放这服务提供端提供的所有接口。

泛化调用主要用于,服务提供端没有API接口以及入参出参,参数和返回没有对应的POJO时,所有POJO参数均用Map表示,通常用于框架集成。使用泛化调用时,服务消费端不再需要依赖于二方的SDK。

 

泛化调用的三种方式

在Dubbo中,有三种泛化调用,分别是:generic=true,generic=bean和generic=nativejava,三种调用方式,使用的序列化和反序列化的方式不同。

 

如何使用

下面,用generic=true的方式,演示一下泛化调用怎么用。

Spring的XML配置:

<dubbo:reference id="userService" interface="com.ctx.userService" generic="true" />

 

在代码里,可以这样获取userService并泛化调用


 // 引用远程服务 
// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>(); 
// 弱类型接口名
reference.setInterface("com.ctx.XxxService");  
reference.setVersion("1.0.0");
// 声明为泛化接口,方式为true
reference.setGeneric(true);

// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用  
GenericService genericService = reference.get(); 
 
// 基本类型以及Date,List,Map等不需要转换,直接调用 
Object result = genericService.$invoke("sayHello", new String[] {"java.lang.String"}, new Object[] {"world"}); 
 
// 用Map表示POJO参数,如果返回值为POJO也将自动转成Map 
Map<String, Object> person = new HashMap<String, Object>(); 
person.put("name", "liuli"); 
person.put("password", "123"); 
// 如果返回POJO将自动转成Map 
Object result = genericService.$invoke("findPerson", new String[]
{"com.ctx.Person"}, new Object[]{person});

注意:如果泛化调用的入参是POJO类型,比如:

public class Person {
    private String name;
    private String password;
    // 省略getset方法
}
Person person = new Person(); 
person.setName("liuli"); 

此时,入参是需要将person转化为Map,map中有一个固定的key是class,value是POJO的包名+类名,这是为了在服务提供端进行反射使用。


Map<String, Object> map = new HashMap<String, Object>(); 
// 注意:如果参数类型是接口,或者List等丢失泛型,可通过class属性指定类型。
map.put("class", "com.ctx.Person"); 
map.put("name", "liuli"); 
map.put("password", "123");

可以通过实现GenericService接口处理所有泛化请求:


public class GenericServiceImpl implements GenericService {
    @Override
    public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
        if (method.equals("hi")) {
            return "hi, " + args[0];
        } else if (method.equals("hello")) {
            return "hello, " + args[0];
        }

        return "ni hao liuli";
    }
}

源码分析

下面的代码,主要是:服务消费端如何使用GenericImplFilter拦截泛化调用,以及服务提供端如何使用GenericFilter拦截请求后并把泛化参数序列化然后请求给具体服务。

 

我们先看服务消费端org.apache.dubbo.rpc.filter.GenericImplFilter是如何拦截泛化调用的。


@Activate(group = Constants.CONSUMER, value = Constants.GENERIC_KEY, order = 20000)
public class GenericImplFilter implements Filter {
  @Override
  public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    String generic = invoker.getUrl().getParameter(Constants.GENERIC_KEY);
    // ...  省略非核心代码
    // 判断是否为泛化调用
    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(generic, byte[].class.getName(), arg.getClass().getName());
                }
            }
        } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
            // 如果泛化方式为bean
            for (Object arg : args) {
                if (!(arg instanceof JavaBeanDescriptor)) {
                    error(generic, JavaBeanDescriptor.class.getName(), arg.getClass().getName());
                }
            }
         }
          // 设置attachment,以便于服务端调用
          ((RpcInvocation) invocation).setAttachment(
                Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));//generic 
      }
      // 发起远程调用
      return invoker.invoke(invocation);
  }
}

GenericImplFilter实现接口Filter(关于Dubbo中的Filter,不是本章节内容,此处不再赘述),然后执行invoke方法,invoke方法主要做了下面4件事情:

1、参数校验,检查这个调用是否是泛化调用。

2、获取泛化参数。

3、判断泛化调用方式:遍历每个参数,然后依次判断参数的泛化方式是nativejava方式还是bean方式。

4、发起远程调用。

 

下面,我们一起来看看,服务提供端是如何通过GenericFilter拦截泛化请求的。

org.apache.dubbo.rpc.filter.GenericFilter


@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
                && !GenericService.class.isAssignableFrom(invoker.getInterface())) {
            // 获取参数名称、参数类型、参数值
            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];
                }
                // 获取泛化引用使用的泛化类型,true or bean or nativejava
                String generic = inv.getAttachment(Constants.GENERIC_KEY);
                if (StringUtils.isBlank(generic)) {
                    generic = RpcContext.getContext().getAttachment(Constants.GENERIC_KEY);
                }
                // 如果generic=true,则使用true方式对入参进行反序列化
                if (StringUtils.isEmpty(generic)
                        || ProtocolUtils.isDefaultGenericSerialization(generic)) {
                    args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
                    // 如果generic=nativajava,则使用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) {
                                throw new RpcException("Deserialize argument [" + (i + 1) + "] failed.", e);
                            }
                        } else {
                            throw new RpcException("...");
                        }
                    }
                    // 如果generic=bean,则使用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 {
                            throw new RpcException("..." );
                        }
                    }
                }
                // 将本次请求传递到FilterChain的下一个Filter中,并返回结果result
                Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
                if (result.hasException()
                        && !(result.getException() instanceof GenericException)) {
                    return new RpcResult(new GenericException(result.getException()));
                }
                // 如果generic=nativejava,则使用JAVA序列化方式对result序列化
                if (ProtocolUtils.isJavaGenericSerialization(generic)) {
                    try {
                        UnsafeByteArrayOutputStream os = new UnsafeByteArrayOutputStream(512);
                        ExtensionLoader.getExtensionLoader(Serialization.class)
                                .getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
                                .serialize(null, os).writeObject(result.getValue());
                        return new RpcResult(os.toByteArray());
                    } catch (IOException e) {
                        throw new RpcException("Serialize result failed.", e);
                    }
                    // 如果generic=bean,则使用bean序列化方式对result序列化
                } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                    return new RpcResult(JavaBeanSerializeUtil.serialize(result.getValue(), JavaBeanAccessor.METHOD));
                } else {
                    // 如果generic=true,则使用bean序列化方式对result序列化
                    return new RpcResult(PojoUtils.generalize(result.getValue()));
                }
            } catch (NoSuchMethodException e) {
                throw new RpcException(e.getMessage(), e);
            } catch (ClassNotFoundException e) {
                throw new RpcException(e.getMessage(), e);
            }
        }
        // 如果不是泛化调用,直接把请求传给FilterChain的下一个Filter
        return invoker.invoke(inv);
    }
}

上述代码,就是Dubbo服务提供端如果拦截泛化请求,并进行处理的,大体流程如下:

1、参数校验,判断此次请求是不是泛化请求。

2、获取参数名称、参数类型、参数值。

3、使用反射获取调用的方法,和使用的泛化方式true or bean or nativejava。

4、根据泛化方式,反序列化泛化参数。

5、将本次请求,包括调用的方法、参数和上下文信息传递给FilterChain的下一个Filter中,并返回Result结果。

6、根据泛化方式,序列化Result结果返回给服务消费端。

 

上述,便是Dubbo实现泛化调用的全过程。

好了,今天的探讨就到这里,如果有不合理的地方请指出,谢谢大家。

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值