dubbo泛化实现使用及原理解析

前言

与泛化引用类似,使用泛化实现你可以在没有接口依赖的情况下去实现一个服务。

这个功能感觉比泛化引用还冷门,不看官方文档的前提下,我压根不知道有这功能。

使用

这边只介绍spring中的使用方式

实现GenericService

public class GenericServiceImpl implements GenericService{

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

spring配置,对接口 com.scj.demo.dubbo.api.HelloService 进行泛化实现

    <bean id="genericService" class="com.scj.demo.dubbo.provider.service.impl.GenericServiceImpl" />

    <dubbo:service interface="com.scj.demo.dubbo.api.HelloService" ref="genericService" />
public interface HelloService {
    String helloWorld(String name);
}

客户端正常调用

helloService.helloWorld("123")

原理

提供者端

打标泛化实现

和泛化引用类似,泛化调用在export时候会根据ref是否为GenericService类型设置interfaceClass以及generic

if (ref instanceof GenericService) {
    interfaceClass = GenericService.class;
    if (StringUtils.isEmpty(generic)) {
        generic = Boolean.TRUE.toString();
    }
} else {
    try {
        interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                                       .getContextClassLoader());
    } catch (ClassNotFoundException e) {
        throw new IllegalStateException(e.getMessage(), e);
    }
    checkInterfaceAndMethods(interfaceClass, methods);
    checkRef();
    generic = Boolean.FALSE.toString();
}

和泛化引用一样

interfaceClass用于生成代理
interface是实际所泛化的接口,会带到provider url中去

除了interface带到provider url,还有一个关键的属性generic=true也会带到provider url

生成Invoker

interfaceClass用来生成代理,只不过这边是将ref生成Invoker的代理

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

注意,下面代码的type为GenericService.class,也就是getInterface()会返回GenericService.class

    public AbstractProxyInvoker(T proxy, Class<T> type, URL url) {
        if (proxy == null) {
            throw new IllegalArgumentException("proxy == null");
        }
        if (type == null) {
            throw new IllegalArgumentException("interface == null");
        }
        if (!type.isInstance(proxy)) {
            throw new IllegalArgumentException(proxy.getClass().getName() + " not implement interface " + type);
        }
        this.proxy = proxy;
        this.type = type;
        this.url = url;
    }

    @Override
    public Class<T> getInterface() {
        return type;
    }

而在GenericFilter中有个判断逻辑

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

通过 !GenericService.class.isAssignableFrom(invoker.getInterface()) ,我们可以知道,针对泛化实现,GenericFilter中的逻辑不生效。

为了能够调用服务端的GenericService,对于rpc报文的构成,确定了一下内容

methodName=$invoke
parameterTypes=String method, String[] parameterTypes, Object[] args
arguments=String method, String[] parameterTypes, Object[] args

在提供者端是找不到将普通接口调用的invocation转换成泛化调用invocation的逻辑了,这个逻辑必定在消费者端。

放入ExportMap

exporterMap用于根据报文的serviceName(也就是接口名)寻找提供者实现,它的key的生成逻辑如下

protected static String serviceKey(URL url) {
    int port = url.getParameter(Constants.BIND_PORT_KEY, url.getPort());
    return serviceKey(port, url.getPath(), url.getParameter(Constants.VERSION_KEY),
                      url.getParameter(Constants.GROUP_KEY));
}

仔细品的话,可以发现,针对泛化实现,url.getPath()为所泛化接口的名字,而不是com.alibaba.dubbo.rpc.service.GenericService

因此消费者端传过来的rpc报文中的serviceName应该为所泛化接口的名字

所以针对泛化实现,消费者端需要传过来的报文需要是以下内容

serviceName={实际接口全限定名}
methodName=$invoke
parameterTypes=String method, String[] parameterTypes, Object[] args
arguments=String method, String[] parameterTypes, Object[] args

消费者端

在消费者端,需要关注的逻辑是,它是如何修改rpc报文的

感知泛化实现

消费者端会对每一个提供者,也就是provider url会生成一个Invoker。

这边一个小细节点是,会把消费者的url和provider url进行合并,所以当provider为泛化实现时,invoker对应的url中会有generic=true

合并逻辑在RegistryDirectory#mergeUrl

