二 捋代码--dubbo源码学习扩展机制spi

可能有的同学要问了,分析dubbo源码为啥都要从spi开始。其实答案很简单----》你不明白这个,你看不懂dubbo的调用流程啊!dubbo里大量使用的适配器扩展动态生成适配器类,你看不懂这个,你跟不不知道dubbo的流程时怎么走的(有点吹啊,你能懂算你🐂!)

一 java的spi机制

说白了,就是你需要在META-INF/services/这个目录下写个配置文件,配置文件名字是你要扩展的接口名,文件内容就是此接口的实现类全名,可以写多个,然后呢jdk帮你写了个java.util.ServiceLoader类通过反射机制找到这个文件,实例化文件里的所有实现类(具体内容大家自己查查就知道了)。

如文件名:

spi文件

文件内容:

spi内容

我用的时候:

我的测试代码

就这么简单,但明显这中模式有一些问题,啥问题?
1.每次加载会把所有类加载,有用没用都给我加载了
2.我如果想找某一个类,需要遍历所有找到匹配的那个很麻烦。

等等。。。。

然后dubbo就自己基于此原理开发了一套spi扩展机制

1 先总体看他的文件是这样子的:

dubbo的相关spi配置文件

文件内容是这样子的:

dubbo中spi文件内容

我们发现他只是在文件内容中多了个名字,想必如果让我们做,这个名字应该是来做缓存的(其实他也是干这个用的)

2 让我们看看dubbo是怎么写的:

他的入口类为ExtensionLoader,我们先看看他都有哪些属性:

    //配置文件位置1,沿用java的目录
    private static final String SERVICES_DIRECTORY = "META-INF/services/";

    //配置文件位置
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    //配置文件位置2,dubbo自定义的目录
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");

    //用来缓存每个类对应的ExtensionLoader加载器
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();

    // ==============================
    //用来缓存此加载器对应的类型
    private final Class<?> type;

    //缓存对应ExtensionFactory工厂,后续注入逻辑会用到
    private final ExtensionFactory objectFactory;
    //缓存类型对配置文件中名字对映射关系
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

    //缓存名字对应类型,和cachedNames相呼应,想成双向
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

    //缓存配置文件中name对应对实例中哪些是Activate对
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();

    //缓存已加载对name对应对实例
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
    
    //缓存生成自适应实例
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
    
    //缓存对应对自适应类
    private volatile Class<?> cachedAdaptiveClass = null;
    
    //缓存对应对default名字
    private String cachedDefaultName;
    
    //存对应建设自适应类时产生错误
    private volatile Throwable createAdaptiveInstanceError;

    //缓存配置文件中对应类对所有时wrapper对类型
    private Set<Class<?>> cachedWrapperClasses;

看完这些属性(请记住注解对内容),下面开始进入正题,我们会举例说话:

例 Protocol adaptiveExtension = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()

   可以看出代码意思是加载Protocol类型对所有实现,并找到对应Adaptive类型扩展点,dubbo对spi中有几个注解:

  • SPI:表示当前接口为扩展点,可以指定value,表示默认扩展点对应名
  • Adaptive:表示此类型扩展点的自适应对象,有方法和类两种层级定义(后面会详细说明)
  • Activate:表示此扩展点为自动激活型,dubbo会自动帮你加载

进入代码,按习惯先来张大图:


图片太大请打开原图放大查看

由图中我们可以看到第一步当没有时会new个ExtensionLoader,

   @SuppressWarnings("unchecked")
   public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        ....
       //一个type对应一个loader类,先从缓存中查看没有大化new个新的
       ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
       if (loader == null) {
           EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
           loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
       }
       return loader;
   }

注意他在new的时候还有点小动作,赋值了个objectFactory,这是干嘛用的呢?留下疑问🤔️,后面解答(当然大图里已有答案)。

//new 个ExtensionLoader
    private ExtensionLoader(Class<?> type) {
        this.type = type;
      //如果当前不是ExtensionFactory工厂则赋值
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExension());
    }

