栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

dubbo有注册中心时,虽然Protocol的@SPI指定了‘dubbo’却执行先RegistryProtocol后DubboProtocol

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

dubbo有注册中心时,虽然Protocol的@SPI指定了‘dubbo’却执行先RegistryProtocol后DubboProtocol

目录

总结

Protocol

@Adaptive修饰方法来指明需要动态生成代码类来增强指定方法

服务发布过程#export

#loadRegistries方法里将协议从‘dubbo’ 改为‘registry’

#doExportUrlsFor1Protocol方法里执行Protocol$Adaptive#export服务发布

执行进入Protocol$Adaptive#export

执行进入RegistryProtocol#export方法,最终回到DubboProtocol


总结
  1. @SPI:注解可以指定名称,用来匹配接口的实现类。在接口配置文件里不仅有最终要使用的target,还把有【该接口类型作为入参的构造器】的实现类当做它的wrapper,在创建target对象时裹在外层。
  2. @Adaptive: 运行时对被该注解修饰了方法的类,用javassist生成并编译增强代码。只有被修饰的方法才被增强! 

所以Protocol实际上是Protocol$Adaptive,在创建实例时外层用wrapper包裹target。最重要的是ProtocolFilterWrapper, 它对DubboProtocol订阅和发布过程里的Invoker创建过滤器链,eg. MonitorFilter、TPSLimiter、ExecuteLimitFilter...

Protocol

Protocol提供了服务发布#export 及 服务消费订阅#refer两个操作入口。 技术原理上是相通的。

本文以服务发布Protocol#export的过程实现为例,来讲述dubbo对  @SPI(需要载入的接口实现类文件配置化并通过名称指定使用)和@Adaptive(动态创建并编译增强代码)的逻辑。

@SPI("dubbo")
public interface Protocol {
  
    int getDefaultPort();

    //服务提供者发布服务, 这里的invoker是serviceImpl的封装代理
    @Adaptive
     Exporter export(Invoker invoker) throws RpcException;

    // 消费者的订阅服务, 这里会绑定通信Client
    @Adaptive
     Invoker refer(Class type, URL url) throws RpcException;

    void destroy();
}

图里有2个重要的注解:

  1. 类定义上的@SPI("dubbo") : 结合dubbo对spi的实现, 由文件配置的方式来指定加载接口的实现实体类。 匹配命名指定使用实现类DubboProtocol;
  2. 在#export、#refer方法上使用的@Adaptive : dubbo对有该注解修饰了方法的类动态生成增强代码类,真正被增强的只有被修饰了的方法! 

在Provider发布服务的过程源码里,ServiceBean的父类ServiceConfig里有个静态Protocol对象:

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

配置文件META-INFdubbointernalcom.alibaba.dubbo.rpc.Protocol:

按常规逻辑这里指定的就该是DubboProtocol, 但实际上确如下图所示:Protocol$Adaptive

这也就引出了本文的主题:dubbo会对@Adaptive修饰的方法体动态生成增强代码。具体如下:

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;


public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    
    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        // 根据extName找到具体适应类,然后调用方法
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        // 根据extName找到具体适应类,然后调用方法
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

@Adaptive修饰方法来指明需要动态生成代码类来增强指定方法

当执行进入 ExtensionLoader#getAdaptiveExtension()  -> #createAdaptiveExtension 动态创建并编译代码的过程里:首先会从配置文件中装载实现Protocol接口的实现类。

 判定:  [有入参是Protocol.class的构造器] 的实现类将被集中到“cachedWrapperClasses”。 用来后续对指定真正使用的Protocol实例进行封装!

// 构造函数的入参是指定的接口   
 private boolean isWrapperClass(Class clazz) {
        try {
            clazz.getConstructor(type);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

ExtensionLoader#createAdaptiveExtensionClass使用 javassist 字节码编辑技术动态创建并编译了 Protocol$Adaptive !  在文章开头有贴它源码。

服务发布过程#export

回到入口处:  ServiceBean#export() -> #doExport() ->#doExportUrls

  private void doExportUrls() {
        List registryURLs = loadRegistries(true); //判定如果有注册中心,重写url协议为register
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);//发布服务
        }
    }

