Dubbo进阶(八)- @Reference或ReferenceConfig.get代理对象如何产生(一):SPI模式中 Wrapper和 SPI 类组装逻辑

本文以围绕以下问题探讨:

  1. Wrapper 和 SPI 类组装逻辑先后顺序 是怎样的呢?

以上问题看完文章后相信大家就可以清楚,若有疑问,关注博主公众号:六点A君,回复标题获取最新答案><

当使用Dubbo时候,使用@Reference 或者 ReferenceConfig.get 时候获取的一个目标接口,那么进行调用时候,接口并不是直接调用到了接口的实现(Impl)类。

    public static void main(String[] args) {
        ReferenceConfig<HelloService> reference = new ReferenceConfig<>();
        reference.setApplication(new ApplicationConfig("dubbo-consumer"));
        reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
        reference.setInterface(HelloService.class);
        HelloService service = reference.get();
        String message = service.hello("dubbo I am anla7856");
        System.out.println(message);
    }

当执行完reference.get() 后,返回的 HelloService 是什么样子呢?肯定不是Consumer 端产生的 HelloServiceImpl,如果是这样,那过滤器岂不是没用了?
下面一步一步看博主分析。

ReferenceConfig 的 createProxy

主要就是在 ReferenceConfigcreateProxy 方法,会产生一个代理对象。
在方法内部检查完参数之后,则会初始化invoker,如果有指定多个url,则会加入集群容错机制,这里先分析通过Protocol 而获取 的refer方法而获取具体invoker对象。

            if (urls.size() == 1) {
            // 当 url有多个
                invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
            } else {
                List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
                URL registryURL = null;
                for (URL url : urls) {
                    invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
                    if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        registryURL = url; // use last registry url
                    }
                }
                if (registryURL != null) { // registry url is available
                    // use RegistryAwareCluster only when register's CLUSTER is available
                    URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
                    // The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
                    invoker = CLUSTER.join(new StaticDirectory(u, invokers));
                } else { // not a registry url, must be direct invoke.
                    invoker = CLUSTER.join(new StaticDirectory(invokers));
                }
            }

而此时,url为 registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-consumer&dubbo=2.0.2&pid=6501&refer=application%3Ddubbo-consumer%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.anla.rpc.pureapi.service.HelloService%26lazy%3Dfalse%26methods%3Dhello%26pid%3D6501%26register.ip%3D192.168.1.107%26release%3D2.7.2%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1566912707974&registry=zookeeper&release=2.7.2&timestamp=1566912927481
此时 REF_PROTOCOLProtocol$Adaptive ,上一篇文章有讲默认的 Adaptive类原理,可以参考:Dubbo进阶(七)- Dubbo 中默认的 Adaptive类生成过程及例子
所以此时加载的将是 RegistryProtocol,并且执行它的 refer方法。
下面给出 Protocol$Adaptive 类的refer方法

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

RegistryProtocol

上一小节发现,由于url 协议为 registry,所以当它调用 ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); 将传入 registry
毫无疑问是返回一个 RegistryProtocol,那么 RegistryProtocol是怎样的呢?

ExtensionLoader的 getExtension

首先看看 ExtensionLoadergetExtension 方法,它是如何组装并返回这个类的呢?

    public T getExtension(String name) {
		// 检查参数
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        // 尝试从缓存中获取 实例
        Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                // 没有则加锁创建
                    instance = createExtension(name);
                  // 重新创建回去
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

上面代码主要有以下逻辑:

  1. 检查参数
  2. 检查是否已有缓存
  3. 没有则调用 createExtension(name) 创建一个对应实例
createExtension

createExtension中,就是完成了具体实例的组装过程:

    private T createExtension(String name) {
    // 获取对应的扩展
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
        // 尝试看是否有缓存起来
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // 注入相关extension注解
            injectExtension(instance);
            // 获取该SPI 里面所有的包装类
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                // 由 SPI 文件中定义顺序,遍历
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

这段代码逻辑比较有意思,除了前面的检查参数、读取缓存外,就是使用到了SPI 文件中的包装类 cachedWrapperClasses
而Protocol的SPI 文件名为:org.apache.dubbo.rpc.Protocol

filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol

org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
native-thrift=org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
xmlrpc=org.apache.dubbo.xml.rpc.protocol.xmlrpc.XmlRpcProtocol
registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper

所以wrapperClasses 包装类集合顺序为ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper
Dubbo 中 包装类的特性就是拥有一个 SPI 对应类型的 单参数构造方法。
这样设计的结构很明显是可以组装成为链式调用。
经过遍历组装后,得到的链式结构为:
ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper->RegistryProtocol
当然上面链式结构也不一定准确,但是`RgistryProtocol一定是在最后调用。

开始比较纠结的点是,既然按照顺序读取,为啥不用List或者其他具有顺序性特性的集合呢?
这样做没有意义,因为 classloader 加载的顺序也是未知的。应当确保 wrapper class 本身的实现与顺序无关。
即保证在 任何一个 Protocol 时候,都会先调用 多个Wrapper 类。
https://github.com/apache/dubbo/issues/4578

RegistryProtocol 的 refer

所以这个refer调用流程,则会按照如下流程调用:

  1. ProtocolFilterWrapperrefer 方法,目的是初始化以及调用Filter
  2. QosProtocolWrapperrefer 方法,目的是初始化QoS 功能,默认是开启的
  3. ProtocolListenerWrapperrefer 方法,初始化 listener 功能
  4. RegistryProtocolrefer 方法,初始化相关注册信息,以及注册监听器

所以划重点: classloader加载SPI 文件类中无需性,所以每个类之前都会调用相应的 Wrapper 类,最后才会调用相关SPI 类。

Protocol 的 SPI 文件中的这三个 Wrapper,当然有不同的功能,但是他们的 refer 方法则都会考虑 是否为registry 服务。

  1. ProtocolFilterWrapperrefer 方法:
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
    }
  1. QosProtocolWrapperrefer 方法:
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            startQosServer(url);
            return protocol.refer(type, url);
        }
        return protocol.refer(type, url);
    }
  1. RegistryProtocolrefer 方法:
    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
                Collections.unmodifiableList(
                        ExtensionLoader.getExtensionLoader(InvokerListener.class)
                                .getActivateExtension(url, INVOKER_LISTENER_KEY)));
    }

如果是,则会直接去先初始化 RegistryProtocol,相关注册中心初始化的且看下回分解。

觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,Dubbo小吃街不迷路:
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值