服务治理中间件 Dubbo 原理解析(Dubbo内核实现)读书笔记

 


一.SPI(service provider interface)传统机制,典型案例举例如下:
JDK 实现 spi 服务查找: ServiceLoader
ServiceLoader 会遍历所有 jar 查找 META-INF/services/com.example.Spi 文件
    public interface Spi {
        boolean isSupport(String name);
        String sayHello();
    }
    
    在A 厂商提供的 jar 包中的 META-INF/services/com.example.Spi 文件内容为:
    com.a.example.SpiAImpl #厂商 A 的 spi 实现全路径类名

    public class SpiAImpl implements Spi {
            public boolean isSupport(String name) {
                return "SPIA".equalsIgnoreCase(name.trim());
            }
            public String syaHello() {
                return “hello 我是厂商 A”;
            }
    }
    在B 厂商提供的 jar 包中的 META-INF/services/com.example.Spi 文件内容为:
    com.b.example.SpiBImpl #厂商 B 的 spi 实现全路径类名

    public class SpiBImpl implements Spi {
      public boolean isSupport(String name) {return"SPIB".equalsIgnoreCase(name.trim());}
      public String syaHello() {
        return “hello 我是厂商 B”;
      }
    }
    一个接口多种实现,就如策略模式一样提供了策略的实现,但是没有提供策略的选择, 使用方可以根据     
   isSupport 方法根据业务传入厂商名来选择具体的厂商。
  public class SpiFactory {
        //读取配置获取所有实现
        private static ServiceLoader spiLoader =ServiceLoader.load(Spi.class);
        //根据名字选取对应实现
        public static Spi getSpi(String name) {
        for (Spi spi : spiLoader) {
            if (spi.isSupport(name) ) {
                return spi;
            }
        }
        return null;
       }
  }
二:基于 SPI 思想 Dubbo 内核实现
1.dubbo中应用举例
   public @interface SPI {
     String value() default ""; //指定默认的扩展点
   }
会依次从这几个文件中读取扩展点
META-INF/dubbo/internal/ //dubbo 内部实现的各种扩展都放在了这个目录
META-INF/dubbo/;META-INF/services/
   @SPI("dubbo") // 接口上打上 SPI 注解,默认扩展点名字为 dubbo
   public interface Protocol {}
dubbo 中 内 置 实 现 了 各 种 协 议 如 : DubboProtocol InjvmProtocol
HessianProtocol WebServiceProtocol 等等;如下以这个为例
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME)

2 ExtensionLoader的私有构造函数:
   private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
   }
 
3 加载适配类方法机制:getAdaptiveExtension/createAdaptiveExtension/getAdaptiveExtensionClass

4.具体Dubbo中spi加载机制  
    1 先熟悉这个类 ExtensionLoader 类
     ExtensionLoader.getExtensionLoader(Protocol.class)每个定义的 spi 的接口
    都会构建一个ExtensionLoader实例,存储在ConcurrentMap<Class<?>, ExtensionLoader<?>> 
    EXTENSION_LOADERS 这个map 对象中
    2 获取或者创建设配对象 getAdaptiveExtension
        1) 如 果 cachedAdaptiveClass有值,说明有且仅有一个实现类打了@Adaptive, 实例化这个对
           象返回
        2) 如果 cachedAdaptiveClass 为空, 创建设配类字节码。
        为什么要创建设配类,一个接口多种实现, SPI 机制也是如此,这是策
        略模式,但是我们在代码执行过程中选择哪种具体的策略呢。 Dubbo采
        用统一数据模式 com.alibaba.dubbo.common.URL( 它是 dubbo定义的数
        据模型不是 jdk 的类),它会穿插于系统的整个执行过程,URL中定义的
        协议类型字段protocol , 会根据具体业务设置不同的协议 。
        url.getProtocol()值可以是 dubbo 也是可以 webservice, 可以是zookeeper 也可以是
        redis 适配类的作用是根据url.getProtocol() 的值extName去
        ExtensionLoader.getExtension(extName)选取具体的扩展点实现所以能够利用javasist生成适配类的条件
           1)接口方法中必须至少有一个方法打上了@Adaptive 注解
           2)打上了@Adaptive 注解的方法参数必须有URL类型参数或者有参数中存在 getURL()方法

    3. getExtension/createExtension/getExtensionClasses/loadExtensionClasses 读取扩展点中的实现类
     a) 先读取 SPI 注解的 value 值,有值作为默认扩展实现的 key
     b) 依次读取路径的文件
        META-INF/dubbo/internal/ com.alibaba.dubbo.rpc.Protocol
        META-INF/dubbo/ com.alibaba.dubbo.rpc.Protocol
        META-INF/services/ com.alibaba.dubbo.rpc.Protocol
    4. loadDirectory/loadResource
        逐行读取 com.alibaba.dubbo.rpc.Protocol 文件中的内容,每行
        内容以 key/value 形式存储的。
     a) 判断类实现(如: DubboProtocol)上有木有打上@Adaptive 注解,如果
        打上了注解,将此类作为 Protocol协议的设配类缓存起来,读取下一行;
        否则适配类通过 javasisit 修改字节码生成,关于设配类功能作用后续介绍
     b) 如果类实现没有打上@Adaptive, 判断实现类是否存在入参为接口的构
        造器(就是 DubbboProtocol 类是否还有入参为 Protocol 的构造器),有
        的话作为Wrapper包装类缓存到此 ExtensionLoader 的 Set<Class<?>>集合中,
        这个其实是个装饰模式
     c) 如果即不是设配对象也不是 wrapped 的对象,那就是扩展点的具体实现
        对象,查找实现类上有没有打上 @Activate 注解 ,有缓存到变量
        cachedActivates 的 map 中将实现类缓存到 cachedClasses 中,以便于使用时获取
    