#loadRegistries方法里将协议从‘dubbo’ 改为‘registry’

原始的URL信息配置如下图:

经过处理后得到registry协议头的URL:

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=trusts-core&dubbo=2.6.2&logger=slf4j&pid=27468®istry=zookeeper×tamp=1654680258624

#doExportUrlsFor1Protocol方法里执行Protocol$Adaptive#export服务发布

这里会根据配置参数组装dubbo协议头的URL,并在之后创建Invoker代理时,将它拼入registry协议头URL末尾的export部分!

dubbo://139.139.37.184:20880/cn.utrust.trusts.interf.BuyBackInterface?anyhost=true&application=trusts-core&bind.ip=139.139.37.184&bind.port=20880&dubbo=2.6.2&generic=false&group=cmis-utrusts&interface=cn.utrust.trusts.interf.BuyBackInterface&logger=slf4j&methods=buyBackSendRepayToYcloans,buyBack&pid=25416&retries=-1&revision=1.0&side=provider&threads=1024&timeout=180000×tamp=1654683368355&version=1.0

 转换成了:

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=trusts-core&dubbo=2.6.2&export=dubbo%3A%2F%2F139.139.37.184%3A20880%2Fcn.utrust.trusts.interf.BuyBackInterface%3Fanyhost%3Dtrue%26application%3Dtrusts-core%26bind.ip%3D139.139.37.184%26bind.port%3D20880%26dubbo%3D2.6.2%26generic%3Dfalse%26group%3Dcmis-utrusts%26interface%3Dcn.utrust.trusts.interf.BuyBackInterface%26logger%3Dslf4j%26methods%3DbuyBackSendRepayToYcloans%2CbuyBack%26pid%3D25416%26retries%3D-1%26revision%3D1.0%26side%3Dprovider%26threads%3D1024%26timeout%3D180000%26timestamp%3D1654683368355%26version%3D1.0&logger=slf4j&pid=25416®istry=zookeeper×tamp=1654683267651

执行进入Protocol$Adaptive#export
// Protocol$Adaptive源码
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); // 如果url的protocol为空, 默认走 dubbo
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        // 根据extName找到具体适应类,然后调用方法
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

从上述Protocol$Adaptive源码可知, 这里关键的是: url#getProtocol()决定了下一个要执行的Protocol接口实现类!

 所以此时使用ExtensionLoader#getExtension('registry')去拿registryProtocol的实例:如果没有历史缓存,则#createExtension创建新的实例。

 从上图中可知道: 新建时会使用多个wrapperClass来封装真实对象。这几个wrapperClass就是在读配置文件时归集而来[有Protocol为入参构造器]的实现类。

  1. ProtocolFilterWrapper : 对非‘registry’协议头的URL在export、refer时给Invoker绑定使用配置的过滤器链FilterChain!Filter通过@Activate来指定支持的group:  provider consumer 及在URL特别指定匹配相应Filter的操作指令:executes、tps、tracing...。 
  2. QosProtocolWrapper : Quality of Service,服务质量。 用来开启一个默认22222端口的服务,用来后台telnet上来online和offline service
  3. ProtocolListenerWrapper : 通过 ExporterListener、InvokerListener 接口对服务的订阅和消费进行监听。默认看起来没具体实现。

这里还需要关注一下#injectExtension方法, 通过反射setter方式,又把AdaptiveExtensionFactory里缓存的Protocol$Adaptive注入回RegistryProtocol里!

执行进入RegistryProtocol#export方法,最终回到DubboProtocol

执行到该方法后,会拿到源头的dubbo协议头URL,从下图中可以看出,又要回到Protocol$Adaptive#export的执行过程里了。只是这次就要选择DubboProtocol#export

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/964640.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号