Dubbo源码解析-——SPI机制

news2024/9/30 5:34:16

文章目录

  • 一、什么是SPI机制
  • 二、Java原生的SPI机制
    • 2.1、javaSPI示例
      • 2.1.1、编写接口和实现类
      • 2.1.2、编写配置文件
      • 2.1.3、通过SPI机制加载实现类
      • 2.1.4、JAVA SPI 源码解析
        • 2.1.4.1、ServiceLoader#load
        • 2.1.4.2、ServiceLoader构造方法
        • 2.1.4.3、ServiceLoader#reload
        • 2.1.4.4、LazyIterator
        • 2.1.4.5、LazyIterator#hasNext
        • 2.1.4.6、LazyIterator#hasNextService
        • 2.1.4.7、LazyIterator#parse
        • 2.1.4.7、LazyIterator#parse
        • 2.1.4.7、LazyIterator#next
      • 2.1.5、JAVA SPI 优缺点
  • 二、Dubbo的SPI机制
    • 2.1、Dubbo SPI 示例
      • 2.1.1、编写接口和实现类
      • 2.1.2、编写配置文件
      • 2.1.3、通过Dubbo SPI 加载实现类
    • 2.2、扩展点注解
      • 2.2.1、@SPI注解
      • 2.2.2、自适用@Adaptive注解
        • 2.2.2.1、@Adaptive使用场景
        • 2.2.2.2、@Adaptive使用示例
      • 2.2.3、自动激活@Activate注解
        • 2.2.3.1、@Activate使用场景
        • 2.2.3.2、@Activate使用示例
      • 2.2.4、不自动注入@DisableInject注解
    • 2.3、Dubbo SPI 机制源码解析
      • 2.3.1、ExtensionLoader#getExtensionLoader
      • 2.3.2、ExtensionLoader#getExtension
      • 2.3.3、ExtensionLoader#createExtension
        • 2.3.3.1、ExtensionLoader#getExtensionClasses
          • 2.3.3.1.1、ExtensionLoader#loadExtensionClasses
          • 2.3.3.1.2、ExtensionLoader#loadDirectory
          • 2.3.3.1.3、ExtensionLoader#loadResource
          • 2.3.3.1.4、ExtensionLoader#loadResource
        • 2.3.3.2、ExtensionLoader#injectExtension
          • 2.3.3.2.1、AdaptiveExtensionFactory#getExtension
          • 2.3.3.2.2、SpiExtensionFactory#getExtension
          • 2.3.3.2.3、SpiExtensionFactory#getExtension
      • 2.3.4、ExtensionLoader#getAdaptiveExtension
        • 2.3.4.1、ExtensionLoader#createAdaptiveExtension
        • 2.3.4.2、ExtensionLoader#getAdaptiveExtensionClass
        • 2.3.4.3、ExtensionLoader#createAdaptiveExtensionClass
        • 2.3.4.3、ExtensionLoader#createAdaptiveExtensionClassCode
      • 2.3.5、ExtensionLoader#getAdaptiveExtension


一、什么是SPI机制

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。

二、Java原生的SPI机制

Java SPI 规定在 classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后文件里面记录的是此 jar 包提供的具体实现类的全限定名。这样当我们引用了某个 jar 包的时候就可以去找这个 jar 包的 META-INF/services/ 目录,再根据接口名找到文件,然后读取文件里面的内容去进行实现类的加载与实例化。

2.1、javaSPI示例

2.1.1、编写接口和实现类

首先在我们当前的项目路径下定义一个接口和对应的两个实现类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1.2、编写配置文件

在classpath路径下的META-INF/services文件夹下配置好接口的实现类,这里规则是文件名是接口的权限定名,内容是实现类的权限定类名,多个实现类之间用换行符分隔。

在这里插入图片描述

2.1.3、通过SPI机制加载实现类

这里我们首先通过ServiceLoader加载Person的实现类,然后我们遍历调用实现类的say方法,我们可以看到我们在配置文件中配置的两个实现类全部被加载出来了。

在这里插入图片描述

2.1.4、JAVA SPI 源码解析

2.1.4.1、ServiceLoader#load

  1. 获取当前线程的类加载器
  2. 然后调用重载方法,把刚刚获得类加载器也传进去,在重载方法中会new一个ServiceLoader的实例。

在这里插入图片描述
在这里插入图片描述

2.1.4.2、ServiceLoader构造方法

  1. 对加载的class类型做判空
  2. 判断有没有指定类加载器,如果没有指定则使用系统类加载器
  3. 初始化访问控制器
  4. 调用reload方法重新加载

在这里插入图片描述

2.1.4.3、ServiceLoader#reload

  1. 这里首先会清空实例化好的缓存
  2. 然后new出来了一个LazyIterator类型的迭代器,这个是重点。

在这里插入图片描述