5.下面给出 createAdaptiveExtensionClass()/AdaptiveClassCodeGenerator.generate生成 生成 Protocol 适配类后:

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
	// 没有打上@Adaptive 的方法如果被调到抛异常
	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!")
    }

	// 没有打上@Adaptive 的方法如果被调到抛异常
	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!");
    }

	// 接口中 export 方法打上@Adaptive 注册
	public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0)
        throws com.alibaba.dubbo.rpc.Invoker {
        if (arg0 == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        //参数类中要有 URL 属性
        if (arg0.getUrl() == null)throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
            //从入参获取统一数据模型 URL
            com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        //从统一数据模型 URL 获取协议,协议名就是 spi 扩展点实现类的 key
        if (extName == null) throw new IllegalStateException(
            "Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        //利用 dubbo 服务查找机制根据名称找到具体的扩展点实现
        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);
    }

	// 接口中 refer 方法打上@Adaptive 注册
	public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0,
        com.alibaba.dubbo.common.URL arg1) throws {
        //统一数据模型 URL 不能为空
        if (arg1 == null)
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        //从统一数据模型 URL 获取协议,协议名就是 spi 扩展点实现类的 key
        String extName = (url.getProtocol() == null ? "dubbo" :url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Failtogetextension(com.a libaba.dubbo.rpc.Protocol) name from url("+ url.toString() + ") use keys([protocol])");
        //利用 dubbo 服务查找机制根据名称找到具体的扩展点实现
        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);
    }
}

6. 通过 AdaptiveClassCodeGenerator().generate生成 生成如上的 java 源码代码,要
被 java 虚拟机加载执行必须得编译成字节码, dubbo 提供两种方式去执行代码的编译 
	1)利用 JDK 工具类编译 
	2)利用 javassit 根据源代码生成字节码。
    	1) 生成 Adaptive 代码 code
    	2) 利用 dubbo 的 spi 扩展机制获取 compiler 的设配类
   	    3) 编译生成的 adaptive 代码
    3)在此顺便介绍下@Adaptive 注解打在实现类上跟打在接口方法上的区别
        1) 如果有打在接口方法上,调 ExtensionLoader.getAdaptiveExtension()
        获取设配类,会先通过前面的过程生成java的源代码,在通过编译器编
        译成class加载 。但是Compiler的实现策略选择也是通过
        ExtensionLoader.getAdaptiveExtension(),如果也通过编译器编译成
        class 文件那岂不是要死循环下去了吗?
        2) ExtensionLoader.getAdaptiveExtension(),对于有实现类上去打了注解
        @Adaptive 的 dubbo spi 扩展机制,它获取设配类不在通过前面过程生成设配类 java 源代        
        码,而是在读取扩展文件的时候遇到实现类打了注解@Adaptive 就把这个类作为设配类缓存在 
        ExtensionLoader 中,调用是直接返回
7. 自动 Wrap 上扩展点的 Wrap 类
这是一种装饰模式的实现,在 jdk 的输入输出流实现中有很多这种设计,在
于增强扩展点功能。这里我们拿对于 Protocol 接口的扩展点实现作为实例讲解:
Protocol 继承关系 ProtocolFilterWrapper,ProtocolListenerWrapper 这个两个类是装饰对象用来增强其他扩展点实现的功能。 ProtocolFilterWrapper 功能主要是在 refer 引用远程服务的中透明的设置一系列的过滤器链用来记录日志,处理超时,权限控制等等功能;ProtocolListenerWrapper 在 provider 的 exporter,unporter服务和consumer的refer服务, destory 调用时添加监听器, dubbo 提供了扩展但是没有默认实现哪些监听器。
Dubbo 是如何自动的给扩展点 wrap 上装饰对象的呢?
1) 在 ExtensionLoader.loadDirectory 加载扩展点配置文件的时候,对扩展点类有接口类型为参数的构造器就是包转对象,缓存到集合中去
2) 在调ExtensionLoader的createExtension(name)根据扩展点 key 创建扩展的时候,先实例化扩展点的实现, 在判断时候有此扩展时候有包装类缓存,有的话利用包转器增强这个扩展点实现的功能。

