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

2018-10-19 06:29:45来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

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反复跟踪,不断琢磨。

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Java多线程——之一创建线程的四种方法

下一篇:Loading class `com.mysql.jdbc.Driver&#39;. This is depre