2.1.4.4、LazyIterator

在前面的我们的示例中我们会从serviceLoader中获取对应的iterator,然后调用其hasNext和next方法,我们看下这个iterator是不是上面的LazyIterator

在这里插入图片描述

这里我们看到这个迭代器首先会判断缓存中有没有,如果缓存中有则直接取缓存中放好的实例,如果没有则会调用lookupIterator的方法执行加载逻辑。这里为什么叫LazyIterator,因为只有在调用到hasNext方法的时候,才会去懒加载对应实现类的实例。

在这里插入图片描述

2.1.4.5、LazyIterator#hasNext

这里就是判断如果权限控制器不为空,则通过doPrivileged方法让程序突破当前域权限的限制,这个感兴趣可以了解,最终都会调用hasNextService方法

在这里插入图片描述

2.1.4.6、LazyIterator#hasNextService

  1. 这里会使用classLoader加载META-INF/services/com.alibaba.dubbo.demo.spi.Person文件中的内容
  2. 然后读取出来文件中的内容,然后调用parse方法解析配置文件中的内容并赋值给pending迭代器。

在这里插入图片描述

2.1.4.7、LazyIterator#parse

  1. 这里就会遍历文件中的内容,然后解析出来每一行,就是对应的实现类的权限定类名然后收集起来返回。

在这里插入图片描述

2.1.4.7、LazyIterator#parse

1.这里就是会解析每一行,然后过滤注释,检查内容合法性,还有根据缓存过滤避免重复加载。

在这里插入图片描述

2.1.4.7、LazyIterator#next

  1. 上面我们在hasNext方法中已经看到了他会去读取配置文件并缓存接口的实现类的权限定类名,然后这些实现类什么时候进行实例化的,我们看下next方法,这里会直接调用nextService方法

在这里插入图片描述

  1. 获取下一个接口实现类的权限定类名
  2. 然后通过反射创建出来实现类,然后转成接口类型
  3. 然后存入providers缓存中并返回

在这里插入图片描述

2.1.5、JAVA SPI 优缺点

优点:

  • 使用 Java SPI 机制的优势是实现解耦,使第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
  • 相比使用提供接口 jar 包供第三方服务使用的方式,SPI 使得源框架不必关心接口的实现类的路径,可以不使用硬编码 import 导入实现类。

缺点:

  • 虽然 ServiceLoader 使用了懒加载,但结果还是通过遍历获取,基本上可以说是全部实例化了一遍,所以说,这个懒加载机制在此场景下是浪费的。
  • 由于是遍历获取,所以获取实现类的方式不够灵活。
    多个并发多线程使用 ServiceLoader 类的实例是不安全的。

二、Dubbo的SPI机制

Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求,Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径。

2.1、Dubbo SPI 示例

2.1.1、编写接口和实现类

  1. 这里需要在接口上面加上@SPI注解

在这里插入图片描述

在这里插入图片描述在这里插入图片描述

2.1.2、编写配置文件

  1. Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下
  2. 与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。

在这里插入图片描述

2.1.3、通过Dubbo SPI 加载实现类

  1. 这里我们可以通过ExtensionLoader来加载接口的不同实现,可以实现按需加载

在这里插入图片描述

2.2、扩展点注解

在上面案例中我们需要在接口上面标注@SPI注解,Dubob给我们提供了一些扩展点注解,可以帮我们动态选择对应的接口实现。

2.2.1、@SPI注解

该注解可以使用在类,接口,枚举上,在dubbo框架中都使用在接口上,它的作用就是标记该接口是一个dubbo spi接口,是一个扩展点,可以有多个接口的实现。该注解有一个value属性,表示该接口的默认实现。

在这里插入图片描述

2.2.2、自适用@Adaptive注解

该注解可以使用在类,接口,枚举,方法上面。如果标注在接口的方法上时,可以根据参数动态获取实现类,在第一次getExtension时,会自动生成和编译一个动态的Adaptive类,达到动态实现类的效果。如果标注在实现类上的时候主要是为了直接固定对应的实现而不需要动态生成代码实现。
该注解有一个参数value,是个string数组类型,表示可以通过多个元素依次查找实现类。

在这里插入图片描述

2.2.2.1、@Adaptive使用场景

举例:一个接口有三个方法,分别是methodA,methodB,methodC。此接口有三个实现类impl1,impl2,impl3。接口通过@SPI注解指定默认实现为impl1,通过@Adaptive注解及URL参数生成一个动态类,可以完成以下动作。

接口能将每个方法的实现都对应不同实现类。例如接口可以的methodA由impl1执行,methodB由impl2执行,methodC由impl3执行。
接口能让方法按一定优先级选择实现类来执行。例如methodA方法上有注解@Adaptive({“key1”,“key2”,“key3”})先尝试查找参数URL中key1对应的实现类,未指定则取key2,还未指定则key3,再没指定则使用SPI注解规定的默认实现类去执行方法。

