泛化指的是服务消费者不需要与提供者一样,编写一份接口类。而是通过$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>
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);
}
}
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
,里面分别存放了name
、id
、class
三个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);
}
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());
}
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());
}
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}
2
3
4
5
6
7
8
9
10
在泛化调用中,我们传入的入参类型是Object[]
,理论上我们可以传入任意类型的值。但是使用泛化调用不论是测试还是其他使用目的,都是在当前项目中不获取provider
侧的interface
作为依赖前提下进行。所以我们将入参值转换为某一固定类型即可,比如说Map
类型就非常方便。如果是Map
类型的话,那么就可以直接使用alibaba
的JSONObject
。