ExtensionLoader
这是构成dubbo spi内核的主要类,因此是阅读dubbo源码必须要先了解的类。
getExtensionLoader
ExtensionLoader
的构造方法是私有的,唯一得到实例的方法就是这个静态方法。它确保了type
是接口,并且通过withExtensionAnnotation
方法确保接口上有SPI
注解,然后构造实例对象,并放入EXTENSION_LOADERS
静态域缓存。
getExtensionClasses
private Map<String, Class<?>> getExtensionClasses() {
//先从类实例缓存中加载
Map<String, Class<?>> classes = cachedClasses.get();
//经典的单例写法
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//加载扩展类并放入缓存
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
这是ExtensionLoader
中一个无参的私有方法,用来加载spi
,可以看到在类中的很多方法都会先调用该方法。为什么不在构造方法中直接调用该方法呢?我觉得可能的原因是在真正使用时加载指定接口的扩展,以节约资源。
Holder
注意上面的cachedClasses
,他的类型是dubbo的Holder类。
public class Holder<T> {
//volatile保证值多线程可见
private volatile T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
对于这个类的用途,不是很理解,并且和AtomicReference
感觉有点相似。
偶然有一天看到一个场景,可能是其用途所在,仅个人理解。代码如下:
public void test(boolean flag) throws Exception {
Thread.sleep(5000);
if (flag) {
System.out.println("now is true");
}
}
flag作为方法的入参,初始值肯定是准确的,但是在线程暂停的5秒内,很有可能外部实际的值已经改变了,但是方法内部判断的依旧是旧值,此时就出现了错误的结果。如果用Holder
或者AtomicBoolean
包起来,那就可以得到当前的准确值。
- 注意以上对
Holder
的理解是错误的,查看Holder
的实例是如何使用的就可发现,其目的是在加锁时有一个局部锁!!
loadExtensionClasses
如果在缓存中没有拿到扩展类,那么就会调用该方法,加载所有的扩展类。dubbo指定了三个资源目录,分别为:META-INF/services/;META-INF/dubbo/;META-INF/dubbo/internal/。
private Map<String, Class<?>> loadExtensionClasses() {
//得到spi注解
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
//得到默认的扩展对象名字
String value = defaultAnnotation.value();
if ((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));
}
//缓存默认扩展类名字
if (names.length == 1) cachedDefaultName = names[0];
}
}
//扩展类名字和类对象的map
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
//加载指定的三个目录的文件名是接口全类名的文件
//这里replace应该是dubbo加入apache项目后做的适配
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
loadDirectory
加载指定目录的扩展类。
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
//目录+接口全类名
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
//得到ExtensionLoader的类加载器
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
//不是bootstrap加载器,直接使用类加载器加载资源
urls = classLoader.getResources(fileName);
} else {
//是bootstrap加载器,调用系统加载器
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
//加载找到的所有的资源文件
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
loadResource
加载具体的某个资源文件。
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
try {
String line;
//按行读取文件内容
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();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
//处理该加载类
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
//缓存加载某个类出现的异常
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
这里将加载某个扩展类出现的异常缓存了起来,不直接抛出保证了可以继续加载其他扩展类,然后在使用该扩展时从缓存中拿到该异常报错。
loadClass
加载资源文件内的指定扩展类。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
//判断是否是接口的实现类
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
//判断接口上是否有Adaptive注解,该注解表示该扩展类是适配类
//然后放入cachedAdaptiveClass缓存,如果有多个报异常
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
//判断是否是包装扩展类,是就放入cachedWrapperClasses缓存
//判断是否有该接口类型入参的构造方法,可以查看isWrapperClass方法
} else if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} else {
//确保有无参构造方法
clazz.getConstructor();
//如果没有用=指定名字就先通过Extension注解拿(不过貌似已经不推荐了)
//再通过类名拿(类名后缀是接口类名的话会去除)
if (name == null || name.length() == 0) {
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 (names != null && names.length > 0) {
//Activate注解表明其是激活扩展,以后会讲到这个注解的作用
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
//缓存激活扩展类
cachedActivates.put(names[0], activate);
} else {
// support com.alibaba.dubbo.common.extension.Activate
com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
if (oldActivate != null) {
cachedActivates.put(names[0], oldActivate);
}
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
//放入缓存
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
//名字重复异常
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
本文总结
SPI
接口上必须要有该注解,该注解的值指定了默认的扩展实现的名字。
dubbo加载目录
- META-INF/services/
- META-INF/dubbo/
- META-INF/dubbo/internal/
dubbo类名来源
- 资源文件中通过
=
指定,可以通过,
分隔(,两边支持空格) - 通过
Extension
注解指定(Deprecated) - 通过类名拿到(截去接口类名)
之前我们看到exceptions
缓存了加载某个扩展类出现的异常,缓存的键是文件的一行(去掉了注释),后续是通过扩展类名去拿缓存的,所以第二种方式显然不能匹配得到。
扩展类分类
- 默认扩展,spi注解指定
- 适配扩展,Adaptive注解指定,只能有一个
- 包装扩展,有接口类型入参的构造方法
- 激活扩展,Activate指定,可以通过该注解的几个值指定是否生效,filter就通过该注解
- 一般扩展,cachedClasses缓存了名字和类的关系,cachedNames缓存了类和首个名字的关系