欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

dubbo源码(章节二) -- 内核探索之SPI

程序员文章站 2022-06-22 12:21:23
dubbo为什么不采用jdk的spi? jdk标准的spi会一次性实例化扩展点的所有实现,如果有扩展实现初始化很耗时,或者有的扩展实现没有使用到也会被加载,会造成资源浪费。 dubbo增加了对扩展点的ioc和aop的支持,一个扩展点可以直接setter注入其他的扩展点。 dubbo spi的一些约定 ......

dubbo为什么不采用jdk的spi?

  • jdk标准的spi会一次性实例化扩展点的所有实现,如果有扩展实现初始化很耗时,或者有的扩展实现没有使用到也会被加载,会造成资源浪费。
  • dubbo增加了对扩展点的ioc和aop的支持,一个扩展点可以直接setter注入其他的扩展点。

 

dubbo spi的一些约定:

spi文件的存储路径:

meta-inf/dubbo/internal/com.xxx.protocol

其中com.xxx.protocal代表文件名,即接口的全路径名。

spi的文件格式定义为:

xxx=com.foo.xxxprotocol
yyy=com.foo.yyyprotocol

这么定义的原因是:如果扩展实现中的静态属性或方法引用了某些第三方库,而该库又依赖缺失的话,就会导致这个扩展实现初始化失败。在这种情况下,dubbo无法定位失败的扩展id,所以无法精确定位异常信息。

 

dubbo spi的目的:

获取一个扩展实现类的对象。

extensionloader<t> getextensionloader(class<t> type)

为了获取到扩展实现类的对象,需要先为该接口获取一个extensionloader,缓存起来,之后通过这个extensionloader获取到对应的extension。 

由extensionloader获取对应extension主要有两种方式:

/**
 * find the extension with the given name.
*/ getextension(string name)

通过给定的name获取对象。

/**
 * get activate extensions.
 */
getadaptiveextension()

获取一个扩展装饰类对象,dubbo的扩展接口有个规则,它所有的实现类中,要么有且仅有一个类被@adaptive标注,getadaptiveextension()返回的就是这个类对象,要么所有的实现类都没有@adaptive注解,此时,dubbo就动态创建一个代理类并由getadaptiveextension()返回。这块后面详细谈论。

 

下面先讨论extensionloader的获取

先从dubbo的第一行代码开始:

com.alibaba.dubbo.container.main.main(args);

从这里的main方法进入,就来到dubbo的main class,这里定义了一个静态初始化的变量loader,这是dubbo的第一个扩展点,我们从这里开始跟代码,

private static final extensionloader<container> loader = 
    extensionloader.getextensionloader(container.class);

进入getextensionloader内部,

 1 private static final concurrentmap<class<?>, extensionloader<?>> extension_loaders = 
    new concurrenthashmap<class<?>, extensionloader<?>>(); 2 3 public static <t> extensionloader<t> getextensionloader(class<t> type) { 4   ...... 5   extensionloader<t> loader = (extensionloader<t>) extension_loaders.get(type); 6   if (loader == null) { 7     extension_loaders.putifabsent(type, new extensionloader<t>(type)); 8     loader = (extensionloader<t>) extension_loaders.get(type); 9   } 10   return loader; 11 }

这个方法的入参为class<t> type,标志了将要获取到的extensionloader的扩展接口的类型,此时实际的传入参数为container.class。

同时大家注意,我们前面说过,为扩展接口创建extensionloader时,所创建的extensionloader会被缓存起来,所以我们这里看到一个concurrenthashmap被申明用做缓存。

上述代码显示缓存中查询为null时,会创建一个extensionloader<t>,我们继续跟踪new extensionloader<t>(type),从代码第7行进入,

1 private extensionloader(class<?> type) {
2   this.type = type;
3   objectfactory = (type == extensionfactory.class ? null : 
      extensionloader.getextensionloader(extensionfactory.class).
        getadaptiveextension()); 4 }

首先为扩展接口的类型type赋值,这里是container.class,大家注意一个细节,前面getextensionloader方法的入参是class<t>,而这里却是class<?>,由泛型变成了通配符,稍后解释原因。

第三行为objectfactory赋值,暂时先不管objectfactory的作用,我们先看主干逻辑,因为这里type是container.class,三元运算符进入后半部分,再次调用getextensionloader,并传入参数extensionfactory.class。