2.2.2.2、@Adaptive使用示例

  1. 这里在接口上通过@SPI指定了默认实现为wheelMaker1,并且通过在方法上标注@Adaptive注解,可以动态从url中解析出来key对应的值并找到起对应的实现类。

在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述

这里在配置文件中指定WheelMaker的三个实现。

在这里插入图片描述

这里我们构造了一个url,并指定了一些key,然后调用ExtensionLoader#getAdaptiveExtension方法获得自适应扩展实现,通过测试案例我们可以发现方法调用的都是不同的实现:

  1. 这里makeWheelA方法上面标注了@Adaptive注解,这里value属性为空,则会从url中查找wheel.maker对应的value,这里wheel.maker是接口WheelMark的转换,将驼峰处分开并转换成小写,以"."连接起来,然后就找到实现类wheelMaker2了
  2. 这里makeWheelB方法上标注了 @Adaptive(“key4”),这里指定了key4,但是我们的url中没有传key4,这里会匹配不到,然后他就会走到我们的在SPI注解中指定的默认实现wheelMaker1
  3. 这里makeWheelC方法上标注了 @Adaptive({“key3”,“key2”,“key1”}),这里指定了3个,会从url中按顺序查找,这里先查找key3,然后就找到了wheelMaker3,如果没找到key3,再往下找key2。

在这里插入图片描述

2.2.3、自动激活@Activate注解

该注解可以标记在类,接口,枚举和方法上,主要使用在有多个扩展点实现,需要根据不通条件激活的场景中
@Activate参数解释:

  • group表示URL中的分组如果匹配的话就激活,可以设置多个
  • value 查找URL中如果含有该key值,就会激活
  • before填写扩展点列表,表示哪些扩展点需要在本扩展点的前面
  • after表示哪些扩展点需要在本扩展点的后面
  • order 排序信息

在这里插入图片描述

2.2.3.1、@Activate使用场景

在Dubbo中有Filter使用,对于Filter来说我们会遇到这样的问题,Filter自身有很多的实现,我们希望某种条件下使用A实现,另外情况下使用B实现,这个时候我们前面介绍@SPI和@Adaptive就不能满足我们要求了,这个时候我们就需要使用@Activate。
Activate注解表示一个扩展是否被激活(使用),可以放在类定义和方法上,Dubbo中用它在扩展类定义上,表示这个扩展实现激活条件和时机。

2.2.3.2、@Activate使用示例

  1. 这里下面这个接口会有多个实现,分别有默认实现,多个组,排序,从URL获取值的实现。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在resources下的META-INF/dubbo下面新建接口的权限定文件名

在这里插入图片描述

这里可以通过测试案例看到,我们可以我们指定的3个注解属性的作用:

  • group 修饰的实现类可以列举为一种标签,标签用来区分是在 Provider 端被激活还是在 Consumer 端被激活;
  • value 修饰的实现类只在 URL 参数中出现指定的 key 时才会被激活;
  • order 用来确定扩展实现类的排序;

在这里插入图片描述

2.2.4、不自动注入@DisableInject注解

该注解可以使用在类,接口,方法上,在createExtension的时候表示不自动注入,在ExtensionLoader 类中,创建子类实现的时候会自动注入由ExtensionLoader管理的类,就会调用 injectExtension(instance)方法,然后在方法遍历的时候会检测有没有该DisableInject注解,如果有的话就会跳过,不会自动注入。这里比较简单就不搞使用案例了,后面分析源码我们可以看到其实现。

在这里插入图片描述

2.3、Dubbo SPI 机制源码解析

2.3.1、ExtensionLoader#getExtensionLoader

  1. 首先坐下校验,判断类型是否为空,判断是否是个接口类,判断接口上面是否标注了@SPI注解
  2. 从缓存中找之前有没有创建过同样类型的ExtensionLoader,有就从缓存中获取,没有则new一个出来返回。

在这里插入图片描述

2.3.2、ExtensionLoader#getExtension

  1. 方法是获取某个类型具体的实现类,这里首先从缓存中获取,如果缓存中没有则会new一个holderput到缓存中
  2. 这里会对holder对象进行加锁,防止相同实例重复创建
  3. 调用createExtension创建对应的实现类并设置到holder对象中

在这里插入图片描述

2.3.3、ExtensionLoader#createExtension

  1. 这里首先调用getExtensionClasses方法获取接口对应的所有的扩展实现类,然后根据名称获取对应的扩展实现类。
  2. 然后判断扩展实例缓存中有没有这个实现类的对象,如果有说明之前已经创建过这个类的实例了,如果没有,则会通过反射创建这个类的实例
  3. 调用injectExtension方法向创建出来的对象注入依赖的属性
  4. 对依赖注入后的实例进行AOP(Wrapper),把当前接口类的所有的Wrapper全部⼀层⼀层包裹在实例对象上,每包裹个Wrapper后,也会对Wrapper对象进行依赖注入。
  5. 返回最终的Wrapper对象。