根据流转图,下面开始调用,缓存部分略过,然后我们重点进入createAdaptiveExtension();

   public T getAdaptiveExtension() {
        //类型自适应缓存实例
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //重点入口,创建自适应逻辑
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

明显createAdaptiveExtension();会生成我们最终要的实例,里边主要逻辑落到了这里:

 return injectExtension((T) getAdaptiveExtensionClass().newInstance());

根据流程图,我们首先分析getAdaptiveExtensionClass(),通过名字我们看是加载对应的自适应的class,缓存部分略过,流程图显示重点逻辑进入getExtensionClasses中然后核心逻辑分为两个分支,一个loadExtensionClasses表示配置文件中加载所有可用实例的class,另一个在没有找到自适应类的情况下通过createAdaptiveExtensionClass()方法的字节码技术生成一个自适应类(这是一个重点)。
先看loadExtensionClasses:

 // 此方法已经getExtensionClasses方法同步过。
  private Map<String, Class<?>> loadExtensionClasses() {
      final SPI defaultAnnotation = type.getAnnotation(SPI.class);
      if (defaultAnnotation != null) {
          String value = defaultAnnotation.value();
          if (value != null && (value = value.trim()).length() > 0) {
              String[] names = NAME_SEPARATOR.split(value);
              if (names.length > 1) {
                  throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                          + ": " + Arrays.toString(names));
              }
              //spi配置对应的默认名字
              if (names.length == 1) cachedDefaultName = names[0];
          }
      }

      Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
      //加载三个配置文件中的对应类的所有类
      loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
      loadFile(extensionClasses, DUBBO_DIRECTORY);
      loadFile(extensionClasses, SERVICES_DIRECTORY);
      return extensionClasses;
  }

再看loadFile明显是去加载上面说的配置文件了,但其中有几个小动作我们要注意下:

 private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
          ....
         //配置文件中的name和className
         name = line.substring(0, i).trim();
         line = line.substring(i + 1).trim();
          ....   
        Class<?> clazz = Class.forName(line, true, classLoader);
         .....
        if (clazz.isAnnotationPresent(Adaptive.class)) {
                      ....
                      //对应上面说的Adaptive注解的类级别,直接赋值
                       cachedAdaptiveClass = clazz;
         } else {
                   try {
                    //重点逻辑,如果此类为wrapper类,将在此缓存
                      clazz.getConstructor(type);
                          Set<Class<?>> wrappers = cachedWrapperClasses;
                             if (wrappers == null) {
                             cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                               wrappers.add(clazz);
                        } catch (NoSuchMethodException e) {
                        ....
                                            clazz.getConstructor();
                        ....
                        //不是wrapper类的缓存到扩张集合中,名字也缓存了
                                            cachedNames.put(clazz, n);
                        ....
                                            extensionClasses.put(n, clazz);
                        }
  
    }

上面的wrapper那段要注意,这段逻辑是形成filer模式的基础,我们回过看下配置文件就会发现这种对象:


wrapper实例

加载完配置文件中的所有class这个时候如果发现没有类级别的Adaptive注解,我们将会走到createAdaptiveExtensionClass这个重点分支(这也是dubbo扩展设计牛逼的一个地方):

   private Class<?> createAdaptiveExtensionClass() {
        //生成自适应类文件内容
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

明显他是通过java的Compiler功能动态生成了class,所以我们重点看的应该就是code是什么(有没了解Compiler机制的小朋友,可以自己学习下啊),我通过debug的方式获取此时的code如下:

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
  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!");
  }

  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!");
  }

  public com.alibaba.dubbo.rpc.Invoker refer(Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
      if (arg1 == null) throw new IllegalArgumentException("url == null");
      com.alibaba.dubbo.common.URL url = arg1;
      String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
      if (extName == null)
          throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
      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);
  }

  public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
      if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
      if (arg0.getUrl() == null)
          throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
      com.alibaba.dubbo.common.URL url = arg0.getUrl();
      //重点部分后续的适配工作全依靠此出逻辑
      String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
      if (extName == null)
          throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    //根据url中的属性来下适配下一个扩展点
      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);
  }
}

看这就是dubbo最终动态生成的自适应,后面我们将会用到,现在先不解释,但有个坑得在此出填一下,就是Adaptive刚才讲了类级别注解,那方法级别呢?对了,就是这时候用的,一言不合上代码:

   private String createAdaptiveExtensionClassCode() {
        ....
        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 {
                ....
            }
        }
        codeBuidler.append("\n}");
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuidler.toString());
        }
        return codeBuidler.toString();
    }

