Dubbo进阶(六)- Dubbo SPI 源码级过程详解

Dubbo SPI 是Dubbo用于支持 扩展 一个功能。

关于 SPI 的具体意思以及内容,相信 官方这篇文档已经讲的足够通俗易懂了:
http://dubbo.apache.org/zh-cn/blog/introduction-to-dubbo-spi.html

里面提到了两点对于SPI 功能的解释,有两点:

  1. 作为框架的维护者,在添加一个新功能时,只需要添加一些新代码,而不用大量的修改现有的代码,即符合开闭原则。
  2. 作为框架的使用者,在添加一个新功能时,不需要去修改框架的源码,在自己的工程中添加代码即可。

Dubbo 支持多种协议,多种配置中心,注册中心,传输层协议的配置,当然里面也有相关代码维护,如果以设计模式角度来说,当然可以通过工厂模式来完成多协议的配置。

上文中对于 SPI 的优点,从框架维护者到使用者都有涉及,不多废话,听博主一层一层解析SPI 过程吧

获取

主要从 ReferenceConfig 入手,它里面有 声明具有 SPI 声明的多个接口,这里以 Protocol 为例:

private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

接下来看看 Protocol 的声明:


/**
 * Protocol. (API/SPI, Singleton, ThreadSafe)
 */
@SPI("dubbo")
public interface Protocol {

    int getDefaultPort();
    @Adaptive
     Exporter export(Invoker invoker) throws RpcException;

    @Adaptive
     Invoker refer(Class type, URL url) throws RpcException;

    void destroy();
}

Protocol 使用了 @SPI 修饰,并且有了默认值是 dubbo,同时定义了 exportrefer等方法。

getExtensionLoader

进去看看 getExtensionLoader 方法:

    public static  ExtensionLoader getExtensionLoader(Class type) {
        // 检查下type是否为null
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        // 检查是否为接口
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
        // 检查是否有 @SPI 注解
        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;
    }

前面有对 type 进行 判断,包括:

  1. 为空
  2. 是否为接口
  3. 是否有 SPI 注解

最后尝试去 EXTENSION_LOADERS 获取,如果没有的化,则会new一个ExtensionLoader(type) 放入,
最后也同样是 返回该 ExtensionLoader

ExtensionLoader 构造方法

    private ExtensionLoader(Class type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

首先会记录该 type,作为 SPI 的类型,而后会去初始化 objectFactory, 如果当前type是 ExtensionFactory,则初始化为null,否则继续调用 ExtensionLoadergetExtensionLoader 去获取 ExtensionFactory 这个 SPI 实例。
这是一个递归调用,所以下一个先回调用 getExtensionLoader ,而后会给 ExtensionFactory 也分配一个 ExtensionLoader,而后执行完之后,则会调用 ExtentionLoadergetAdaptiveExtension

getAdaptiveExtension

这里以传入的 type 为 ExtensionFactorygetAdaptiveExtension

    public T getAdaptiveExtension() {
    // 看是否已经初始化了
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                // 再尝试获取一次
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                        	// 初始化
                            instance = createAdaptiveExtension();
                            // 放入 cachedAdaptiveInstance 中
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                        	// 报错了,放入了 createAdaptiveInstanceError
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
            // 说明出错了
                throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }
		// 返回instance
        return (T) instance;
    }

判断是 当前缓存的 cachedAdaptiveInstance 优先实例 为 空,如果为空,则需要调用 createAdaptiveExtension 创建一个。

createAdaptiveExtension

createAdaptiveExtension 中,则主要会调用 getAdaptiveExtensionClass 获取优先加载的类,并使用 newInstance new出实例。
而后则会调用 injectExtensionnewInstance 出来的实例进行 SPI 属性注入。

