当前位置: 首页 > news >正文

Dubbo源码解析-——SPI机制

文章目录

  • 一、什么是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;//查找参数列表中有没有URLfor (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 checkString 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 typeelse { //没有url参数的String attribMethod = null;// find URL getter methodLBL_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是否是nullString 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 keyif (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 checkString 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);elsegetNameCode =String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);elsegetNameCode =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);elsegetNameCode = String.format("url.getParameter(\"%s\")", value[i]);elsegetNameCode = "url.getProtocol()";}} else {if (!"protocol".equals(value[i]))if (hasInvocation)getNameCode =String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);elsegetNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);elsegetNameCode =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 statementif (!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。

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

相关文章:

Dubbo源码解析-——SPI机制

文章目录一、什么是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#load2.1.4.2、ServiceLoader构造方法2.1.4.3、ServiceLoader#reload2.1.4.4、LazyI…...

赛后补题:CF1789C Serval and Toxel‘s Arrays

传送门:CF 题目描述: 题目较长,此处省略 输入: 3 3 2 1 2 3 1 4 2 5 1 1 1 1 1 10 10 4 6 9 12 16 20 2 10 19 7 1 3 5 4 2 17 2 18 6 11 7 1 8 17 5 5 5 5 2 2 输出: 13 1 705比赛的时候感觉已经想到了正解,但是没有想的很清楚,所以赛时没有打出来. 我认为这道题的突破口其…...

Linux学习(8.7)命令与文件的搜寻

目录 命令与文件的搜寻 which 文件档名的搜寻&#xff1a; whereis (寻找特定文件) locate find 以下内容转载自鸟哥的Linux私房菜 命令与文件的搜寻 which 这个命令是根据『PATH』这个环境变量所规范的路径&#xff0c;去搜寻『运行档』的档名&#xff5e; 所以&am…...

Linux下 Makefile文件基本语法二

本文继续上一篇关于 Makefile 文件内容的介绍。上一篇文章如下&#xff1a; Linux下 Makefile 基本语法_凌雪舞的博客-CSDN博客 一. Makefile 上一篇文章介绍了 Makefile基本语法中的变量&#xff0c;模式规则&#xff0c;自动化变量。这里继续介绍 Makefile 的另外一些语…...

【前端】JavaScript构造函数

文章目录概念执行过程返回值原型与constructor继承方式原型链其他继承方式&#xff08;还没写&#xff09;参考概念 在JS中&#xff0c;通过new来实例化对象的函数叫构造函数。实例化对象&#xff0c;也就是初始化一个实例对象。构造函数一般首字母大写。 构造函数的目的&…...

STM32单片机之温湿度检测系统(DTH11、OLED、LCD1602)

LCD1602LCD1602引脚第 1 脚: VSS 为电源地 第 2 脚: VDD 接 5V 正电源 第 3 脚: VL 为液晶显示器对比度调整端,接正电源时对比度最弱&#xff0c;接地时对比度最高&#xff0c;对比度过高时会产生“鬼影”&#xff0c;使用时可以通过一个 10K 的电位器调整对比度。 第 4 脚&…...

vitepress 就这几步操作,博客就搭好啦?

Ⅰ、什么是vitepress &#x1f48e; vitepress 使用场景 简单的说 &#xff0c;只要 会用 markdown 语法&#xff0c;就能构建自己的 「博客、笔记、使用文档」等系统 &#xff1b; ✨ vitepress 优势 优势介绍傻瓜式操作只需要配置 菜单 和 对应的 markdown 就能实现博客、笔…...

【Python工具篇】Anaconda中安装python2和python3以及在pycharm中使用

背景&#xff1a;已经安装好anaconda、python3、pycharm&#xff0c;因为项目使用的是python2语法&#xff0c;所以需要在anaconda中安装python2&#xff0c;并在pycharm中使用&#xff0c;下面给出步骤。 1. 打开cmd或者是Anaconda Prompt。 下面是anaconda prompt. 2. 查…...

Android 网络框架——Retrofit源码精析

众所周知&#xff0c;Retrofit是OkHttp的封装&#xff0c;APP对网络交互部分的实现基本上都是RxJavaRetrofitOkHttp架构&#xff08;或协程RetrofitOkHttp&#xff09;&#xff0c;可以说&#xff0c;Retrofit已经广为人知。本文主要介绍Retrofit主线源码实现机制&#xff0c;及…...

