spring版本 5.x
jdk版本 1.8
dubbo版本 2.6.0
概述
本文主要是通过阅读源代码的方式理解下 dubbo consumer 是如何调用远程服务的。
关键字
动态代理, NIO
通过第(四)篇文章我们知道@Reference 注解是通过动态代理实现的。如果对动态代理不是了解可以阅读下这篇博客:JAVA动态代理实现与原理详细分析。 这里不做动态代理的详细分析,只是简单概括下动态代理:
- 在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
- 通过 Proxy 类生成的代理对象与代理对象会实现同一个接口
- Proxy 类生成的代理对象会持有一个InvocationHandler
- 被代理的对象的所有方法都会通过InvocationHandler的 invoke()方法执行,所以想在代理阶段做任何事,只要修改 invoke方法就行了。
- Proxy 类生成的代理对象文件(类似常规的.class 文件)是存在内存中的
通过《@Reference 注解注入原理》这篇博客,我们已经知道被@Reference 修饰的域实际上是一个代理类。其实在 Debug 代码的时候,我们也会看到:
@Reference 注解修饰的域是通过InvokerInvocationHandler实现的动态代理。被代理类的任何方法都会通过通过InvokerInvocationHandler的 invoker 方法执行
/**
proxy 参数暂未用到,应该是代理类,与被代理类实现了同一接口
method 即被代理的方法
args 为被代理的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
//这个 invoker是在解析@Reference 注解使创建的,通过Protocol.refer()。最后的远程调用也发生在这行代码里的 invoke方法。其中的 recreate()为重新构建返回结果。
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
继续往下跟踪,最后发现下一步的关键 invoke 发生在ProtocolFilterWrapper 中(buildInvokerChain方法),通过 dubbo的 filter 来继续 invoke(这也符合 dubbo 官方架构图中的描述:通过 cluster invoker调用 protocol filter, 再通过 protocol filter 调用 protocol invoker), 代码如下:
需要注意的是buildInvokerChain方法并不发生在远程调用阶段, 顾名思义这个方法其实是个构建调用链的方法,执行时机发生在构建 Invoker阶段,也就是Protocol的refer()方法,下面具体分析下代码:
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
//这个 filter 列表加载的是通过 @Activate 注解修饰的group="consumer" 的dubbo filter,
//通过@Activate注解会获得三个 filter,
//分别是 ConsumerContextFilter 、FutureFilter、MonitorFilter,
//且这三个 filter 有序的,排序规则是按照@Activate注解的 order 字段。小的在前面,默认为零。 ConsumerContextFilter的 order 是-10000, 所以它排在这个列表的前面
//这个 dubbo filter 列表会被依次调用执行
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (filters.size() > 0) {
//此处为倒序, 因此 filter列表的头节点ConsumerContextFilter将赋值给 last 参数作为调用链的头节点返回。
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
//这里生成一个内部类,这个内部类有三个属性,也就这个作用域内的三个 final 类型的变量:
//invoker
//filter
//next (也是一个 Invoker)
last = new Invoker<T>() {
public Class<T> getInterface() {
return invoker.getInterface();
}
public URL getUrl() {
return invoker.getUrl();
}
public boolean isAvailable() {
return invoker.isAvailable();
}
public Result invoke(Invocation invocation) throws RpcException {
//filter.invoke() 实际上执行的 next 的 invoke方法.
//而 next 节点的 invoke 方法又是执行的 next 节点持有的 filter的invoke(),如此往复循环,完成链式调用
return filter.invoke(next, invocation);
}
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
总结下invoke调用链的原理:每个节点是一个 Invoker对象,它会有三个成员变量,分别是 invoker、filter、next(Invoker类型), 每个Invoker对象的 invoke 方法里执行的是其成员变量filter的 invoke方法,而filter的 invoke方法执行的 next的 invoke 方法。 即: invoker.invoke() --> filter.invoke() --> next.invoke() --> filter.invoke()…
再调用过程中,在ListenerInvokerWrapper.invoke()打个断点,看下调用堆栈, 同样印证了我们上面所说的。
接下来分析如何进行的远程交互
从上图得知,MonitorFilter所调用的 next invoker 是 DubboInvoker, 其本身没有 invoke 方法,调用的父类AbstractInvoker中的 invoke 方法, 而AbstractInvoker 中 invoke方法调用的是派生类的 doInvoke方法。最后我们回到 DubboInvoker类中的doInvoke()
protected Result doInvoke(final Invocation invocation) throws Throwable {
//忽略。。。
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
if (isOneway) {
//忽略。。。
} else if (isAsync) {
//忽略。。。
} else {
RpcContext.getContext().setFuture(null);
//最后通过ExchangeClient执行远程调用
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
// 忽略。。
}
}
一路跟踪下去,最后发现发生在HeaderExchangeChannel的request方法中,如图:
最后发送请求的是dubbo 的 NettyClient,至此,我们终于印着了dubbo 是通过 NIO 进行远程交互的原理。至于具体如何进行远程通信,我们就得去阅读 netty 源代码了,本文就不再分析了。 本文还有很多细节没有分析到,如有错误欢迎读者指正。