dubbo为什么不采用jdk的spi?
- jdk标准的spi会一次性实例化扩展点的所有实现,如果有扩展实现初始化很耗时,或者有的扩展实现没有使用到也会被加载,会造成资源浪费。
- dubbo增加了对扩展点的ioc和aop的支持,一个扩展点可以直接setter注入其他的扩展点。
dubbo spi的一些约定:
spi文件的存储路径:
-INF/dubbo/internal/com.xxx.Protocol
其中com.xxx.Protocal代表文件名,即接口的全路径名。
spi的文件格式定义为:
xxx=com.foo.XxxProtocol
yyy=com.foo.YyyProtocol
这么定义的原因是:如果扩展实现中的静态属性或方法引用了某些第三方库,而该库又依赖缺失的话,就会导致这个扩展实现初始化失败。在这种情况下,dubbo无法定位失败的扩展id,所以无法精确定位异常信息。
dubbo spi的目的:
获取一个扩展实现类的对象。
Extensi er<T> getExtensi er(Class<T> type)
为了获取到扩展实现类的对象,需要先为该接口获取一个extensi er,缓存起来,之后通过这个extensi er获取到对应的extension。
由extensi er获取对应extension主要有两种方式:
/** * Find the extension with the given name. */ getExtension(String name)
通过给定的name获取对象。
/** * Get activate extensions. */ getAdaptiveExtension()
获取一个扩展装饰类对象,dubbo的扩展接口有个规则,它所有的实现类中,要么有且仅有一个类被@Adaptive标注,getAdaptiveExtension()返回的就是这个类对象,要么所有的实现类都没有@Adaptive注解,此时,dubbo就动态创建一个代理类并由getAdaptiveExtension()返回。这块后面详细谈论。
下面先讨论extensi er的获取
先从dubbo的第一行代码开始:
com.alibaba.dubbo.container.Main.main(args);
从这里的main方法进入,就来到dubbo的Main class,这里定义了一个静态初始化的变量loader,这是dubbo的第一个扩展点,我们从这里开始跟代码,
private static final Extensi er<Container> loader = Extensi er.getExtensi er(Container.class);
进入getExtensi er内部,
1 private static final ConcurrentMap<Class<?>, Extensi er<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, Extensi er<?>>(); 2 3 public static <T> Extensi er<T> getExtensi er(Class<T> type) { 4 ...... 5 Extensi er<T> loader = (Extensi er<T>) EXTENSION_LOADERS.get(type); 6 if (loader == null) { 7 EXTENSION_LOADERS.putIfAbsent(type, new Extensi er<T>(type)); 8 loader = (Extensi er<T>) EXTENSION_LOADERS.get(type); 9 } 10 return loader; 11 }
这个方法的入参为Class<T> type,标志了将要获取到的extensi er的扩展接口的类型,此时实际的传入参数为Container.class。
同时大家注意,我们前面说过,为扩展接口创建extensi er时,所创建的extensi er会被缓存起来,所以我们这里看到一个concurrentHashMap被申明用做缓存。
上述代码显示缓存中查询为null时,会创建一个extensi er<T>,我们继续跟踪new Extensi er<T>(type),从代码第7行进入,
1 private Extensi er(Class<?> type) { 2 this.type = type; 3 Factory = (type == ExtensionFactory.class ? null : Extensi er.getExtensi er(ExtensionFactory.class). getAdaptiveExtension()); 4 }
首先为扩展接口的类型type赋值,这里是Container.class,大家注意一个细节,前面getExtensi er方法的入参是Class<T>,而这里却是Class<?>,由泛型变成了通配符,稍后解释原因。
第三行为 Factory赋值,暂时先不管 Factory的作用,我们先看主干逻辑,因为这里type是Container.class,三元运算符进入后半部分,再次调用getExtensi er,并传入参数ExtensionFactory.class。
继续跟踪代码,在getExtensi er内部它又会去查询缓存,因为这里还是不存在ExtensionFactory.class的key,所以继续进入new Extensi er<T>(type)的逻辑,这次的传入参数是ExtensionFactory.class,所以 Factory被赋值为null。
这里就可以看出来了,两次调用Extensi er的构造方法,入参分别为Container.class和ExtensionFactory.class,所以构造方法的入参使用通配符,同时concurrentHashMap作为缓存,它的key也是Class<?>。
ok,到目前为止,我们总结下调用链:
Extensi er.getExtensi er(Container.class); -->this.type = type; --> Factory = Extensi er.getExtensi er(ExtensionFactory.class). getAdaptiveExtension(); -->Extensi er.getExtensi er(ExtensionFactory.class); -->this.type = type; --> Factory = null;
缓存中应该有两项记录了,
{ "key": "Container.class", "value": "......" }, { "key": "ExtensionFactory.class", "value": "......" }
总结下以上代码,每一个Extensi er都包含有两个属性type, Factory:
- Class<T> type,初始化时要得到的接口名。
- ExtensionFactory Factory,初始化一个AdaptiveExtensionFactory。 Factory的作用是:为dubbo的Ioc提供所有对象,这个的实现原理还是相当复杂的,之后单开一篇来说。
ok,现在我们获取到了Extensi er,接下来就是获取Extension了。
下面讨论getAdaptiveExtension()
回忆前面在创建Extensi er<Container>时,要初始化它的 Factory:
Factory = Extensi er.getExtensi er(ExtensionFactory.class)
.getAdaptiveExtension();
这里就是先获取Extensi er<ExtensionFactory>,之后调用了getAdaptiveExtension()。所以我们跟踪这个方法:
1 public T getAdaptiveExtension() { 2 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 = " -INF/services/"; private static final String DUBBO_DIRECTORY = " -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的存储路径就是 -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的存储路径 -INF/dubbo/internal/,type.getName()获得扩展接口的类路径,两者拼接就得到一个dubbo spi的完整路径,这里的话就是: -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的扩展实现都有哪些,手动打开 -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 = Extensi er.getExtensi er(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注解,所以如果我们执行下面这句代码,
Extensi er.getExtensi er(Protocol.class).getAdaptiveExtension();
它在通过spi配置文件的类加载中得不到一个Adaptive的实现类,所以代码最终会进入createAdaptiveExtensionClass()方法中,我们通过debug拿到String code的值,如下,
1 package com.alibaba.dubbo.rpc; 2 import com.alibaba.dubbo.common.extension.Extensi er; 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)Extensi er.getExtensi er(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)Extensi er.getExtensi er(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反复跟踪,不断琢磨。
继续阅读与本文标签相同的文章
-
基础c4d教程:简单的木质吊灯建模,小白也能学会
2026-05-18栏目: 教程
-
大族激光:智能装备LION系列光纤激光切割机发布仪式在湘隆重召开
2026-05-18栏目: 教程
-
实拍上汽首个“无人”仓库,本月正式运行
2026-05-18栏目: 教程
-
自动驾驶光车以外的硬件就7万 滴滴想让你不买车就能先坐上
2026-05-18栏目: 教程
-
滴滴迎来大整顿!1000万罚单认清现实,8万司机被开除
2026-05-18栏目: 教程