分布式算法 - Snowflake算法

Snowflake&#xff0c;雪花算法是由Twitter开源的分布式ID生成算法&#xff0c;以划分命名空间的方式将 64-bit位分割成多个部分&#xff0c;每个部分代表不同的含义。这种就是将64位划分为不同的段&#xff0c;每段代表不同的涵义&#xff0c;基本就是时间戳、机器ID和序列数。…...

【java web篇】Maven的基本使用以及IDEA 配置Maven

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…...

【蓝桥集训】第七天并查集

作者&#xff1a;指针不指南吗 专栏&#xff1a;Acwing 蓝桥集训每日一题 &#x1f43e;或许会很慢&#xff0c;但是不可以停下来&#x1f43e; 文章目录1.亲戚2.合并集合3.连通块中点的数量有关并查集的知识学习可以移步至—— 【算法】——并查集1.亲戚 或许你并不知道&#…...

【Playwright】扑面而来的Playwright测试框架

在当今快节奏的开发环境中&#xff0c;测试是软件开发的重要组成部分。 Microsoft Playwright 是一种流行的测试自动化框架&#xff0c;允许开发人员为 Web 应用程序编写端到端测试。 Playwright 建立在 Puppeteer 之上&#xff0c;这是另一个流行的测试自动化框架。在这篇博文…...

React(三) ——新、旧生命周期

&#x1f9c1;个人主页&#xff1a;个人主页 ✌支持我 &#xff1a;点赞&#x1f44d;收藏&#x1f33c;关注&#x1f9e1; 文章目录⛳React生命周期&#x1f30b;初始化阶段&#x1f463;运行中阶段&#x1f3d3;销毁阶段&#x1f3eb;新生命周期的替代&#x1f69a;react中性…...

IT男的一次中年破局尝试--出书

一、转战外企 接上回《人到中年——IT男择业感悟》后&#xff0c;自己从大央企去了某知名外企。外企虽然最近几年的日子已经没有10年前的辉煌与滋润&#xff0c;但相对来说&#xff0c;还能勉强找到工作与生活的平衡点。 划重点&#xff0c;35岁上下的人换工作理由&#xf…...

Python 内置函数eval()

Python 内置函数eval() eval(expression, globalsNone, localsNone) 函数用来执行一个字符串表达式&#xff0c;并返回表达式的值。 expression: 字符串表达式。global: 可选&#xff0c;globals必须是一个字典。locals: 可选&#xff0c;locals可以是任何映射对象。 示例 &…...

【ArcGIS Pro二次开发】系列学习笔记,持续更新,记得收藏

一、前言 这个系列是本人的一个学习笔记。 作为一个ArcGIS Pro二次开发的初学者&#xff0c;最困扰的就是无从入手。网上关于ArcGIS Pro二次开发的中文资料极少&#xff0c;官方文档对于我这样的英文苦手又太不友好。 在搜索无果后&#xff0c;决定自已动手&#xff0c;从头…...

EasyRecovery16MAC苹果版本Photo最新版数据恢复软件

无论是在工作学习中&#xff0c;还是在生活中&#xff0c;Word、Excle等办公软件都是大家很常用的。我们在使用电脑的过程中&#xff0c;有时会因自己的误删或电脑故障&#xff0c;从而导致我们所写的文档丢失了。出现这样的大家不要着急&#xff0c;今天小编就给大家推荐一款可…...

Go的string与strings.Builder

Go的string与strings.Builder 文章目录Go的string与strings.Builder一、strings.Builder 的优势二、string类型的值三、与string相比&#xff0c;Builder的优势体现在拼接方面3.1 Builder的拼接&#xff0c;与Builder的自动扩容3.2 手动扩容3.3 Builder 的重用四、strings.Buil…...

8.Spring Security 权限控制

1.简介入门JavaEE和SpringMVC &#xff1a;Spring Security就是通过11个Fliter进行组合管理小Demouser实体类user.type字段&#xff0c;0普通用户&#xff0c;1超级管理员&#xff0c;2版主补全get set tostringimplement UserDetails&#xff0c;重写以下方法// true: 账号未过…...