继续跟踪代码,在getextensionloader内部它又会去查询缓存,因为这里还是不存在extensionfactory.class的key,所以继续进入new extensionloader<t>(type)的逻辑,这次的传入参数是extensionfactory.class,所以objectfactory被赋值为null。

这里就可以看出来了,两次调用extensionloader的构造方法,入参分别为container.class和extensionfactory.class,所以构造方法的入参使用通配符,同时concurrenthashmap作为缓存,它的key也是class<?>。

ok,到目前为止,我们总结下调用链:

extensionloader.getextensionloader(container.class);
    -->this.type = type;
    -->objectfactory = extensionloader.getextensionloader(extensionfactory.class).
      getadaptiveextension(); -->extensionloader.getextensionloader(extensionfactory.class); -->this.type = type; -->objectfactory = null;

缓存中应该有两项记录了,

{
  "key": "container.class",
  "value": "......"
}, {   "key": "extensionfactory.class",   "value": "......"
}

总结下以上代码,每一个extensionloader都包含有两个属性type,objectfactory:

  • class<t> type,初始化时要得到的接口名。
  • extensionfactory objectfactory,初始化一个adaptiveextensionfactory。objectfactory的作用是:为dubbo的ioc提供所有对象,这个的实现原理还是相当复杂的,之后单开一篇来说。

ok,现在我们获取到了extensionloader,接下来就是获取extension了。

 

下面讨论getadaptiveextension()

回忆前面在创建extensionloader<container>时,要初始化它的objectfactory:

objectfactory = extensionloader.getextensionloader(extensionfactory.class)
  .getadaptiveextension();

这里就是先获取extensionloader<extensionfactory>,之后调用了getadaptiveextension()。所以我们跟踪这个方法:

1 public t getadaptiveextension() {
2   object instance = cachedadaptiveinstance.get();
3   if (instance == null) {
4     instance = createadaptiveextension(); 5     cachedadaptiveinstance.set(instance);
6   } 7   return (t) instance; 8 }

为了节约篇幅,非主干逻辑的代码这里做了省略,可以看到,这个方法主要是为了给变量cachedadaptiveinstance赋值。继续跟踪代码第四行:

1 private t createadaptiveextension() {
2   return injectextension((t) getadaptiveextensionclass().newinstance());
3 }

这里调用了getadaptiveextensionclass(),先获取扩展的类对象,再实例化出具体的扩展对象,我们进入getadaptiveextensionclass():

1 private class<?> getadaptiveextensionclass() {
2   getextensionclasses();
3   if (cachedadaptiveclass != null) {
4     return cachedadaptiveclass;
5   }
6   return cachedadaptiveclass = createadaptiveextensionclass();
7 }

这里涉及到一个全局变量cachedadaptiveclass,这个变量很重要,留意一下,稍后马上就会说到,这里我们先看getextensionclasses()做了什么:

private map<string, class<?>> getextensionclasses() {
  map<string, class<?>> classes = cachedclasses.get();
  if(classes == null){
    classes = loadextensionclasses();
    cachedclasses.set(classes);
  }
  return classes; }

省约了非主干逻辑,这里其实就是加载所有的extensionclasses,也就是该扩展接口type的所有实现类,继续跟进:

private static final string services_directory = "meta-inf/services/";
private static final string dubbo_directory = "meta-inf/dubbo/";
private static final string dubbo_internal_directory = dubbo_directory + "internal/";

private map<string, class<?>> loadextensionclasses() {
  ......
  map<string, class<?>> extensionclasses = new hashmap<string, class<?>>();
  loaddirectory(extensionclasses, dubbo_internal_directory);
  loaddirectory(extensionclasses, dubbo_directory);
  loaddirectory(extensionclasses, services_directory);
  return extensionclasses;
}

这里依次加载三个目录,由于我们知道spi的存储路径就是meta-inf/dubbo/internal/,所有我们这里暂时只关注第一个loaddirectory即可,

 1 private void loaddirectory(map<string, class<?>> extensionclasses, string dir) {
 2   string filename = dir + type.getname();
3   enumeration<java.net.url> urls; 4   classloader classloader = findclassloader();
5   urls = classloader.getresources(filename);
6   while (urls.hasmoreelements()) { 7     java.net.url resourceurl = urls.nextelement(); 8     loadresource(extensionclasses, classloader, resourceurl); 9   }
10 }

