dubbo+zookeeper示例代码_深刻体会dubbo泛化调用类型丢失问题

631adb7fa3ad39e8e3cee5d609682c65.png

今天小编工作上遇到一个关于dubbo泛化调用比较有代表性意义的坑,写出来给小伙伴们分享一下,希望大家能从我的经验中获得一些成长,未来遇到类似的场景也能少走一些弯路。

我们来看下dubbo框架中对泛化调用的接口定义:

public interface GenericService {

    /**
     * 泛化调用
     * 
     * @param method 方法名,如:findPerson,如果有重载方法,需带上参数列表,如:findPerson(java.lang.String)
     * @param parameterTypes 参数类型
     * @param args 参数列表
     * @return 返回值
     * @throws Throwable 方法抛出的异常
     */
    Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;

}

可以看到泛化调用,需要入参方法名,参数类型列表和参数值列表。

再来看段dubbo泛化调用的示例代码:

// 注册中心
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("127.0.0.1:2181");
        registry.setProtocol("zookeeper");
        // 应用信息
        ApplicationConfig application = new ApplicationConfig();
        application.setName("GenericInvokeTest");
        application.setRegistry(registry);

        // 泛化接口
        ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
        // 真实接口
        reference.setInterface("com.test.pdf.service.PdfConvertService");
        // dubbo注册的版本号
        reference.setVersion("1.0.0");
        // 超时时间
        reference.setTimeout(10000);
        // 声明为泛化接口
        reference.setGeneric(true);
        reference.setApplication(application);

        // 添加缓存策略
        ReferenceConfigCache cache = ReferenceConfigCache.getCache();
        GenericService genericService = cache.get(reference);

        // 设置接口所需参数的类型
        String[] parameterTypes = new String[]{"java.lang.String"};
        // 传递 必传的参数
        String request = "https://www.test.com/test.doc";

        Object[] args0 = new Object[]{request};
        //调用泛化接口
        Object object = genericService.$invoke("word2Pdf", parameterTypes, args0);
        System.out.println(JSON.toJSONString(object));

一般情况下,我们通过以上代码可以很好的实现dubbo远程泛化调用,一切看起来是那么的完美(当然了,上述示例仅使用了java原生数据类型,如果是自定义类型也是一样的,这里就不贴代码了,否则有凑字数的嫌疑)。

来看一个远端服务端真实接口定义,正常情况下是这样的:

/**
     * 校验url访问权限
     *
     * @param loginUser 用户信息
     * @param url 访问url
     * @param type 访问类型
     * @return
     */
Response<PrivilegeCheck> isResourcePermit(LoginUser loginUser, String url, String type);

小编特意在这里的将入参增加了一个自定义数据类型LoginUser,其定义如下:

public interface LoginUser {

    Long getMainId();
    Integer getStatus();
    String getAccount();
}

小伙伴们可能发现了,这个isResourcePermit方法第一个入参怎么是个interface呢?其实正常情况入参是接口也没有关系,因为正常Dubbo invoke调用时是有传入真实参数类型的。已dubbo默认的Javassist代理来看:

public class JavassistProxyFactory extends AbstractProxyFactory {

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper类不能正确处理带$的类名
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, 
                                      Class<?>[] parameterTypes, 
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

}

这里最终invokeMethod的时候传入的arguments就是真实的类信息。

回到泛化调用的问题上来,先上证据:

{
    "methodName": "isResourcePermit",
    "paramTypes": [
        "com.test.user.dto.LoginUser",
        "java.lang.String",
        "java.lang.String"
    ],
    "paramValues": [
        {
            "id": 21232,
            "mainId": 21232,
            "status": 1,
            "account": "dcgg19899260300"
        },
        "/ad/exchange/msg/operators/event/messages",
        "get"
    ]
}

这里我们构造了paramTypes和paramValues来匹配isResourcePermit接口。

请注意,这里问题就来了,paramTypes的第一个入参其实是个接口,但是paramValues入参一定是个具体的实现类对象。这里我们构造了LoginUser的实现类MyLoginUser,其定义如下:

@Data
public class MyLoginUser implements LoginUser {

      private Long id;
    private Long mainId;
    private Integer status;
    private String account;
}

仔细看MyLoginUser比LoginUser多了1个Long类型的id字段。我们来泛化调用的效果:

da4216152b1bc154f86b694bcd402bf2.png

这里会报“java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long”。这就是所谓的dubbo泛化调用真实类型丢失问题。

仔细想一想,也确实是这样,我们泛化调用的只告诉了dubbo参数接口类型,反序列化时dubbo怎么会知道应该反序列化成什么对象呢!

明白了这个道理,我们不禁会想,难道dubbo框架没有考虑到这个问题吗?事实上,dubbo有考虑到这个问题,解决的思路也很简单,那就是在paramValues中指定实现类的类型。

[
        {
            "id": 21232,
            "mainId": 21232,
            "status": 1,
            "account": "dcgg19899260300",
            "class": "com.test.user.dto.MyLoginUser" 
        },
        "/ad/exchange/msg/operators/event/messages",
        "get"
    ]

使用以上paramValues我们就获取到了想要的结果。

{
  "success":true,
  "result":{
    "accessType":0,
    "isPermit":true,
  },
  "message":null,
}

附录:dubbo协议头信息

fceeec6072cd882064ada6fd6de6f826.png

dubbo协议头是16字节的定长数据,16*8=128位,地址范围0~127。

2字节magic字符串0xdabb,0-7高位,8-15低位
1字节的消息标志位,16-20序列id,21 event,22 two way,23请求或响应标识
1字节状态,当消息类型为响应时,设置响应状态,24-31位
8字节,消息ID,long类型,32-95位
4字节,消息长度,96-127位

源代码参考:com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec

公众号链接:

深刻体会dubbo泛化调用类型丢失问题​mp.weixin.qq.com
64e14fd80c0c76cb47a37c73e3cf4b1e.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值