8. IOC大家所熟知的ioc是spring的三大基础功能之一,dubbo 的ExtensionLoader 在加载扩展实现的时候内部实现了个简单的ioc机制来实现对扩展实现所依赖的参数的注入, dubbo对扩展实现中公有的set方法且入参个数为一个的方法,尝试从对象工厂ObjectFactory获取值注入到扩展点实现中去
下面我们来看看 ObjectFactory 是如何根据类型和名字来获取对象的,ObjectFactory 也是基于dubbo的spi扩展机制的它跟Compiler接口一样适配类注解@Adaptive是打在类AdaptiveExtensionFactory 上的不是通过 javassist 编译生成的。AdaptiveExtensionFactory 持有所有ExtensionFactory对象的集合, dubbo内部默认实现的对象工厂是 SpiExtensionFactory 和SpringExtensionFactory,他们经过 TreeMap 排好序的查找顺序是优先先从SpiExtensionFactory 获取,如果返回空在从 SpringExtensionFactory 获取
1) SpiExtensionFactory 工厂获取要被注入的对象,就是要获取dubbo spi
    扩展的实现,所以传入的参数类型必须是接口类型并且接口上打上了
    @SPI 注解,返回的是一个适配类对象。
2) SpringExtensionFactory, Dubbo 利用 spring 的扩展机制跟 spring 做了
    很好的融合。在发布或者去引用一个服务的时候,会把 spring 的容器添
    加到 SpringExtensionFactory 工厂集合中去, 当 SpiExtensionFactory
    没有获取到对象的时候会遍历 SpringExtensionFactory 中的 spring 容
    器来获取要注入的对象

三: 动态编译
我们运行的 java 代码,一般都是编译之后的字节码。 Dubbo 为了实现基于 spi
思想的扩展特性,特别是能够灵活添加额外功能,对于扩展或者说是策略的选择
这个叫做控制类也好设配类也好的类要能够动态生成。当然对应已知需求如
Protocol, ProxyFactory 他们的策略选择的设配类代码 dubbo 直接提供也无妨,
但是 dubbo 作为一个高扩展性的框架,使得用户能够添加自己的需求,根据配置
动态生成自己的设配类代码,这样就需要在运行的时候去编译加载这个设配类的
代码。下面我们就是来了解下 Dubbo 的动态编译。

编译接口定义
@SPI("javassist")
public interface Compiler {
    Class<?> compile(String code, ClassLoader classLoader);
}

SPI 注解表示如果没有配置,dubbo 默认选用 javassist 编译源代码
接口方法 compile 第一个入参 code,就是 java 的源代码
接口方法 compile 第二个入参 classLoader,按理是类加载器用来加载编译后的
字节码,其实没用到,都是根据当前线程或者调用方的 classLoader加载的
@Adaptive
public class AdaptiveCompiler implements Compiler {
    private static volatile String DEFAULT_COMPILER;public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader =
            ExtensionLoader.getExtensionLoader(Compiler.class);
        String name = DEFAULT_COMPILER; // copy reference
        if (name != null && name.length() > 0) {
            compiler = loader.getExtension(name);
        } else {
            compiler = loader.getDefaultExtension();
        }
        return compiler.compile(code, classLoader);
    }
}
AdaptiveCompiler 是 Compiler 的适配类, 它有类注解@Adaptive 表示这个
Compile r 的设配类不是动态编译生成的。 AdaptiveCompiler 作用就是策略的选
择,根据条件选择何种编译策略来编译动态生成的源代码。
AbstractCompiler 为编译的抽象类,抽象出公用逻辑,这里它主要是利用正则
匹配出源代码中的包名和类名后先在 jvm 中 Class.forName 看下是否存在,如果
存在反回,不存在在执行编译与加载。
关于 JavassistCompiler 和 JdkCompiler 执行 doCompile 的过程都是利用
Javassit 和 Jdk 提供的相关 api 或者扩展接口实现的。


如果系统配置中没有给 AdaptiveCompiler 设置哪种 compiler, 那么获取默认
compiler, 默认策略根据打在接口 Compiler 上的@SPI 值为 javassist, 所以
dubbo 默认利用 javassist 生成 SPI 机制的设配用来根据统一数据模型 URL 中获
取协议来选择使用何种策略

Adaptive注解理解

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

icool_ali

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值