在这里插入图片描述

2.3.3.1、ExtensionLoader#getExtensionClasses

  1. 这里首先从缓存中获取当前接口的所有扩展点实现类,如果没有,那么需要解析配置文件进行加载

在这里插入图片描述

2.3.3.1.1、ExtensionLoader#loadExtensionClasses
  1. 首先解析接口上的SPI注解,然后拿到SPI注解配置的默认实现类并缓存起来
  2. 调用loadDirectory从指定的3个配置路径中查找扩展配置文件,并放入extensionClasses中,路径分别为:
  • /META-INF/dubbo/internal/
  • / META-INF/dubbo/
  • /META-INF/services/ 这个主要是兼容jdk的spi
  1. 返回加载出来的扩展实现类extensionClasses

在这里插入图片描述
在这里插入图片描述

2.3.3.1.2、ExtensionLoader#loadDirectory
  1. 这里根据目录前缀和当前类型的权限定名拼接出来一个文件名,例:META-INF/dubbo/com.test.Robot
  2. 获取当前ExtensionLoader对应的classLoader
  3. 使用类加载器加载文件获取到对应的资源
  4. 调用loadResource解析资源获取到对应的实现类信息

在这里插入图片描述

2.3.3.1.3、ExtensionLoader#loadResource
  1. 这里就是对文件资源的解析,会一行一行读,然后过滤掉注释内容,然后解析处实现类的名字和权限定类名
  2. 调用loadClass加载实现类

在这里插入图片描述

2.3.3.1.4、ExtensionLoader#loadResource
  1. 首先判断实现类是否是接口的子类,不是的话抛出异常
  2. 判断实现类上面有没有@Adaptive注解,如果有的话再判断cachedAdaptiveClass是否是null,这个cachedAdaptiveClass就是缓存带有@Adaptive注解的实现类,没有的话就赋值给cachedAdaptiveClass,有的话就是不是同一个实现类class,不是的话就抛出异常,这里主要是为了一个扩展点接口只能有一个@Adaptive 放到类上面的实现类。
  3. 判断是不是包装类,这里就是根据这个实现类有没有一个构造器是传入当前类型的,如果是那么就说明是包装类,就缓存到cachedWrapperClasses中
  4. 判断name是否为空,如果是空的话,就调用findAnnotationName获取name,这里主要是兼容jdk的spi,因为dubbo的spi是 名称=实现类的权限定名,jdk直接是实现类的权限定名,这里是兼容一下。
  5. 如果有name的话,再从实现类上获取@Activate注解,如果上面有标注这个注解通过cachedActivates缓存起来
  6. 接着就是遍历分割完的names,如果cachedNames 不包含 实现类class 的话就put进去缓存起来,如果extensionClasses中没有当前name的class就put进去缓存起来,如果有了的话,就要判断两个class是否相等,不相等话就要抛出异常了,因为同一个扩展点接口不允许有相同name的扩展点实现类的出现。

在这里插入图片描述

这里判断是不是wrapper,就是根据这个实现类有没有一个构造器是传入当前类型的。

在这里插入图片描述

主要就是判断有没有@Extension注解,如果没有的话,就要拿实现类的类名,然后判断实现类类名是否是以接口名为结尾的,如果是的话截取下要前面那块,如果不是以接口名为结尾的,就返回实现类的名字,如果有@Extension注解就直接拿注解里面的value值,这里主要是兼容jdk spi。

在这里插入图片描述

最后全部解析完成后,extensionClasses中放的就是配置文件中name作为key,然后实现类作为value的map

2.3.3.2、ExtensionLoader#injectExtension

上面已经解析配置文件获取对应扩展名的扩展实现类并创建出对应的实例,然后会掉这个方法对创建出来的实例的属性进行依赖注入

  1. 遍历实现类的所有方法,然后判断如果方法是set方法,并且只有一个参数,方法类型是public这样才能帮你自动注入,如果方法上标注了@DisableInject也不进行自动注入
  2. 获取参数的类型,并截取set方法中除了set的剩余字符串
  3. 调用objectFactory#getExtension获取指定类型的实现,这里objectFactory一般是AdaptiveExtensionFactory,这样获取到具体的实现并通过反射设置进去。

在这里插入图片描述

2.3.3.2.1、AdaptiveExtensionFactory#getExtension
  1. 根据扩展点类型获取具体实现类,这里也是根据ExtensionLoader获取支持的扩展实现,然后遍历支持的ExtensionFactory,获取对应的实现类
  2. 这里ExtensionFactory有两个具体实现,一个是SpiExtensionFactory,一个是SpringExtensionFactory