curl / python+selenium爬取网页信息

Python爬取网页信息 需求: 持续爬取某嵌入式设备配置网页上的状态信息 shell脚本 简单快速, 不用装插件只能爬取静态内容 用curl命令返回整个网页的内容用grep命令抓取其中某些字段结合正则表达式可多样查找但对于动态内容, 比如对某嵌入式设备配置网页上的一条不断更新的信…...

晶体塑性有限元 Abaqus 三维泰森多边形(voronoi模型)插件 V7.0

1 上一版本完整功能介绍&#xff1a; Voronoi晶体插件-6.0版本[新功能介绍] 晶体塑性有限元 Abaqus 三维泰森多边形&#xff08;voronoi模型&#xff09;插件 V6.0 2 新增功能模块 7.0版本新增功能模块包括&#xff1a;柱状晶体模块和分层晶体模块。 2.1 二维柱状晶体模块 …...

CPython解释器性能分析与优化

原文来自微信公众号“编程语言Lab”&#xff1a;CPython 解释器性能分析与优化 搜索关注 “编程语言Lab”公众号&#xff08;HW-PLLab&#xff09;获取更多技术内容&#xff01; 欢迎加入 编程语言社区 SIG-元编程 参与交流讨论&#xff08;加入方式&#xff1a;添加文末小助手…...

Linux 进程:理解进程和pcb

目录一、进程的概念二、CPU分时机制三、并发与并行1.并发2.并行四、pcb的概念一、进程的概念 什么是进程&#xff1f; 进程就是进行中的程序&#xff0c;即运行中的应用程序。比如&#xff1a;电脑上打开的LOL、QQ…… 这些都是一个个的进程。 什么是应用程序&#xff1f; 应用…...

银行数字化转型导师坚鹏:招商银行数字化转型战略研究

招商银行数字化转型战略研究课程背景&#xff1a; 很多银行存在以下问题&#xff1a;不清楚如何制定银行数字化转型战略&#xff1f;不知道其它银行的数字化转型战略是如何演变的&#xff1f; 课程特色&#xff1a;用实战案例解读招商银行数字化转型战略。用独特视角解…...

java 一文讲透面向对象 (20万字博文)

目录 一、前言 二、面向对象程序设计介绍 1.oop三大特性 : 2.面向对象和面向过程的区别 : 3.面向对象思想特点 : 4.面向对象的程序开发 : 三、Java——类与对象 1.java中如何描述一个事物? 2.什么是类? 3.类的五大成员&#xff1a; 4.封装的前提——抽象 : 5.什么是对…...

使用file-selector-button美化原生文件上传

前言 你平时见到的上传文件是下面这样的? 还是下面这种美化过的button样式 还是下面这种复杂的上传组件。 <input type="file" >:只要指定的是type类型的input,打开浏览器就是上面第一种原生的浏览器默认的很丑的样式。 下面的两种是我从ElementUI截的图,…...

0402换元积分法-不定积分

文章目录1 第一类换元法1.1 定理11.2 例题1.2 常见凑微分形式1.2.1常见基本的导数公式的逆运算1.2.2被积函数含有三角函数2 第二类换元法2.1 定理22.2 常见第二换元代换方法2.2.1 三角代换-弦代换2.2.2 三角代换-切代换2.2.3 三角代换-割代换2.2.4 三角代换汇总2.2.5 倒代换2.2…...

信号类型(雷达)——脉冲雷达(三)

系列文章目录 《信号类型&#xff08;雷达通信&#xff09;》 《信号类型&#xff08;雷达&#xff09;——雷达波形认识&#xff08;一&#xff09;》 《信号类型&#xff08;雷达&#xff09;——连续波雷达&#xff08;二&#xff09;》 文章目录 前言 一、相参雷达 1…...

并查集(13张图解)--擒贼先擒王

目录 前言 故事 &#x1f33c;思路 &#x1f33c;总结 &#x1f33c;代码 &#x1f44a;观察过程代码 &#x1f44a;正确代码 &#x1f44a;细节代码 来自《啊哈算法》 前言 刚学了树在优先队列中的应用--堆的实现 那么树还有哪些神奇的用法呢&#xff1f;我们从一…...