SPI在java中称为Service Provider Interface,Dubbo中对java的spi机制自己实现相对应的逻辑。
我们以Dubbo中的Protociol为例,一般我们去获取对应的Protocol的时候都是通过如下方式:
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); // 或者 ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
我们看下Dubbo中具体怎么实现,
public staticExtensionLoader getExtensionLoader(Class type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } ExtensionLoader loader = (ExtensionLoader ) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader (type)); loader = (ExtensionLoader ) EXTENSION_LOADERS.get(type); } return loader; }
可以看到,Dubbo中是每个通过SPI加载类型都有一个对应的ExtensionLoader,我们看看具体怎么加载类的:
public T getExtension(String name) {
T extension = getExtension(name, true);
if (extension == null) {
throw new IllegalArgumentException("Not find extension: " + name);
}
return extension;
}
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder
这里我们首先会通过getExtensionClasses去加载对应的SPI扩展类,通过loadExtensionClasses去进行实际的加载,这里首先会用java的 ServiceLoader去加载对应的LoadingStrategy,而ServiceLoader会加载当前classpath下meta-INF.services下对应去路径类对应的文件中列出的类:
而这里LoadingStrategy则会加载如下三个实现类:
org.apache.dubbo.common.extension.DubboInternalLoadingStrategy org.apache.dubbo.common.extension.DubboLoadingStrategy org.apache.dubbo.common.extension.ServicesLoadingStrategy
而这里这几个LoadingStrategy主要是定义了要扫描哪些文件夹:
DubboInternalLoadingStrategy会加载如下文件夹:
public class DubboInternalLoadingStrategy implements LoadingStrategy {
public String directory() {
return "meta-INF/dubbo/internal/";
}
public int getPriority() {
return MAX_PRIORITY;
}
}
DubboLoadingStrategy:
public class DubboLoadingStrategy implements LoadingStrategy {
public String directory() {
return "meta-INF/dubbo/";
}
public boolean overridden() {
return true;
}
public int getPriority() {
return NORMAL_PRIORITY;
}
}
ServicesLoadingStrategy:
public class ServicesLoadingStrategy implements LoadingStrategy {
public String directory() {
return "meta-INF/services/";
}
public boolean overridden() {
return true;
}
public int getPriority() {
return MIN_PRIORITY;
}
}
也就是说,如果我们想要Dubbo的SPI区加载我们指定的目录下的实现的话,那么我们需要实现LoadingStrategy然后在classpath下新建预估ieorg.apache.dubbo.common.extension.LoadingStrategy名称的文件,文件的内容为我们自己实现的LoadingStrategy全名称,多个换行即可
从上面可以看到,Dubbo会从如下几个文件夹去加载对应的SPI扩展类:
meta-INF/dubbo/internal/、meta-INF/dubbo/、meta-INF/services/
最终通过loadResource去加载对应需要加载的类全名称文件里面的内容:
private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL, boolean overridden, String... excludedPackages) { try { try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) { String line; String clazz = null; while ((line = reader.readLine()) != null) { final int ci = line.indexOf('#'); if (ci >= 0) { line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf('='); if (i > 0) { name = line.substring(0, i).trim(); clazz = line.substring(i + 1).trim(); } else { clazz = line; } if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) { loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden); } } catch (Throwable t) { } } } } } catch (Throwable t) { } }
可以看到,这里是按照行进行读取的,然后需要注意的是这里会判断每行是否有=,如果有=号的化,那么会进行分割,拿到对应的SPI需要加载的类
拿到类之后,接下来就去加载类,通过loadClass实现:
private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class> clazz, String name, boolean overridden) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error occurred when loading extension class (interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface."); } if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz, overridden); } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { clazz.getConstructor(); if (StringUtils.isEmpty(name)) { name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) { cacheActivateClass(clazz, names[0]); for (String n : names) { cacheName(clazz, n); saveInExtensionClass(extensionClasses, clazz, n, overridden); } } } }
上面逻辑主要有三个:
- 加载的类上面是否有Adaptive注解,如果有Adaptive那么会设置cacheAdaptiveClass中
- 如果没有Adaptive注解,那么判断是否是Wrapper类,这里Dubbo判断是否是Wrapper的方式很简单,就是判断这个类的构造函数是否支持只传入一个参数,且这个参数是待加载的类型:
private boolean isWrapperClass(Class> clazz) {
try {
clazz.getConstructor(type);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
- 如果上面两项都不符合的话,如果name为空,那么查看类上Extension注解对应的值作为name,如果没有Extension注解,那么name的逻辑为判断类名称的是否以待获取的类名称结尾,如果是直接直接取类名称去除待获取类名称的部分并转小写然后将name和对应的class类型设置到缓存中去:
private String findAnnotationName(Class> clazz) {
org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
if (extension != null) {
return extension.value();
}
String name = clazz.getSimpleName();
if (name.endsWith(type.getSimpleName())) {
name = name.substring(0, name.length() - type.getSimpleName().length());
}
return name.toLowerCase();
}
也就是说,我们通过
这样我们就加载到了所有的类,并设置到了对应的类中,如果不是Adaptive和WrapperClass那么会将类和对应name放置到一个map中并返回,然后在createExtension中根据name就能够获取到对应的类型
对于createExtension,首先是根据获取到的类型进行实例化,并注入相关的属性(注入属性也是通过SPI机制,如果有set方法会进行注入,如果方法上有DisableInject注解或忽略
实例化类之后,会进行wrap也就是包装,会实例化Wrapper,然后将生成的实际类的实例注入进去,这里由于会存在多个Warapper类,会进行排序,通过获取Activate中的order属性,这里是按照从小到大的顺序排列。
这样多个Wrapper实际上是一个Wraprer嵌套这一个Wrapper。
所以说,Dubbo的SPI机制如果有warapper实现返回的是一个Wrapper类。
另外对于getAdaptiveExtension,Dubbo中的处理也很简单暴力,直接加载对应要加载类名称后面加上$Adaptive:
private Class> createAdaptiveExtensionClass() {
ClassLoader classLoader = findClassLoader();
if (ApplicationModel.getEnvironment().getConfiguration().getBoolean(NATIVE, false)) {
try {
return classLoader.loadClass(type.getName() + "$Adaptive");
} catch (ClassNotFoundException e) {
//ignore
e.printStackTrace();
}
}
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
比如ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();加载的就是Protocol$Adaptive然后在Protocol$Adaptive中仍然通过ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName)去加载对应的类
到这里Dubbo的SPI机制原理大概就分析完了,我们总结下整个流程以org.apache.dubbo.rpc.Protocol为例:
- 当我们通过ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);去加载一个Protocol实例的时候,首先Dubbo会通过JAVA的ServiceLoader去加载当前classpath下meta-INF/dubbo/internal/、meta-INF/dubbo/、meta-INF/services/下文件名称为org.apache.dubbo.rpc.Protocol
- 加载到org.apache.dubbo.rpc.Protocol文件后,会按行读取,每行会按照=进行分割
- 对于读取出来的类的类型,会进行判断,1. 是否有Adaptive注解,如果有会设到cachedAdaptiveClass属性中去 2. 是否是WrapperClass,判断很简单,就是看这个类是否有一个构造函数只有一个参数且参数类型是Protocol类型 3. 如果这行没有=,获取对应类型的名称(先看是否有Extension注解,如果没有,那么将该类型名称去除接口部分后转小写), 然后将name和class类型放到一个map中并设置cachedActivates
- 循环处理所有的文件内容,返回一个map,map的key为name,value为对应的类型
- 对获取到的类型进行实例化,直接调用clazz.getDeclaredConstructor().newInstance()进行实例化,实例化后,还会进行初始化,初始化也是通过SPI机制,对于有set方法的属性,通过SPI机制去加载对应的类型并注入
- 判断是否有WarapperClass,如果有的话且需要进行wrap,会将步骤3中获取的WrapperClass列表进行排序,这里会进行reverse,也即是按照从大到小的顺序排序,如果WrapperClass没有Wrapper注解或者Wrapper注解的matches包含这个name,那么沪江这个wrapper进行实例化,多个Wrapper会一个嵌套一个。
THE END