在这里插入图片描述

2.3.3.2.2、SpiExtensionFactory#getExtension
  1. 这里首先判断要注入的class是否类上面标注了SPI注解,如果标注了,则获取对应类型的ExtensionLoader
  2. 调用ExtensionLoader#getAdaptiveExtension获取适配器实现并返回

在这里插入图片描述

2.3.3.2.3、SpiExtensionFactory#getExtension
  1. 这里首先就是根据name去spring容器中获取对应的bean返回
  2. 如果根据name获取不到bean,则再根据对应的class类型去获取bean返回

在这里插入图片描述

2.3.4、ExtensionLoader#getAdaptiveExtension

  1. 在之前我们的示例中我们会调用getAdaptiveExtension方法获取自适应的扩展实现,方法逻辑大致如下:
  2. 先从缓存中获取自适应的实例,如果缓存中没有,则会调用createAdaptiveExtension方法创建一个自适应实例并返回。

在这里插入图片描述
在这里插入图片描述

2.3.4.1、ExtensionLoader#createAdaptiveExtension

  1. 这里首先调用getAdaptiveExtensionClass获取自适应扩展实现的class
  2. 然后通过反射创建实例
  3. 调用injectExtension方法对这个创建出来的实例进行依赖注入

在这里插入图片描述

2.3.4.2、ExtensionLoader#getAdaptiveExtensionClass

  1. 首先是获取所有的扩展实现类class,之前咱们分析过,会将实现类上带有@Adaptive注解的缓存到cachedAdaptiveClass 这个成员中。
  2. 那如果没有实现类上带有@Adaptive注解的时候,就要通过createAdaptiveExtensionClass来创建自适应扩展实现类了。

在这里插入图片描述

2.3.4.3、ExtensionLoader#createAdaptiveExtensionClass

  1. 首先是通过createAdaptiveExtensionClassCode方法来拼装成自适应实现类的代码
  2. 接着就是使用spi获取编译器,然后进行编译,加载,返回自适应实现类的class对象。

在这里插入图片描述

