美文网首页
dubbo泛化实现使用及原理解析

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

作者: 土豆肉丝盖浇饭 | 来源:发表于2020-08-31 16:55 被阅读0次

前言

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

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

使用

这边只介绍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。

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

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

巧妙,我只能这么说。

相关文章

网友评论

      本文标题:dubbo泛化实现使用及原理解析

      本文链接:https://www.haomeiwen.com/subject/lffpsktx.html