private URL mergeUrl(URL providerUrl) {
        providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); // Merge the consumer side parameters
        //...
}

具体逻辑不细讲,大家需要知道,针对泛化实现,provider url中的generic=true会合并到消费者对应invoker的url中,然后就有了下面逻辑的触发。

现在dubbo存在一个bug,https://github.com/apache/dubbo/issues/6186,因此调用泛化实现会偶尔报失败

修改报文

之前在消费者端GenericImplFilter有的一段诡异的逻辑因此也能讲的通了。

if (ProtocolUtils.isGeneric(generic)
                && !Constants.$INVOKE.equals(invocation.getMethodName())
                && invocation instanceof RpcInvocation) {
    //...
}

虽然invoker的type是目标接口,但是它合并后的url带了generic=true的标,所以会进入这个逻辑分支。

而在这个逻辑分支里,会把普通调用的invocation转化成泛化调用所需要的invocation。

其实就是封装成GenericService所需要的参数。

RpcInvocation invocation2 = (RpcInvocation) invocation;
String methodName = invocation2.getMethodName();
Class<?>[] parameterTypes = invocation2.getParameterTypes();
Object[] arguments = invocation2.getArguments();

String[] types = new String[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
    types[i] = ReflectUtils.getName(parameterTypes[i]);
}

//二次序列化逻辑 忽略
//...

//转换参数,符合GenericService#$invoke所需要的
invocation2.setMethodName(Constants.$INVOKE);
invocation2.setParameterTypes(GENERIC_PARAMETER_TYPES);
invocation2.setArguments(new Object[]{methodName, types, args});
Result result = invoker.invoke(invocation2);

因为实际调用到的是泛化实现,所以针对结果返回以及异常处理也要进行特殊处理

针对正常返回的再次反序列化

if (!result.hasException()) {
    Object value = result.getValue();
    try {
        Method method = invoker.getInterface().getMethod(methodName, parameterTypes);
        if (ProtocolUtils.isBeanGenericSerialization(generic)) {
            if (value == null) {
                return new RpcResult(value);
            } else if (value instanceof JavaBeanDescriptor) {
                return new RpcResult(JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) value));
            } else {
                throw new RpcException(
                    "The type of result value is " +
                    value.getClass().getName() +
                    " other than " +
                    JavaBeanDescriptor.class.getName() +
                    ", and the result is " +
                    value);
            }
        } else {
            return new RpcResult(PojoUtils.realize(value, method.getReturnType(), method.getGenericReturnType()));
        }
    } catch (NoSuchMethodException e) {
        throw new RpcException(e.getMessage(), e);
    }
}

针对异常的再次反序列化

else if (result.getException() instanceof GenericException) {
    GenericException exception = (GenericException) result.getException();
    try {
        String className = exception.getExceptionClass();
        Class<?> clazz = ReflectUtils.forName(className);
        Throwable targetException = null;
        Throwable lastException = null;
        try {
            targetException = (Throwable) clazz.newInstance();
        } catch (Throwable e) {
            lastException = e;
            for (Constructor<?> constructor : clazz.getConstructors()) {
                try {
                    targetException = (Throwable) constructor.newInstance(new Object[constructor.getParameterTypes().length]);
                    break;
                } catch (Throwable e1) {
                    lastException = e1;
                }
            }
        }
        if (targetException != null) {
            try {
                Field field = Throwable.class.getDeclaredField("detailMessage");
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                field.set(targetException, exception.getExceptionMessage());
            } catch (Throwable e) {
                logger.warn(e.getMessage(), e);
            }
            result = new RpcResult(targetException);
        } else if (lastException != null) {
            throw lastException;
        }
    } catch (Throwable e) {
        throw new RpcException("Can not deserialize exception " + exception.getExceptionClass() + ", message: " + exception.getExceptionMessage(), e);
    }
}

总结

泛化调用和泛化实现这两个功能在底层设计上也做到了很大的复用,你需要知道他们两个的存在,看能看懂GenericFilter和GenericImplFilter。

不管是泛化调用还是泛化实现,泛化这个行为,或者说从普通报文到泛化报文的修改这个行为,都是在消费者端进行的。

提供者端的操作主要是,找实现,调用实现,返回结果。

巧妙,我只能这么说。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342