这里传入的dir就是dubbo spi的存储路径meta-inf/dubbo/internal/,type.getname()获得扩展接口的类路径,两者拼接就得到一个dubbo spi的完整路径,这里的话就是:meta-inf/dubbo/internal/com.alibaba.dubbo.common.extension.extensionfactory。

获取该路径下的所有url资源,逐个处理,我们跟踪方法loadresource(......),

 1 private void loadresource(......) {
2   ......
3   string line; 4   while ((line = reader.readline()) != null) {
5     int i = line.indexof('=');
6     string name = line.substring(0, i).trim();
7     line = line.substring(i+1).trim();
8     loadclass(......, class.forname(line, true, classloader), name);
9   }
10 }

这个类的作用就是通过传入的url,读取文件,并逐行处理,前面说过dubbo spi的文件格式为xxx=com.foo.xxx,所以这里解析字符串就可以拿到扩展实现类的类名及对应的类路径,下一步就是加载这些实现类了,再这之前我们不妨先看看extensionfactory的扩展实现都有哪些,手动打开meta-inf/dubbo/internal/com.alibaba.dubbo.common.extension.extensionfactory文件,查看里面的内容,

adaptive=com.alibaba.dubbo.common.extension.factory.adaptiveextensionfactory
spi=com.alibaba.dubbo.common.extension.factory.spiextensionfactory

我们看到有两个实现,分别是adaptiveextensionfactory、spiextensionfactory,下面就将分别加载它们,

 1 private void loadclass(......, class<?> clazz, string name) {
2   if (clazz.isannotationpresent(adaptive.class)) { 3     if (cachedadaptiveclass == null) { 4       cachedadaptiveclass = clazz; 5     } else if (!cachedadaptiveclass.equals(clazz)) { 6       throw new illegalstateexception("more than 1 adaptive class found: "......)
7     } 8   } else if (iswrapperclass(clazz)) { 9     set<class<?>> wrappers = cachedwrapperclasses; 10     if (wrappers == null) { 11       cachedwrapperclasses = new concurrenthashset<class<?>>(); 12       wrappers = cachedwrapperclasses; 13     } 14     wrappers.add(clazz); 15   } else { 16     clazz.getconstructor();
17     ......
18     cachedactivates.put(name, activate);
19     cachednames.put(clazz, name);
20     extensionclasses.put(name, clazz);
21   } 22 }
1 private boolean iswrapperclass(class<?> clazz) {
2   try {
3     clazz.getconstructor(type);
4     return true;
5   } catch (nosuchmethodexception e) {
6     return false;
7   }
8 }

程序第二行判断如果当前类拥有@adaotive注解,则为全局变量cachedadaptiveclass赋值,否则,程序第八行判断如果当前类不包含@adpative注解,且当前类的构造器方法包含目标接口(type)类型,则当前类被加入cachedwrapperclasses缓存,否则,如果当前类即不被@adaptive注解,也没有type类型的构造器,它最终会被加入到extensionclasses中,extensionclasses最终赋值给了cachedclasses。

注意代码第五行,cachedadaptiveclass是class<?>类型的变量,也就是说,只能是一个值,那么当我们逐行加载spi配置文件里的类时,如果有两个类都标注了@adaptive注解呢?第二个被标注的类会直接抛异常,因为此时cachedadaptiveclass已经被赋值了第一个类的类型,它当然不会equals第二个类了,由此就证明了我们开篇提到的dubbo spi的规则,它的所有实现类中,最多有一个被@adaptive注解。

ok,到了这里,加载扩展classes的过程就结束了,我们回到getextensionclasses()的调用处,在方法getadaptiveextensionclass()中,完成了classes的加载之后,接下来就判断全局变量cachedadaptiveclass的值,如果不为null,则表明该变量在上述类加载过程中被赋值了,我们前面描述了,这个值就只能是一个带有@adaptive注解的扩展类。如果该变量仍然为null,则表明这个扩展接口没有一个实现类带有@adaptive注解,大家回忆一下,前面我们说过dubbo spi的规则,它要么只有一个实现类被@adaptive注解,要么就没有,如果它没有一个实现类被@adaptive注解,那么就动态创建一个代理类,这句话就体现在这里了,显而易见,方法createadaptiveextensionclass()就将被用来完成这件事。

