前言
与泛化引用类似,使用泛化实现你可以在没有接口依赖的情况下去实现一个服务。
这个功能感觉比泛化引用还冷门,不看官方文档的前提下,我压根不知道有这功能。
使用
这边只介绍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。
不管是泛化调用还是泛化实现,泛化这个行为,或者说从普通报文到泛化报文的修改这个行为,都是在消费者端进行的。
提供者端的操作主要是,找实现,调用实现,返回结果。
巧妙,我只能这么说。