泛化指的是服务消费者不需要与提供者一样,编写一份接口类。而是通过$invoke接口直接回调注册的服务。 我们目前大多数系统都是在服务消费者和服务提供者两端同路径下有同样的接口,只不过在服务提供者端会有该接口的具体实现,服务消费者有一个没有任何具体实现的接口,是因为在设计RPC之初,设计者的最高理念就是面向接口编程。

# 一、代码案例

泛化的调用主要区别体现在消费端,所以提供者的配置保持一致,我们下面只列举消费者的配置和接口调用代码,因为消费者没有接口,所以直接编写消费者端的Spring配置文件spring-dubbo-consumer-generic.xml

<?xml version="1.1" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
       http://www.springframework.org/schema/beans/spring-beans.xsd  
       http://code.alibabatech.com/schema/dubbo  
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
       
    <dubbo:application owner="zzx" name="itblogcn-consumer" />
    <!--zookeeper注册中心 -->
    <dubbo:registry protocol="zookeeper" address="192.168.10.11:2181"/> 
    <dubbo:reference id="itBlogCnService" interface="org.bazinga.service.ItBlogCnService" generic="true"/> 
    
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

配置文件中有两处不同,第一个是interface,其实该接口在消费者端并不存在,这是与往常写的不一样的地方,第二个地方需要注意的地方就是generic="true"这样的标签,表示该接口支持泛型调用。

接下来我们进行泛型调用DubboConsumerGenericService.java

import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.alibaba.dubbo.rpc.service.GenericService;
 
public class DubboConsumerGenericService {
    
    public static void main(String[] args) {

    //Spring泛化调用
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(            "spring-dubbo-consumer-generic.xml");
      context.start();
      
      GenericService itBlogCnService = (GenericService) context.getBean("itBlogCnService");
      Object result = itBlogCnService.$invoke("helloService", new String[] { "java.lang.String" }, new Object[] { "hello" });
      // {name=charles, id=1, class=com.alibaba.dubbo.samples.generic.api.User}
      System.out.println(result);
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Spring的上下文中读到itBlogCnService之后把它强转为GenericService的类,然后调用GenericService$invoke的方法,该方法有三个参数,第一个参数是你调用远程接口的具体方法名,第二个参数是helloService这个方法的入参的类型,最后一个参数是值。

Dubbo框架会自动将POJO的返回值转换成Map。可以看到,返回值user是一个HashMap,里面分别存放了nameidclass三个k/v

# 二、源码分析

Dubbo泛化基于Filter实现,首先客户端会通过GenericImplFilter对泛化调用相关参数重新构建和处理。generic=true表示使用默认序列化方式。

【1】GenericImplFilter中会进入第一个if判断中:组装invocation,并通过PojoUtils工具类进行出入参数正反序列化。

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String generic = invoker.getUrl().getParameter("generic");
        if (this.isCallingGenericImpl(generic, invocation)) {
            RpcInvocation invocation2 = new RpcInvocation(invocation);
            invocation2.put("GENERIC_IMPL", true);
            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]);
            }

            Object[] args;
            if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                args = new Object[arguments.length];

                for(int i = 0; i < arguments.length; ++i) {
                    args[i] = JavaBeanSerializeUtil.serialize(arguments[i], JavaBeanAccessor.METHOD);
                }
            } else {
                args = PojoUtils.generalize(arguments);
            }

            if (RpcUtils.isReturnTypeFuture(invocation)) {
                invocation2.setMethodName("$invokeAsync");
            } else {
                invocation2.setMethodName("$invoke");
            }

            invocation2.setParameterTypes(GENERIC_PARAMETER_TYPES);
            invocation2.setParameterTypesDesc("Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;");
            invocation2.setArguments(new Object[]{methodName, types, args});
            return invoker.invoke(invocation2);
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

然后会调用服务端的对应的接口和方法,会被GenericFilter拦截。

public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
    if ((inv.getMethodName().equals("$invoke") || inv.getMethodName().equals("$invokeAsync")) && inv.getArguments() != null && inv.getArguments().length == 3 && !GenericService.class.isAssignableFrom(invoker.getInterface())) {
        String name = ((String)inv.getArguments()[0]).trim();
        String[] types = (String[])((String[])inv.getArguments()[1]);
        Object[] args = (Object[])((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];
            }

            if (types == null) {
                types = new String[params.length];
            }

            if (args.length != types.length) {
                throw new RpcException("GenericFilter#invoke args.length != types.length, please check your params");
            } else {
                String generic = inv.getAttachment("generic");
                if (StringUtils.isBlank(generic)) {
                    generic = this.getGenericValueFromRpcContext();
                }

                if (!StringUtils.isEmpty(generic) && !ProtocolUtils.isDefaultGenericSerialization(generic) && !ProtocolUtils.isGenericReturnRawResult(generic)) {
                    if (ProtocolUtils.isGsonGenericSerialization(generic)) {
                      // 默认泛化调用序列化方式
                        args = this.getGsonGenericArgs(args, method.getGenericParameterTypes());
                    } 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

org.apache.dubbo.common.utils.PojoUtils看一下这个工具类中realize方法的实现,注意传入的参数,第一个是入参值数组,第二个是入参类型数组,第三个参数为反射获取的method对象,获取该method的入参类型Type实例,可以通过getTypeName()方法获取对应的包含泛型类入参的类全限定名。例如有一个方法的的入参是List<String>,那么其入参类型全限定名展示为java.util.List<java.lang.String>

public static Object[] realize(Object[] objs, Class<?>[] types, Type[] gtypes) {
    if (objs.length == types.length && objs.length == gtypes.length) {
        Object[] dests = new Object[objs.length];

        for(int i = 0; i < objs.length; ++i) {
            dests[i] = realize(objs[i], types[i], gtypes[i]);
        }

        return dests;
    } else {
        throw new IllegalArgumentException("args.length != types.length");
    }
}

public static Object realize(Object pojo, Class<?> type, Type genericType) {
    return realize0(pojo, type, genericType, new IdentityHashMap());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

realize0方法中有根据第一个参数的类型判断,然后进行参数值的数据解析:

private static Object realize1(Object pojo, Class<?> type, Type genericType, final Map<String, Type> mapParent, final Map<Object, Object> history) {
    if (pojo == null) {
        return null;
    } else if (type != null && type.isEnum() && pojo.getClass() == String.class) {// 如果是String类型入参do something}
    //....
    } else if (pojo.getClass().isArray()) {//如果是数据类型入参 do something}
    //....
    } else if (pojo instanceof Collection<?>) {//如果是集合类型 do something}
    //....
    } else if (pojo instanceof Map<?, ?> && type != null) {//如果是Map类型 do something}
1
2
3
4
5
6
7
8
9
10

在泛化调用中,我们传入的入参类型是Object[],理论上我们可以传入任意类型的值。但是使用泛化调用不论是测试还是其他使用目的,都是在当前项目中不获取provider侧的interface作为依赖前提下进行。所以我们将入参值转换为某一固定类型即可,比如说Map类型就非常方便。如果是Map类型的话,那么就可以直接使用alibabaJSONObject

(adsbygoogle = window.adsbygoogle || []).push({});