诶,我们发现dubbo实现的时候非自适应的被一个异常带过了,非自适应的我们看了边生成类可以看出,大同小异,都是根据url中的属性来下适配下一个扩展点,发布服务时我们会来揭晓他的使用。
至此我们需要的getAdaptiveExtension就获取到了class,下面根据大图流程就new对象,(T) getAdaptiveExtensionClass().newInstance(),这就没啥说的了,然后就是injectExtension()方法,这也是dubbo的spi一个重点方法,

private T injectExtension(T instance) {
      try {
          if (objectFactory != null) {
              for (Method method : instance.getClass().getMethods()) {
                  if (method.getName().startsWith("set")
                          && method.getParameterTypes().length == 1
                          && Modifier.isPublic(method.getModifiers())) {
                      Class<?> pt = method.getParameterTypes()[0];
                      try {
                          String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        //获取对应有set方法的属性从objectFactory中进行赋值
                          Object object = objectFactory.getExtension(pt, property);
                          if (object != null) {
                              method.invoke(instance, object);
                          }
                      } catch (Exception e) {
                          logger.error("fail to inject via method " + method.getName()
                                  + " of interface " + type.getName() + ": " + e.getMessage(), e);
                      }
                  }
              }
          }
      } catch (Exception e) {
          logger.error(e.getMessage(), e);
      }
      return instance;
  }

这段逻辑和spring中的依赖注入有异曲同工之妙,那dubbo又是如何spring挂钩的呢?那又回到了objectFactory的生成,上文已经说过objectFactory是通过
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExension())获取到的自适应类,先让我们看这个类什么样子,我们找到com.alibaba.dubbo.common.extension.ExtensionFactory对应的配置文件中有三个类,其中有个AdaptiveExtensionFactory为@Adaptive注解标注,所以此时我们获取到到是这个类:

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

  private final List<ExtensionFactory> factories;

  public AdaptiveExtensionFactory() {
      ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
      List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
      for (String name : loader.getSupportedExtensions()) {
          list.add(loader.getExtension(name));
      }
      factories = Collections.unmodifiableList(list);
  }
  //获取属性方法,从所有工厂获取
  public <T> T getExtension(Class<T> type, String name) {
      for (ExtensionFactory factory : factories) {
          T extension = factory.getExtension(type, name);
          if (extension != null) {
              return extension;
          }
      }
      return null;
  }

}

可以看出这个适配器类中其实是从另两个扩展工厂去找对象到,其中就有spring工厂


扩展工厂配置文件

好了,到这里加载自适应到相关主逻辑就看完了,让我们歇口气,看看下一个spi中到知识点

例 getExtension(String name)方法(这个就简单了):

public T getExtension(String name) {
      if (name == null || name.length() == 0)
          throw new IllegalArgumentException("Extension name == null");
      if ("true".equals(name)) {
          return getDefaultExtension();
      }
      Holder<Object> holder = cachedInstances.get(name);
      if (holder == null) {
          cachedInstances.putIfAbsent(name, new Holder<Object>());
          holder = cachedInstances.get(name);
      }
      Object instance = holder.get();
      if (instance == null) {
          synchronized (holder) {
              instance = holder.get();
              if (instance == null) {
                  //获取扩展点
                  instance = createExtension(name);
                  holder.set(instance);
              }
          }
      }
      return (T) instance;
  }

这里边重点createExtension(name)方法,可以看到上面加载class一样,重点是最后多了个包装多逻辑,这就是filer形成多源头,后面会有大用

 private T createExtension(String name) {
        //已经分析过的加载class逻辑
        Class<?> clazz = getExtensionClasses().get(name);
         .....
      //包装类包装逻辑
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
       ....
    }

   over!到此dubbo的spi实现大致分析完了,但你可能对其使用还是没有了解清楚,那就得看看下面分析但dubbo服务发布、引用和调用等等逻辑了,毕竟只有用过的东西才代表你会了。

下一篇   dubbo服务发布源码欣赏
首页     dubbo源码欣赏简

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容