getAdaptiveExtensionClass

    private Class getAdaptiveExtensionClass() {
       // 加载类
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        // 如果加载的类中,没有 @Adaptive类型,则需要创建一个默认的 @Adaptive字节码类。
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

首先会调用 getExtensionClass 去从 文件目录下读取类,而后将 有 @Adaptive 的进行初始化。

getExtensionClasses

getExtensionClass 则会先判断 该类型的 SPI 类是否已经加载,即从 cachedClasses 中读取。否则将会调用 loadExtensionClasses 去调用。

    private Map> getExtensionClasses() {
        Map> classes = cachedClasses.get();
        // 判断是否已经缓存
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                // 否则将会去获取load 以下对应文件中内容
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

loadExtensionClasses

loadExtensionClasses 则是从不同位置文件夹去加载配置,由于 加入旧版本的支持,所以会 将根目录下 com.alibaba 对应包名也处理下:

    private Map> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map> extensionClasses = new HashMap<>();
        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;
    }

cacheDefaulltExtensionName 方法中,主要目的就是解析 @SPI注解,获取 value值,然后将 value值 存储到 当前ExtensionLoader 的cachedDefaultName 中。
接下来执行了多次 loadDirectory,主要就是加载 META-INF 下面 类名相同的扩展文件,主要就是 dubbo 的SPI机制。
主要有以下几个方面:

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

另外,对于旧版本的 SPI问题,Dubbo也给出了解决方案,使用 type.getName().replace("org.apache", "com.alibaba")org.apache 改为com.alibaba
通过给定的 ExtensionLoader中的type,从而加载对应名字的 SPI 文件。
从而反射将其初始化。

    private void loadDirectory(Map> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        try {
            Enumeration urls;
            // 获取类加载器
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                // url 是文件的地址,这一步只是加载对应 spi 文件名 的所有的文件地址
                urls = classLoader.getResources(fileName);
            } else {
                // 直接加载系统资源
                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 occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

loadResource

由于 spi 文件的 格式是key=value ,所以需要读取这一段并加载里面所得到的类名:
另外,由于 在loadResource时候,有些 可插拔式的接口并没有完全引入相应的包,例如在加载 Protocol 的SPI时候,初始化
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol,如果没有引入 hessian 相应依赖,则会报错,放入 exceptions中。

    private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                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) {
                            // 允许报错,因为dubbo 的spi里面定义了 很多协议,但是需要有对应jar包引入,如果没有引入,则在这一步,则会报错,例如hessian 和 http。webservice等
                            // 这一步错误,主要是在 Class.forName 这一步出错。
                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

loadClass

这个方法挺重要的,在初始化对应 SPI 中类时候,需要对其类进行区分:

  1. @Adaptive 的修饰类
  2. Wrapper类
  3. 普通扩展类
    区分后,并缓存到不同地方。
 private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class clazz, String name) 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)) {
        //  缓存clazz,到 cachedAdaptiveClass 中
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
        // 包装类,就是这个类有一个构造方法,然后只有一个参数就是type
            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)) {
            // 缓存 Activate的class
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                // 缓存名字,可以多个名字对应同一个类 到 
                    cacheName(clazz, n);
                // 缓存类
                    saveInExtensionClass(extensionClasses, clazz, name);
                }
            }
        }
    }

最后执行 saveInExtensionClass,将 对应 SPI 扩展类,放到 extensionClass中,依次返回给loadResourceloadDirectoryloadExtensionClasses,最终设置给 cachedClasses

总结

总结以下,整个 SPI 加载过程

  1. 通过 静态方法 ExtensionLoader.getExtensionLoader 去初始化或者获取已有的 ExtensionLoader
  2. getExtensionLoader 中会先去检查该type是否已经加载,有则直接返回,否则new 一个 ExtensionLoader 返回
  3. getAdaptiveExtension 主要获取使用 @Adaptive 修饰的 扩展,可以理解为默认扩展
  4. 如果从来没有加载过该扩展,则会尝试去DUBBO_INTERNAL_DIRECTORYDUBBO_DIRECTORYSERVICES_DIRECTORY 去加载对应文件名为 type 的SPI 类。
  5. 最后将加载过得 SPI 类缓存在 ExtensionLoader中。
    而后,会将已经加载的放入ExtensionLoader 的多个字段缓存中,具体可以看以下表格

ExtensionLoader详解

ExtensionLoader 是 SPI 功能的一个核心的类,这里单独提出来,对其机制,以及 字段进行单独分析,可以先读完全篇文章在读这里
字段:

字段名 类型 作用
EXTENSION_LOADERS ConcurrentMap, ExtensionLoader> 用于存储 该 type 的 SPI 所对应的 ExtensionLoader如果有则说明该类型的 SPI已经Load
objectFactory ExtensionFactory ExtensionFactory 也是一个SPI类,主要用于加载 SPI类,主要有 SpiExtensionFactorySpringExtensionFactoryAdaptiveExtensionFactory
cachedAdaptiveInstance Holder 用于保存当前的 该 SPI 的 优先缓存实例
cachedWrapperClasses Set> 用于存储该 SPI 类对应的 Wrapper 类,即包装类,包装类主要作用可以使Filter或者修饰作用,其特点是 有一个单参数的构造方法,且其参数为 该 SPI 类
cachedNames ConcurrentMap, String> 将 SPI 文件中,以Class 为key, name为value
cachedActivates Map 用于存储 被 @Active 注解修饰的类和映射,名字是 SPI 对应的key,Object是 Activate 对象

题外

如果整个扩展子类都没有使用 @Adaptive 修饰怎么办?此时 使用的是 @SPI 中的默认扩展名字。并且 Dubbo 会生成一个默认的代理,通过SPI 中 默认扩展名字获取具体的 扩展类。
而后使用createAdaptiveExtensionClass 生成代理类,欲知下文如何,且看下篇分析

觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,Dubbo小吃街不迷路:

你可能感兴趣的:(dubbo,SPI,中间件,Java,dubbo)