2.3.4.3、ExtensionLoader#createAdaptiveExtensionClassCode

  1. 首先遍历接口的所有方法,判断上面有没有标注@Adaptive注解,一个没有就抛出异常,只要有一个方法上有注解就继续执行
  2. 拼装package,import,然后就是类名(接口名+$Adaptive),然后实现一下接口
  3. 遍历方法,判断方法上是否有@Adaptive注解,如果没有将抛出异常的代码追加到code上,如果有@Adaptive注解,找出URL参数在参数列表的位置,如果有URL参数就拼装成判断是否是null,如果是null就抛出异常,然后设置到本地变量。
  4. 如果没有URL,就遍历参数类型里面有get方法并且get方法不是静态没有参数返回值是URL类型的,没有找到这么个条件的参数的时候,就会抛出异常 ,如果找到判断这个参数不是null,参数获取的URL不是null,然后URL设置到本地变量url中。
  5. 接着获取@Adaptive 注解值,如果没有值,就那class 名字来
    如果参数有Invocation类型的,然后生成判断null的代码与String methodName = arg0.getMethodName(),并设置标志hasInvocation为true.
  6. 接着就是遍历@Adaptive 上面value数组,这块主要是实现通过url来获取配置参数的值,这个值就是对应扩展实现类的name,判断extName如果是null的话就要抛出异常,接着就是通过getExtensionLoader(当前扩展接口).getExtension(extName)来获取扩展实现类对象,然后再通过这个实现类对象调用自己对应的这个方法.
  private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuilder = new StringBuilder();
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        //查找方法上面有没有@Adaptive注解
        for (Method m : methods) {
            if (m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // no need to generate adaptive class since there's no adaptive method found.
        if (!hasAdaptiveAnnotation)
            throw new IllegalStateException(
                    "No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");

        codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
        codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
        codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");

        for (Method method : methods) {
            //返回值
            Class<?> rt = method.getReturnType();
            //参数类型
            Class<?>[] pts = method.getParameterTypes();
            //异常
            Class<?>[] ets = method.getExceptionTypes();

            Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
            StringBuilder code = new StringBuilder(512);
            if (adaptiveAnnotation == null) {
                code.append("throw new UnsupportedOperationException(\"method ")
                        .append(method.toString()).append(" of interface ")
                        .append(type.getName()).append(" is not adaptive method!\");");
            } else {
                int urlTypeIndex = -1;
                //查找参数列表中有没有URL
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].equals(URL.class)) {
                        urlTypeIndex = i;
                        break;
                    }
                }
                // found parameter in URL type
                //说明有
                if (urlTypeIndex != -1) {
                    // Null Point check
                    String s =
                            String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
                                    urlTypeIndex);
                    code.append(s);

                    s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
                    code.append(s);
                }
                // did not find parameter in URL type
                else { //没有url参数的
                    String attribMethod = null;

                    // find URL getter method
                    LBL_PTS:
                    for (int i = 0; i < pts.length; ++i) {
                        //查找参数里面 有get方法并且返回值是URL的
                        Method[] ms = pts[i].getMethods();
                        for (Method m : ms) {
                            String name = m.getName();
                            if ((name.startsWith("get") || name.length() > 3)
                                    && Modifier.isPublic(m.getModifiers())
                                    && !Modifier.isStatic(m.getModifiers())
                                    && m.getParameterTypes().length == 0
                                    && m.getReturnType() == URL.class) {
                                urlTypeIndex = i;
                                attribMethod = name;
                                break LBL_PTS;
                            }
                        }
                    }
                    if (attribMethod == null) {
                        throw new IllegalStateException("fail to create adaptive class for interface " + type.getName()
                                + ": not found url parameter or url attribute in parameters of method "
                                + method.getName());
                    }

                    // Null point check
                    //验证有get方法返回值是URL的参数,是否为null,然后get到的URL是否是null
                    String s =
                            String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                                    urlTypeIndex, pts[urlTypeIndex].getName());
                    code.append(s);
                    s =
                            String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                                    urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
                    code.append(s);

                    //如果参数获取到的URL不是null,则设置URL到本地变量url中
                    s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
                    code.append(s);
                }

                //接着遍历@Adaptive注解上面的value数组,这块主要是通过url来获取配置参数的值,这个值就是对应扩展实现类的
                //name
                //判断extName如果是null的话就要抛出异常,接着就是通过getExtensionLoader(当前扩展接口).getExtension(extName)
                // 来获取扩展实现类对象,然后再通过这个实现类对象调用自己对应的这个方法。
                String[] value = adaptiveAnnotation.value();
                // value is not set, use the value generated from class name as the key
                if (value.length == 0) { //没有值
                    char[] charArray = type.getSimpleName().toCharArray();
                    StringBuilder sb = new StringBuilder(128);
                    for (int i = 0; i < charArray.length; i++) {
                        if (Character.isUpperCase(charArray[i])) {//是大写字母
                            if (i != 0) {
                                sb.append(".");
                            }
                            sb.append(Character.toLowerCase(charArray[i]));
                        } else {
                            sb.append(charArray[i]);
                        }
                    }
                    value = new String[] {sb.toString()};
                }

                boolean hasInvocation = false;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                        // Null Point check
                        String s =
                                String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                        code.append(s);
                        s = String.format("\nString methodName = arg%d.getMethodName();", i);
                        code.append(s);
                        hasInvocation = true;
                        break;
                    }
                }

                String defaultExtName = cachedDefaultName;
                String getNameCode = null;
                for (int i = value.length - 1; i >= 0; --i) {
                    if (i == value.length - 1) {
                        if (null != defaultExtName) {
                            if (!"protocol".equals(value[i]))
                                if (hasInvocation)
                                    getNameCode =
                                            String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                else
                                    getNameCode =
                                            String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode =
                                        String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                        } else {
                            if (!"protocol".equals(value[i]))
                                if (hasInvocation)
                                    getNameCode =
                                            String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                else
                                    getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                            else
                                getNameCode = "url.getProtocol()";
                        }
                    } else {
                        if (!"protocol".equals(value[i]))
                            if (hasInvocation)
                                getNameCode =
                                        String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                            else
                                getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                        else
                            getNameCode =
                                    String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
                    }
                }
                code.append("\nString extName = ").append(getNameCode).append(";");
                // check extName == null?
                String s = String.format("\nif(extName == null) " +
                                "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
                        type.getName(), Arrays.toString(value));
                code.append(s);

                s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
                code.append(s);

                // return statement
                if (!rt.equals(void.class)) {
                    code.append("\nreturn ");
                }

                s = String.format("extension.%s(", method.getName());
                code.append(s);
                for (int i = 0; i < pts.length; i++) {
                    if (i != 0)
                        code.append(", ");
                    code.append("arg").append(i);
                }
                code.append(");");
            }

            codeBuilder.append("\npublic ").append(rt.getCanonicalName()).append(" ").append(method.getName()).append("(");
            for (int i = 0; i < pts.length; i++) {
                if (i > 0) {
                    codeBuilder.append(", ");
                }
                codeBuilder.append(pts[i].getCanonicalName());
                codeBuilder.append(" ");
                codeBuilder.append("arg").append(i);
            }
            codeBuilder.append(")");
            if (ets.length > 0) {
                codeBuilder.append(" throws ");
                for (int i = 0; i < ets.length; i++) {
                    if (i > 0) {
                        codeBuilder.append(", ");
                    }
                    codeBuilder.append(ets[i].getCanonicalName());
                }
            }
            codeBuilder.append(" {");
            codeBuilder.append(code.toString());
            codeBuilder.append("\n}");
        }
        codeBuilder.append("\n}");
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuilder.toString());
        }
        return codeBuilder.toString();
    }

