Dubbo SPI(一)

ExtensionLoader

这是构成dubbo spi内核的主要类,因此是阅读dubbo源码必须要先了解的类。

getExtensionLoader

ExtensionLoader的构造方法是私有的,唯一得到实例的方法就是这个静态方法。它确保了type是接口,并且通过withExtensionAnnotation方法确保接口上有SPI注解,然后构造实例对象,并放入EXTENSION_LOADERS静态域缓存。

getExtensionClasses

private Map> getExtensionClasses() {
    //先从类实例缓存中加载
    Map> 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 {
    //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> 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> extensionClasses = new HashMap>();
    //加载指定的三个目录的文件名是接口全类名的文件
    //这里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> extensionClasses, String dir, String type) {
    //目录+接口全类名
    String fileName = dir + type;
    try {
        Enumeration 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> 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> 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> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet>();
            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加载目录

  1. META-INF/services/
  2. META-INF/dubbo/
  3. META-INF/dubbo/internal/

dubbo类名来源

  1. 资源文件中通过=指定,可以通过,分隔(,两边支持空格)
  2. 通过Extension注解指定(Deprecated)
  3. 通过类名拿到(截去接口类名)

之前我们看到exceptions缓存了加载某个扩展类出现的异常,缓存的键是文件的一行(去掉了注释),后续是通过扩展类名去拿缓存的,所以第二种方式显然不能匹配得到。

扩展类分类

  1. 默认扩展,spi注解指定
  2. 适配扩展,Adaptive注解指定,只能有一个
  3. 包装扩展,有接口类型入参的构造方法
  4. 激活扩展,Activate指定,可以通过该注解的几个值指定是否生效,filter就通过该注解
  5. 一般扩展,cachedClasses缓存了名字和类的关系,cachedNames缓存了类和首个名字的关系

你可能感兴趣的:(Dubbo SPI(一))