dubbo SPI @Adaptive源码解读

àì夳堔傛蜴生んèń 2022-11-20 07:00 260阅读 0赞

接上文dubbo SPI @Adaptive注解使用方法与原理解析

目录

  • 概览
  • getAdaptiveExtension——入口
    • createAdaptiveExtension
      • 1getAdaptiveExtensionClass
        • 1.1getExtensionClasses
          • 1.1.1loadExtensionClasses
        • 1.2 createAdaptiveExtensionClass
      • 2injectExtension

概览

自适应类的用法示例

  1. SimpleExt simpleExt= ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();

接口代码示例

  1. @SPI("impl1")
  2. public interface SimpleExt {
  3. String echo(URL url,String s);
  4. @Adaptive({ "key4"})
  5. void printA(URL url);
  6. @Adaptive
  7. void printB(URL url);
  8. @Adaptive({ "key3","key2","key1"})
  9. void printC(URL url);
  10. }

getAdaptiveExtension方法的大致流程如下图(从左到右)
在这里插入图片描述

接下来对每个方法进行解读。

getAdaptiveExtension——入口

此方法是获取自适应类的入口

  1. public T getAdaptiveExtension() {
  2. //获得自适应扩展类实例的缓存
  3. Object instance = cachedAdaptiveInstance.get();
  4. //这里是使用了双重校验锁的单例模式
  5. if (instance == null) {
  6. if (createAdaptiveInstanceError != null) {
  7. throw new IllegalStateException("Failed to create adaptive instance: " +
  8. createAdaptiveInstanceError.toString(),
  9. createAdaptiveInstanceError);
  10. }
  11. synchronized (cachedAdaptiveInstance) {
  12. instance = cachedAdaptiveInstance.get();
  13. if (instance == null) {
  14. try {
  15. //无缓存则创建实例并缓存
  16. instance = createAdaptiveExtension();
  17. cachedAdaptiveInstance.set(instance);
  18. } catch (Throwable t) {
  19. createAdaptiveInstanceError = t;
  20. throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
  21. }
  22. }
  23. }
  24. }
  25. return (T) instance;
  26. }

createAdaptiveExtension

当前面的方法没有获取带自适应类实例的缓存时,会进入此方法。这个方法里一共做了三件事。

  1. getAdaptiveExtensionClass 先获取自适应类
  2. newInstance 再生成对应的实例
  3. injectExtension 然后为实例注入扩展类属性

    private T createAdaptiveExtension() {

    1. try {
    2. return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    3. } catch (Exception e) {
    4. throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    5. }
    6. }

1getAdaptiveExtensionClass

这里将分为两步骤

  1. getExtensionClasses加载扩展点文件。若实现类上有@Adaptive注解,会被缓存起来,只能存在一个自适应类的缓存,默认是不可覆盖的,并且后续的执行方法都是用的此实现类。
  2. createAdaptiveExtensionClass创建自适应类(仅在自适应类缓存为空时进入此方法)

    private Class<?> getAdaptiveExtensionClass() {

    1. //加载配置文件
    2. getExtensionClasses();
    3. //若自适应类有缓存则返回
    4. if (cachedAdaptiveClass != null) {
    5. return cachedAdaptiveClass;
    6. }
    7. //自适应类的缓存为空则创建
    8. return cachedAdaptiveClass = createAdaptiveExtensionClass();
    9. }

1.1getExtensionClasses

此方法是dubbo SPI的核心方法,获取扩展类都需要经过这步(包括getDefalultExtension,getAdaptiveExtension,getActivateExtension)。同样还是先希望从缓存取,不存在就去加载文件填充缓存。

  1. private Map<String, Class<?>> getExtensionClasses() {
  2. //取缓存
  3. Map<String, Class<?>> classes = cachedClasses.get();
  4. if (classes == null) {
  5. synchronized (cachedClasses) {
  6. classes = cachedClasses.get();
  7. if (classes == null) {
  8. //缓存无,则从文件加载扩展点
  9. classes = loadExtensionClasses();
  10. cachedClasses.set(classes);
  11. }
  12. }
  13. }
  14. return classes;
  15. }
1.1.1loadExtensionClasses

这里也分为两步骤

  1. cacheDefaultExtensionName获取扩展点名称并缓存。例如@SPI("impl1")则是将”impl1”缓存下来
  2. loadDirectory解析扩展点文件并缓存实现类。例如配置文件内容为impl1=com.example.demo.impl.SimpleExtImpl1,则会以key=“impl1” value= SimpleExtImpl1类的键值对 存在Map<String, Class<?>>中。除了进行普通扩展类的缓存,此方法还会额外做三种缓存(自适应类,包装扩展类,自动激活类)以加快后续方法的执行效率。

    private Map> loadExtensionClasses() {

    1. //获取扩展点名称并缓存
    2. cacheDefaultExtensionName();
    3. Map<String, Class<?>> extensionClasses = new HashMap<>();
    4. for (LoadingStrategy strategy : strategies) {
    5. //加载指定文件夹下的配置文件
    6. loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    7. //同时加载alibaba和apache的类
    8. loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    9. }
    10. return extensionClasses;
    11. }

