文章目录
- 一、什么是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
- 获取当前线程的类加载器
- 然后调用重载方法,把刚刚获得类加载器也传进去,在重载方法中会new一个ServiceLoader的实例。
2.1.4.2、ServiceLoader构造方法
- 对加载的class类型做判空
- 判断有没有指定类加载器,如果没有指定则使用系统类加载器
- 初始化访问控制器
- 调用reload方法重新加载
2.1.4.3、ServiceLoader#reload
- 这里首先会清空实例化好的缓存
- 然后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
- 这里会使用classLoader加载META-INF/services/com.alibaba.dubbo.demo.spi.Person文件中的内容
- 然后读取出来文件中的内容,然后调用parse方法解析配置文件中的内容并赋值给pending迭代器。
2.1.4.7、LazyIterator#parse
- 这里就会遍历文件中的内容,然后解析出来每一行,就是对应的实现类的权限定类名然后收集起来返回。
2.1.4.7、LazyIterator#parse
1.这里就是会解析每一行,然后过滤注释,检查内容合法性,还有根据缓存过滤避免重复加载。
2.1.4.7、LazyIterator#next
- 上面我们在hasNext方法中已经看到了他会去读取配置文件并缓存接口的实现类的权限定类名,然后这些实现类什么时候进行实例化的,我们看下next方法,这里会直接调用nextService方法
- 获取下一个接口实现类的权限定类名
- 然后通过反射创建出来实现类,然后转成接口类型
- 然后存入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、编写接口和实现类
- 这里需要在接口上面加上@SPI注解
2.1.2、编写配置文件
- Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下
- 与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。
2.1.3、通过Dubbo SPI 加载实现类
- 这里我们可以通过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使用示例
- 这里在接口上通过@SPI指定了默认实现为wheelMaker1,并且通过在方法上标注@Adaptive注解,可以动态从url中解析出来key对应的值并找到起对应的实现类。
这里在配置文件中指定WheelMaker的三个实现。
这里我们构造了一个url,并指定了一些key,然后调用ExtensionLoader#getAdaptiveExtension方法获得自适应扩展实现,通过测试案例我们可以发现方法调用的都是不同的实现:
- 这里makeWheelA方法上面标注了@Adaptive注解,这里value属性为空,则会从url中查找wheel.maker对应的value,这里wheel.maker是接口WheelMark的转换,将驼峰处分开并转换成小写,以"."连接起来,然后就找到实现类wheelMaker2了
- 这里makeWheelB方法上标注了 @Adaptive(“key4”),这里指定了key4,但是我们的url中没有传key4,这里会匹配不到,然后他就会走到我们的在SPI注解中指定的默认实现wheelMaker1
- 这里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使用示例
- 这里下面这个接口会有多个实现,分别有默认实现,多个组,排序,从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
- 首先坐下校验,判断类型是否为空,判断是否是个接口类,判断接口上面是否标注了@SPI注解
- 从缓存中找之前有没有创建过同样类型的ExtensionLoader,有就从缓存中获取,没有则new一个出来返回。
2.3.2、ExtensionLoader#getExtension
- 方法是获取某个类型具体的实现类,这里首先从缓存中获取,如果缓存中没有则会new一个holderput到缓存中
- 这里会对holder对象进行加锁,防止相同实例重复创建
- 调用createExtension创建对应的实现类并设置到holder对象中
2.3.3、ExtensionLoader#createExtension
- 这里首先调用getExtensionClasses方法获取接口对应的所有的扩展实现类,然后根据名称获取对应的扩展实现类。
- 然后判断扩展实例缓存中有没有这个实现类的对象,如果有说明之前已经创建过这个类的实例了,如果没有,则会通过反射创建这个类的实例
- 调用injectExtension方法向创建出来的对象注入依赖的属性
- 对依赖注入后的实例进行AOP(Wrapper),把当前接口类的所有的Wrapper全部⼀层⼀层包裹在实例对象上,每包裹个Wrapper后,也会对Wrapper对象进行依赖注入。
- 返回最终的Wrapper对象。
2.3.3.1、ExtensionLoader#getExtensionClasses
- 这里首先从缓存中获取当前接口的所有扩展点实现类,如果没有,那么需要解析配置文件进行加载
2.3.3.1.1、ExtensionLoader#loadExtensionClasses
- 首先解析接口上的SPI注解,然后拿到SPI注解配置的默认实现类并缓存起来
- 调用loadDirectory从指定的3个配置路径中查找扩展配置文件,并放入extensionClasses中,路径分别为:
- /META-INF/dubbo/internal/
- / META-INF/dubbo/
- /META-INF/services/ 这个主要是兼容jdk的spi
- 返回加载出来的扩展实现类extensionClasses
2.3.3.1.2、ExtensionLoader#loadDirectory
- 这里根据目录前缀和当前类型的权限定名拼接出来一个文件名,例:META-INF/dubbo/com.test.Robot
- 获取当前ExtensionLoader对应的classLoader
- 使用类加载器加载文件获取到对应的资源
- 调用loadResource解析资源获取到对应的实现类信息
2.3.3.1.3、ExtensionLoader#loadResource
- 这里就是对文件资源的解析,会一行一行读,然后过滤掉注释内容,然后解析处实现类的名字和权限定类名
- 调用loadClass加载实现类
2.3.3.1.4、ExtensionLoader#loadResource
- 首先判断实现类是否是接口的子类,不是的话抛出异常
- 判断实现类上面有没有@Adaptive注解,如果有的话再判断cachedAdaptiveClass是否是null,这个cachedAdaptiveClass就是缓存带有@Adaptive注解的实现类,没有的话就赋值给cachedAdaptiveClass,有的话就是不是同一个实现类class,不是的话就抛出异常,这里主要是为了一个扩展点接口只能有一个@Adaptive 放到类上面的实现类。
- 判断是不是包装类,这里就是根据这个实现类有没有一个构造器是传入当前类型的,如果是那么就说明是包装类,就缓存到cachedWrapperClasses中
- 判断name是否为空,如果是空的话,就调用findAnnotationName获取name,这里主要是兼容jdk的spi,因为dubbo的spi是 名称=实现类的权限定名,jdk直接是实现类的权限定名,这里是兼容一下。
- 如果有name的话,再从实现类上获取@Activate注解,如果上面有标注这个注解通过cachedActivates缓存起来
- 接着就是遍历分割完的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
上面已经解析配置文件获取对应扩展名的扩展实现类并创建出对应的实例,然后会掉这个方法对创建出来的实例的属性进行依赖注入
- 遍历实现类的所有方法,然后判断如果方法是set方法,并且只有一个参数,方法类型是public这样才能帮你自动注入,如果方法上标注了@DisableInject也不进行自动注入
- 获取参数的类型,并截取set方法中除了set的剩余字符串
- 调用objectFactory#getExtension获取指定类型的实现,这里objectFactory一般是AdaptiveExtensionFactory,这样获取到具体的实现并通过反射设置进去。
2.3.3.2.1、AdaptiveExtensionFactory#getExtension
- 根据扩展点类型获取具体实现类,这里也是根据ExtensionLoader获取支持的扩展实现,然后遍历支持的ExtensionFactory,获取对应的实现类
- 这里ExtensionFactory有两个具体实现,一个是SpiExtensionFactory,一个是SpringExtensionFactory
2.3.3.2.2、SpiExtensionFactory#getExtension
- 这里首先判断要注入的class是否类上面标注了SPI注解,如果标注了,则获取对应类型的ExtensionLoader
- 调用ExtensionLoader#getAdaptiveExtension获取适配器实现并返回
2.3.3.2.3、SpiExtensionFactory#getExtension
- 这里首先就是根据name去spring容器中获取对应的bean返回
- 如果根据name获取不到bean,则再根据对应的class类型去获取bean返回
2.3.4、ExtensionLoader#getAdaptiveExtension
- 在之前我们的示例中我们会调用getAdaptiveExtension方法获取自适应的扩展实现,方法逻辑大致如下:
- 先从缓存中获取自适应的实例,如果缓存中没有,则会调用createAdaptiveExtension方法创建一个自适应实例并返回。
2.3.4.1、ExtensionLoader#createAdaptiveExtension
- 这里首先调用getAdaptiveExtensionClass获取自适应扩展实现的class
- 然后通过反射创建实例
- 调用injectExtension方法对这个创建出来的实例进行依赖注入
2.3.4.2、ExtensionLoader#getAdaptiveExtensionClass
- 首先是获取所有的扩展实现类class,之前咱们分析过,会将实现类上带有@Adaptive注解的缓存到cachedAdaptiveClass 这个成员中。
- 那如果没有实现类上带有@Adaptive注解的时候,就要通过createAdaptiveExtensionClass来创建自适应扩展实现类了。
2.3.4.3、ExtensionLoader#createAdaptiveExtensionClass
- 首先是通过createAdaptiveExtensionClassCode方法来拼装成自适应实现类的代码
- 接着就是使用spi获取编译器,然后进行编译,加载,返回自适应实现类的class对象。
2.3.4.3、ExtensionLoader#createAdaptiveExtensionClassCode
- 首先遍历接口的所有方法,判断上面有没有标注@Adaptive注解,一个没有就抛出异常,只要有一个方法上有注解就继续执行
- 拼装package,import,然后就是类名(接口名+$Adaptive),然后实现一下接口
- 遍历方法,判断方法上是否有@Adaptive注解,如果没有将抛出异常的代码追加到code上,如果有@Adaptive注解,找出URL参数在参数列表的位置,如果有URL参数就拼装成判断是否是null,如果是null就抛出异常,然后设置到本地变量。
- 如果没有URL,就遍历参数类型里面有get方法并且get方法不是静态没有参数返回值是URL类型的,没有找到这么个条件的参数的时候,就会抛出异常 ,如果找到判断这个参数不是null,参数获取的URL不是null,然后URL设置到本地变量url中。
- 接着获取@Adaptive 注解值,如果没有值,就那class 名字来
如果参数有Invocation类型的,然后生成判断null的代码与String methodName = arg0.getMethodName(),并设置标志hasInvocation为true.- 接着就是遍历@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方法获取自动激活的扩展实现,方法逻辑大致如下:
- 首先将values放到names这个list中,然后判断如果names中没有-default就继续执行,这里-default是用户自己设置,表示不使用这些配置的key
- 调用getExtensionClasses加载扩展点的所有实现类,这个之前剖析过
- 然后遍历cachedActivates这个map,这个是我们在执行getExtensionClasses的时候缓存的,根据注解中的配置,判断group是否一致,如果是这个group,再判断names中有没有包含这个names,然后判断names中有没有配置-name,-name表示不激活该扩展实现,再然后就是判断注解中的value在url中有对应参数,同时满足这些条件说明这个扩展实现是满足的,添加到exts集合中
- 对exts集合进行排序
- 最后再遍历一下names,如果name没有-开头或者是names中没有-xxx ,然后就通过name获取到对应的扩展点实现,添加到usrs这个list中,最后判断usrs这个list不是空的话,就将usrs元素扔到exts中,然后return exts。