1 private class<?> createadaptiveextensionclass() {
2   string code = createadaptiveextensionclasscode();
3   classloader classloader = findclassloader();
4   compiler compiler = extensionloader.getextensionloader(compiler.class)
5     .getadaptiveextension(); 6   return compiler.compile(code, classloader); 7 }

这个过程分四步来做,首先在程序第二行,将动态生成一个代理类,这个的实现代码非常繁琐,这里不贴出来了,其原理就是通过一个adpative类的模板来生成代理类,我总结下模板给大家参考,

package <扩展点接口所在包>

public class <扩展点接口名>$adaptive implements <扩展点接口>{   public <有@adaptive注解的接口方法>(参数){
    //这里生成代理对象,执行方法
  } }

也就是说,代理类只会生成接口中被@adaptive注解了的方法,如果试图在代理类中调用接口中没有标注@adaptive的方法,程序会抛出异常。

因为这里当前的type是extensionfactory,这个接口是拥有一个被@adaptive注解了的类的,所以我们debug到这里会直接获得adaptiveextensionfactory,不会触发动态生成代理类的过程,为了了解动态代理类到底长什么样子,这里我给大家举个例子,protocol是dubbo的一个spi接口,它的所有实现类没有一个被@adaptive注解,所以如果我们执行下面这句代码,

extensionloader.getextensionloader(protocol.class).getadaptiveextension();

它在通过spi配置文件的类加载中得不到一个adaptive的实现类,所以代码最终会进入createadaptiveextensionclass()方法中,我们通过debug拿到string code的值,如下,

 1 package com.alibaba.dubbo.rpc;
 2 import com.alibaba.dubbo.common.extension.extensionloader;
3 4 public class protocol$adaptive implements com.alibaba.dubbo.rpc.protocol { 5   public void destroy() {
6     throw new exception("method destroy() of protocol is not adaptive method!"); 7   }
8 9   public int getdefaultport() {
10     throw new exception("... not adaptive method!"); 11   }
12 13   public exporter export(invoker arg0) throws rpcexception { 
14     ......
15     protocol extension = (protocol)extensionloader.getextensionloader(protocol.class)
16       .getextension(extname); 17     return extension.export(arg0); 18   }
19 20   public invoker refer(class arg0, url arg1) throws rpcexception { 21     ......
22     protocol extension = (protocol)extensionloader.getextensionloader(protocol.class)
23       .getextension(extname); 24     return extension.refer(arg0, arg1); 25   } 26 }

我们对比protocol接口的定义来看,

 1 @spi("dubbo")
 2 public interface protocol {
 3 
 4     int getdefaultport();
 5 
 6     @adaptive
 7     <t> exporter<t> export(invoker<t> invoker) throws rpcexception;
 8     
 9     @adaptive
10     <t> invoker<t> refer(class<t> type, url url) throws rpcexception;
11 
12     void destroy();
13 }

可以看到,方法export和refer被标注了@adaptive注解,所以在代理类中生成了代理方法,为什么说是代理方法呢,因为在方法内部又生成了一个protocol对象,通过这个对象完成了方法调用,对象extension就是一个不折不扣的代理对象。另外,在接口中方法getdefaultport()和方法destroy()没有被标注@adaptive注解,所以在代理类中它们没有被实现。

生成了代理类之后,下一步,就是动态编译这个代理类,关于动态编译的内容,之后单独开一篇来讨论。另外这里从代码中可以看到,compiler也是由spi获得的,实际上,dubbo所有扩展对象的获取都是通过spi完成的,故而我们也说spi是dubbo内核的灵魂之所在。

ok,不论是通过配置文件加载,还是通过动态编译生成代理类,到这里为止,getadaptiveextensionclass()方法就执行完了,我们终于获得了扩展类的类对象,但这还不是扩展对象,继续回到方法getadaptiveextensionclass()的调用处,

1 private t createadaptiveextension() {
2   return injectextension((t) getadaptiveextensionclass().newinstance());
3 }

这里我们拿刚刚返回的类对象new了一个instance出来,之后就作为参数被传入injectextension()方法中了,这个方法会进入dubbo的ioc,控制反转,实现扩展对象的动态注入,ioc的相关内容下一篇文章再做讨论。

dubbo的adaptive spi,主要逻辑到这里就梳理完成了,spi是dubbo内核的灵魂,同时它的实现原理也是颇复杂的,这里提到的几段代码需要结合debug反复跟踪,不断琢磨。