这个自适应就是根据接口自己实现一个实现类,然后这个实现类是能够根据@Adaptive 配置的参数值然后去URL中获取对应的值,然后再根据这个值使用spi获取扩展实现类,最后调用这个实现类的对应方法。

2.3.5、ExtensionLoader#getAdaptiveExtension

在之前我们的示例中我们会调用getActivateExtension方法获取自动激活的扩展实现,方法逻辑大致如下:

  1. 首先将values放到names这个list中,然后判断如果names中没有-default就继续执行,这里-default是用户自己设置,表示不使用这些配置的key
  2. 调用getExtensionClasses加载扩展点的所有实现类,这个之前剖析过
  3. 然后遍历cachedActivates这个map,这个是我们在执行getExtensionClasses的时候缓存的,根据注解中的配置,判断group是否一致,如果是这个group,再判断names中有没有包含这个names,然后判断names中有没有配置-name,-name表示不激活该扩展实现,再然后就是判断注解中的value在url中有对应参数,同时满足这些条件说明这个扩展实现是满足的,添加到exts集合中
  4. 对exts集合进行排序
  5. 最后再遍历一下names,如果name没有-开头或者是names中没有-xxx ,然后就通过name获取到对应的扩展点实现,添加到usrs这个list中,最后判断usrs这个list不是空的话,就将usrs元素扔到exts中,然后return exts。

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373423.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IoT项目系统架构案例2

项目背景 1.这个项目是对之前的案例的升级改造参考&#xff1a;IoT项目系统架构案例_iot案例_wxgnolux的博客-CSDN博客2.基于方案1的项目实施过程中碰到的问题,对硬件设备标准化的理念及新的功能需求(如根据天气预报温度调水温,APP界面可操作性优化等)•采用目前IoT主流厂商的架…

java的一些冷知识

接口并没有继承Object类首先接口是一种特殊的类&#xff0c;理由就是将其编译后是一个class文件大家都知道java类都继承自Object&#xff0c;但是接口其实是并没有继承Object类的 可以自己写代码测试: 获取接口类的class对象后遍历它的methods&#xff0c;可以发现是不存在Obje…

Java EE|TCP/IP协议栈之网络层IP协议详解

文章目录一、IP协议感性认知简介特点二、IP协议报头结构4位版本4位首部长度8位服务类型16位总长度16位标识8位生存时间&#xff08;TTL&#xff09;8位协议16位首部长度32位源ip&32位目的地址三、网络地址管理网段划分路由选择参考一、IP协议感性认知 简介 P是整个TCP/IP…

【云原生】k8s的pod基本概念

一、资源限制 Pod 是 kubernetes 中最小的资源管理组件&#xff0c;Pod 也是最小化运行容器化应用的资源对象。一个 Pod 代表着集群中运行的一个进程。kubernetes 中其他大多数组件都是围绕着 Pod 来进行支撑和扩展 Pod 功能的&#xff0c;例如用于管理 Pod 运行的 StatefulSe…

小说网站测试

目录 通用测试点 登录页面测试 接口测试 UI测试 注册页面 接口自动化 UI测试 忘记密码页面 接口测试 UI测试 修改密码页面 进行接口测试 UI测试 主页页面测试 分类页面测试 ​查询页面测试 作者页面测试 阅读小说页面测试 书架页面测试 通用测试点 登录页面测试 接…

k8s-资源限制-探针检查

文章目录一、资源限制1、资源限制的使用2、reuqest资源&#xff08;请求&#xff09;和limit资源&#xff08;约束&#xff09;3、Pod和容器的资源请求和限制4、官方文档示例5、资源限制实操5.1 编写yaml资源配置清单5.2 释放内存&#xff08;node节点&#xff0c;以node01为例…

计算机网络题库---错题本

&#xff08;一&#xff09;老生常谈 第一章&#xff1a; 1.什么是计算机网络&#xff1f;其主要功能是什么&#xff1f; 解答&#xff1a; 利用通信设备和线路&#xff0c;将分布在地理位置不同的、功能独立的多个计算机系统连接起来&#xff0c;以功能完善的网络软件实现网…

ChatGPT 开发人员教程 - 38种提高工作效率10倍的方法

未来的时代&#xff0c;又将是一个“洋枪洋炮”对“大刀长矛”的时代。在过去的十年里&#xff0c;传统行业在和经过IT改造的行业竞争时&#xff0c;无一例外地败北。08年金融危机前&#xff0c;全世界市值前十的公司&#xff0c;只有微软一家是IT企业。仅仅过去了十年&#xf…