1.2 createAdaptiveExtensionClass

此方法将生成一个动态的自适应类,它能根据参数决定执行方法的实现类,主要做了三件事。

  1. 生成类代码字符串。生成代码的逻辑主要分为7步。

    1.1 生成与扩展点相同位置的packageimport ExtensionLoader这个类。为了不写其他的import方法,其他调用都使用全路径名。同时设置类名为“接口名称+$Adaptive”的格式。例如SimpleExt接口会生成SimpleExt&Adaptive

    1.2 遍历接口的所有方法,获取方法的返回类型,参数类型,异常类型等。

    1.3 生成校验参数是否为空的代码。

    1.4 生成默认实现类名称。如果@Adaptive注解没有默认值,则会先根据此名称去寻找它的扩展实现类。如SimpleExt的默认类名称为simple.ext

    1.5 生成获取扩展点名称的代码。例如对于前面的示例接口SimpleExtprintC方法,会生成url.getParameter("key3", url.getParameter("key2",url.getParameter("key1","impl1")));

    1.6 生成获取具体实现类的代码。通过上一步获得的extName执行getExtension(extName) 方法获取实现类。

    1.7 生成用此实现类调用方法的代码。

  2. 获取编译器的自适应类。实现Compile接口的AdaptiveCompiler类上有@Adaptive注解,因此此类会作为编译器的默认实现。
  3. 使用编译器编译类

    private Class<?> createAdaptiveExtensionClass() {

    1. //生成自适应类的代码
    2. String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    3. ClassLoader classLoader = findClassLoader();
    4. //获取编译器类
    5. org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    6. //动态编译生成类
    7. return compiler.compile(code, classLoader);
    8. }

2injectExtension

这一部分是为了给新创建的扩展点实现类注入属性。只有当此属性也为扩展点(且该扩展点已被加载并生成实例)时才能注入。
主要逻辑是遍历该类的所有方法,查看setter方法的返回类型对应的类是否为扩展点,是的话将其通过反射调用此setter方法注入。

  1. private T injectExtension(T instance) {
  2. //此工厂中存储了已经实例化的扩展点,若工厂不为空,则查找工厂中是否拥有可注入的类
  3. if (objectFactory == null) {
  4. return instance;
  5. }
  6. try {
  7. //通过反射获取并遍历实现类的所有方法
  8. for (Method method : instance.getClass().getMethods()) {
  9. //只遍历以"set"命名开头的方法
  10. if (!isSetter(method)) {
  11. continue;
  12. }
  13. /** * Check {@link DisableInject} to see if we need auto injection for this property */
  14. if (method.getAnnotation(DisableInject.class) != null) {
  15. continue;
  16. }
  17. //获得该构造方法第一个参数类型
  18. Class<?> pt = method.getParameterTypes()[0];
  19. //若是基础类型则跳过,如 int,double,String等
  20. if (ReflectUtils.isPrimitives(pt)) {
  21. continue;
  22. }
  23. try {
  24. //获取该属性名,如"setVersion" => "version"
  25. String property = getSetterProperty(method);
  26. //从扩展类工厂中获取该属性的实例
  27. Object object = objectFactory.getExtension(pt, property);
  28. //如果成功获取,则将该扩展类实例注入进去
  29. if (object != null) {
  30. method.invoke(instance, object);
  31. }
  32. } catch (Exception e) {
  33. logger.error("Failed to inject via method " + method.getName()
  34. + " of interface " + type.getName() + ": " + e.getMessage(), e);
  35. }
  36. }
  37. } catch (Exception e) {
  38. logger.error(e.getMessage(), e);
  39. }
  40. return instance;
  41. }

整体的流程就到此结束了,代码多处是反射和缓存的应用,如有错误欢迎指出~

如果有疑问,欢迎评论~
如果成功解决了你的问题,点个赞再走吖~

发表评论

表情:
评论列表 (有 0 条评论,260人围观)

还没有评论,来说两句吧...

相关阅读

    相关 dubbo解析-SPI机制

     架构体系   框架介绍   概述   Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spri

    相关 dubbo解析-spi(五)

    Dubbo源码解析系列文章均来自肥朝简书 前言 之前对dubbo的`SPI`进行了四篇的分享.大家对这个概念有了一些初步的了解.谈到编程水平如何进阶,大家可能都会异口同