Dubbo-服务引用

为君一笑 提交于 2020-01-21 03:10:55

前言

使用 Dubbo,只需要服务消费者引用服务提供者的接口jar包(泛化调用可以不用服务提供者的接口jar包),就可以像调用本地方法一样,调用远程的服务,但在消费者这边,并没有服务实现的代码,那是怎么实现调用的呢?本文就分析一下服务引用的原理。

服务引用流程

服务引用实体关系

解析配置文件

根据配置文件 <dubbo:reference init=“true”> init的配置走不同的服务引用时机,最终都走到同样的路口:ReferenceBean#getObject。

服务引用的时机

服务引用使用 < dubbo:reference> 标签,按照是否配置 init=“true” 会有两种服务引用的时机:

  • 配置了 <dubbo:reference init=“true”> 会在解析到这个标签的时候就进行服务引用,也就是饿汉式的方式。
  • 配置了 <dubbo:reference > 并不会在解析这个标签的时候进行服务引用,而是在获取这个 bean 的时候进行服务引用,也就是懒汉式的方式。

饿汉式的服务引用原理是 ReferenceBean 标签实体类实现了 InitializingBean 接口,会在初始化 Bean 的时候就执行 afterPropertiesSet 方法,并且 afterPropertiesSet 方法会判断 init =true 是否成立,成立的话就调用 getObject 方法,getObject 方法是服务引用的入口

而懒汉式的服务引用原理是 ReferenceBean 实现了 FactoryBean 接口,这个接口会在获取 Bean 的时候调用 getObject 方法。对比这两种服务引用的时机,最后都是调用了 FactoryBean 接口的 getObject 方法,唯一的不同就是在初始化 Bean 的时候执行 afterPropertiesSet 方法中会判断 init 是否为 true。

注册订阅

根据协议不同会走不同服务引用策略,包括 injvm、直连、注册中心。对于注册中心的服务引用,会根据 URL 中参数 protocol 走具体的注册中心(ZookeeperRegistry),然后会完成注册consumer节点,订阅provider、router、configurators节点。

订阅之后创建invoker,启动Netty连接远程服务,invoker放入Map

订阅之后对应节点的信息会通知到消费者,在 RegistryDirectory 根据 URL 引用服务,引用服务就是创建 netty 客户端并且连接远程服务提供者(lazy=false),并创建 invoker 对象(DubboInvoker),这里的 invoker 对象会持有远程服务的 NettyClient,最后把创建的 DubboInvoker 对象放入 Map 中,在后面调用的时候根据 URL/方法名从 Map 中获取对应的 DubboInvoker 对象从而发起调用。

// RegistryDirectory.toInvokers 调用具体协议发起引用
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);

// DubboProtocol.refer 创建DubboInvoker对象,并且getClients()方法中去连接远程服务
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);

// RegistryDirectory.refreshInvoker 把创建的DubboInvoker对象放入Map中
this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
this.urlInvokerMap = newUrlInvokerMap;

Cluster聚合Directory

上面订阅之后创建 DubboInvoker 等是订阅之后通知而来的,是异步的,所以在消费者服务启动的时候并不一定会有 DubboInvoker ,所以这里会用 Cluster聚合Directory ,Cluster 是我们在发起调用的核心,这里聚合的 Directory,里面有 Map ,订阅之后会把 DubboInvoker 放入 Map,所以我们在调用的时候,通过 Cluster 发起调用,而 invoker 就是从 Directory 中的 Map 中查找对应的 DubboInvoker。

// RegistryProtocol.doRefer
Invoker invoker = cluster.join(directory);

这里返回的 invoker 是 MockClusterInvoker,这是因为 cluster 的扩展实现有一个包装类 MockClusterWrapper(通过 SPI-API 的方式),所以对于 cluster 的默认实现 FailoverCluster 外面还会包装一层 MockCluster 。

此外需要注意到,传入参数是 directory,并不是 DubboInvoker,

代理工厂生成代理类Proxy0

最后我们用代理工厂生成 MockClusterInvoker 的代理类(Proxy0),在使用时demoService.sayHello()就转换成了Proxy0.sayHello()

// ReferenceConfig.createProxy
private T createProxy(Map<String, String> map) {
 ...
 return (T) proxyFactory.getProxy(invoker);// 使用代理工厂创建代理类
}

// JavassistProxyFactory.getProxy
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
  return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

我们看下JavassistProxyFactory.getProxy 是怎么创建代理类的。

  1. Proxy.getProxy(interfaces) 。首先根据接口信息,动态拼接一个类的字符串,然后用 Javassist 编译并加载这个类,这个类就是代理类。我们看个代理生成的类的例子。注意看这个类,其实现了我们要代理的接口,并且构造方法中会传入 InvocationHandler 对象,然后在执行 sayHello 方法的时候,会调用这个 handler.invoke 方法。

    public class proxy0 implements org.apache.dubbo.demo.DemoService {
        public static java.lang.reflect.Method[] methods;
    
        private java.lang.reflect.InvocationHandler handler;
    
        public proxy0() {
        }
    
        public proxy0(java.lang.reflect.InvocationHandler arg0) {
            handler = $1;
        }
    
        public java.lang.String sayHello(java.lang.String arg0) {
            Object[] args = new Object[1];
            args[0] = ($w) $1;
           // 执行InvocationHandler的invoke 方法
            Object ret = handler.invoke(this, methods[0], args);
            return (java.lang.String) ret;
        }
    }
    
  2. new InvokerInvocationHandler(invoker)。这个是传入到代理类 Proxy0 的参数,InvokerInvocationHandler 实现 InvocationHandler 接口,而 InvocationHandler 这个是 JDK 动态代理机制的核心,当我们调用 Proxy0.sayHello() 方法的时候会调用到这个 InvokerInvocationHandler.invoker() 方法,之后会调用到 invoker.invoke() 方法,这样就完成了 Proxy0 —> InvokerInvocationHandler —> invoker 的代理过程。

这里别把 Javassist 动态代理和 JDK 动态代理搞混了,Dubbo 服务引用中用 Javassist 代理工厂生成了接口的代理类 Proxy0,当服务调用发生的时候,是用的 JDK 动态代理的机制去调用代理类的,InvocationHandler 类继承 InvocationHandler 类。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!