文献阅读:Training language models to follow instructions with human feedback

文献阅读&#xff1a;Training language models to follow instructions with human feedback 1. 文献工作简介2. 模型优化设计3. 实验结果4. 总结 & 思考 文献链接&#xff1a;https://arxiv.org/abs/2203.02155 1. 文献工作简介 这篇文章是OpenAI在上年提出的一篇对于…

Go项目(商品微服务-1)

文章目录简介建表protohandler商品小结简介 商品微服务主要在于表的设计&#xff0c;建哪些表&#xff1f;表之间的关系是怎样的&#xff1f; 主要代码就是 CURD表和字段的设计是一个比较有挑战性的工作&#xff0c;比较难说清楚&#xff0c;也需要经验的积累&#xff0c;这里…

【Linux】工具(2)——vim

本期博客我们进入到Linux环境下vim工具的学习&#xff1a;一、vim是什么&#x1f4cc;Vim是一个超级超级强大的文本编辑器。Vim及前身VI&#xff0c;历史悠久&#xff08;可能比多数读者的年龄更大&#xff09;&#xff0c;经历了几十年的考验和发展。Vim全称叫Vi IMproved. 而…

Linux安装云原生网关Kong/KongA

目录1 概述2 创建服务器3 安装postgres4 安装kong5 安装node6 安装KONGA1 概述 Kong Kong是一款基于OpenResty&#xff08;NginxLua模块&#xff09;编写的高可用、易扩展的开源API网关&#xff0c;专为云原生和云混合架构而建&#xff0c;并针对微服务和分布式架构进行了特别…

蓝桥杯算法模板

模拟散列表拉链法import java.io.*; import java.util.*; public class a1 {static int n;static int N100003;static int[] hnew int[N];static int[] enew int[N];static int[] nenew int[N]; static int idx; static void insert(int x){int k(x%NN)%N;e[idx]x;ne[idx]h[k];…

终端软件架构说

目录 零&#xff1a;前言 一&#xff0c;基于服务的架构 二&#xff0c;基于多进程多线程的架构 三&#xff0c;以数据为中心的架构 四&#xff0c;类Android的分层架构设计 五&#xff0c;总结 零&#xff1a;前言 谈到架构&#xff0c;可能大家的第一感觉是信息系统的…

2023年三月份图形化三级打卡试题

活动时间 从2023年3月1日至3月21日&#xff0c;每天一道编程题。 本次打卡的规则如下&#xff1a; 小朋友每天利用10~15分钟做一道编程题&#xff0c;遇到问题就来群内讨论&#xff0c;我来给大家答疑。 小朋友做完题目后&#xff0c;截图到朋友圈打卡并把打卡的截图发到活动群…

Vue 3第三章:模板语法及指令介绍

文章目录1. 插值表达式1.1. 声明变量可直接在模板中使用&#xff0c;采用{{变量名称}}的方式1.2. 模板语法支持三元表达式1.3. 模板语法支持运算1.4. 模板语法支持方法调用2. 指令2.1. v-bind&#xff1a;用于绑定属性或动态绑定对象的值到元素上。2.2. v-if、v-else-if、v-els…

C#学习记录——接口的实现

一小部分知识精英依旧直面核心困难&#xff0c;努力地进行深度钻研&#xff0c;生产内容&#xff1b;而大多数信息受众始终在享受轻度学习&#xff0c;消费内容。如果我们真的希望在时代潮流中占据一席之地&#xff0c;那就应该尽早抛弃轻松学习的幻想&#xff0c;锤炼深度学习…

Burp Suite 常用模块简介

Burp Suite 常用模块分为 目标站点(target)模块 代理(proxy)模块 攻击(Intruder)模块 重放(Repeater) 模块 Target模块是对站点资源的收集&#xff0c;与站点各资源包发出和相应包的记录 Proxy模块是核心模块&#xff0c;可以拦截数据包发送往浏览器&#xff0c;进行修改后再…

网络协议分析(2)判断两个ip数据包是不是同一个数据包分片

一个节点收到两个IP包的首部如下&#xff1a;&#xff08;1&#xff09;45 00 05 dc 18 56 20 00 40 01 bb 12 c0 a8 00 01 c0 a8 00 67&#xff08;2&#xff09;45 00 00 15 18 56 00 b9 49 01 e0 20 c0 a8 00 01 c0 a8 00 67分析并判断这两个IP包是不是同一个数据报的分片&a…

Android JetPack之启动优化StartUp初始化组件的详解和使用

一、背景 先看一下Android系统架构图 在Android设备中&#xff0c;设备先通电&#xff08;PowerManager&#xff09;&#xff0c;然后加载内核层&#xff0c;内核走完&#xff0c;开始检查硬件&#xff0c;以及为硬件提供的公开接口&#xff0c;然后进入到库的加载。库挂载后开…