RPC框架之Dubbo(2)——SPI

314 阅读6分钟

官方文档:dubbo.apache.org/zh/docs/v2.…

基本概念

Java SPIjuejin.cn/post/707559…

Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

Dubbo 改进了 JDK 标准的 SPI 的以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  • 增加了对扩展点 IOCAOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

约定

在扩展类的 jar 包内,放置扩展点配置文件 META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。

示例

定义一个接口

@SPI
public interface Robot {
    void sayHello();
}

定义2个实现类

public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

META-INF/dubbo 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。其内容如下:

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader 可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。与 Java SPI 实现类配置不同,Dubbo SPI 是通过 键值对 的方式进行配置,这样就可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}

测试结果:

image.png

源码分析

首先,通过 ExtensionLoadergetExtensionLoader() 方法获取一个 ExtensionLoader 实例(这个方法在3.0版本已经废弃)。再通过 ExtensionLoadergetExtension() 方法获取扩展类对象。

//方法入口
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) {
    checkDestroyed();
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        // 获取默认的扩展实现类
        return getDefaultExtension();
    }
    String cacheKey = name;
    if (!wrap) {
        cacheKey += "_origin";
    }
    // 获取Holder,顾名思义,用于持有目标对象
    final Holder<Object> holder = getOrCreateHolder(cacheKey);
    Object instance = holder.get();
    // double check
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 创建扩展实例
                instance = createExtension(name, wrap);
                // 将实例设置到 holder 中
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

创建扩展类实例

通过 createExtension() 方法创建扩展实例:

  1. 通过 getExtensionClasses() 获取所有的扩展类
  2. 通过反射创建扩展对象
  3. 向扩展对象中注入依赖
  4. 将扩展对象包裹在相应的 Wrapper 对象中

第一个步骤是加载扩展类的关键,第三和第四个步骤是 Dubbo IOCAOP 的具体实现。

Wrapper 包装类:dubbo.apache.org/zh/docs/v2.…

private T createExtension(String name, boolean wrap) {
    // 从配置文件中加载所有的扩展类,可得到“配置项名称”到“配置类”的映射关系表
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null || unacceptableExceptions.contains(name)) {
        throw findException(name);
    }
    try {
        T instance = (T) extensionInstances.get(clazz);
        if (instance == null) {
            // 通过反射创建实例
            extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
            instance = (T) extensionInstances.get(clazz);
            // 在注入依赖之前调用外部接口方法
            instance = postProcessBeforeInitialization(instance, name);
            // 在实例中注入依赖
            injectExtension(instance);
            // 在注入依赖之后调用外部接口方法
            instance = postProcessAfterInitialization(instance, name);
        }
     
        // 包装类
        if (wrap) {
            // 缓存的 Wrapper class
            List<Class<?>> wrapperClassesList = new ArrayList<>();
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }

            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                for (Class<?> wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    boolean match = (wrapper == null) ||
                        ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
                            !ArrayUtils.contains(wrapper.mismatches(), name));
                    if (match) {
                        // 实例化 Wrapper 类并注入依赖
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        // 在注入依赖之后调用外部接口方法
                        instance = postProcessAfterInitialization(instance, name);
                    }
                }
            }
        }

        // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
            type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

加载所有扩展类

在通过名称获取扩展类之前,首先需要根据配置文件解析出扩展项名称到扩展类的映射关系表(Map<名称, 扩展类>),之后再根据扩展项名称从映射关系表中取出相应的扩展类即可。

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;
}

loadExtensionClasses() 方法总共做了两件事情,一是对 @SPI 注解进行解析,二是调用 loadDirectory() 方法加载指定文件夹配置文件。

private Map<String, Class<?>> loadExtensionClasses() {
    checkDestroyed();
    // 解析 @SPI 注解
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();

    // 加载指定文件夹配置文件
    // LoadingStrategy[] strategies 加载策略
    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy, type.getName());

        // compatible with old ExtensionFactory
        if (this.type == ExtensionInjector.class) {
            loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
        }
    }

    return extensionClasses;
}

LoadingStrategy 类去加载指定文件夹下的配置文件。LoadingStrategy 是个接口,通过Java SPI 机制去获取该接口的实现类。其配置在 META-INF/servies/org.apache.dubbo.common.extension.LoadingStrategy

其三个实现类分别代表的配置文件目录为:

  • DubboInternalLoadingStrategy:META-INF/dubbo/internal/
  • DubboLoadingStrategy:META-INF/dubbo/
  • ServicesLoadingStrategy:META-INF/services/
org.apache.dubbo.common.extension.DubboInternalLoadingStrategy
org.apache.dubbo.common.extension.DubboLoadingStrategy
org.apache.dubbo.common.extension.ServicesLoadingStrategy

解析 @SPI

解析 @SPIvalue 值。type 变量是在构造 ExtensionLoader 时传入的 服务接口 class 类型。

private void cacheDefaultExtensionName() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation == null) {
        return;
    }

    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];
        }
    }
}

加载指定文件夹配置文件

loadDirectory() 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource() 方法加载资源。

// 方法入口
private void loadDirectory(Map<String, Class<?>> extensionClasses, LoadingStrategy strategy, String type) {
    loadDirectory(extensionClasses, strategy.directory(), type, strategy.preferExtensionClassLoader(),
        strategy.overridden(), strategy.includedPackages(), strategy.excludedPackages(), strategy.onlyExtensionClassLoaderPackages());
    
    // 兼容com.alibab 开头的包名
    String oldType = type.replace("org.apache", "com.alibaba");
    loadDirectory(extensionClasses, strategy.directory(), oldType, strategy.preferExtensionClassLoader(),
        strategy.overridden(), strategy.includedPackagesInCompatibleType(), strategy.excludedPackages(), strategy.onlyExtensionClassLoaderPackages());
}

// 重载
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                           boolean extensionLoaderClassLoaderFirst, boolean overridden, String[] includedPackages,
                           String[] excludedPackages, String[] onlyExtensionClassLoaderPackages) {
    
    // fileName = 文件夹路径 + type全限定名
    String fileName = dir + type;
    try {
        List<ClassLoader> classLoadersToLoad = new LinkedList<>();

        // try to load from ExtensionLoader's ClassLoader first
        if (extensionLoaderClassLoaderFirst) {
            ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
            if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                classLoadersToLoad.add(extensionLoaderClassLoader);
            }
        }

        // load from scope model
        Set<ClassLoader> classLoaders = scopeModel.getClassLoaders();

        if (CollectionUtils.isEmpty(classLoaders)) {
            // 通过ClassLoader获取配置文件的URL(当前配置文件所在的路径)
            // file:/D:/workspace/**/target/classes/META-INF/dubbo/org.example.dubbo.provider.spi.Robot
            Enumeration<java.net.URL> resources = ClassLoader.getSystemResources(fileName);
            if (resources != null) {
                while (resources.hasMoreElements()) {
                    // 加载资源
                    loadResource(extensionClasses, null, resources.nextElement(), overridden, includedPackages, excludedPackages, onlyExtensionClassLoaderPackages);
                }
            }
        } else {
            classLoadersToLoad.addAll(classLoaders);
        }

        // 每个classLoader都加载一次配置文件,获取该URL
        Map<ClassLoader, Set<java.net.URL>> resources = ClassLoaderResourceLoader.loadResources(fileName, classLoadersToLoad);
        resources.forEach(((classLoader, urls) -> {
            // 方法内部也调用了 loadResource() 方法
            loadFromClass(extensionClasses, overridden, urls, classLoader, includedPackages, excludedPackages, onlyExtensionClassLoaderPackages);
        }));
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
            type + ", description file: " + fileName + ").", t);
    }
}

loadResource() 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass() 方法进行其他操作。

//加载资源文件
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                          java.net.URL resourceURL, boolean overridden, String[] includedPackages, String[] excludedPackages, String[] onlyExtensionClassLoaderPackages) {
    try {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
            String line;
            String clazz;
            //按行读取配置内容
            while ((line = reader.readLine()) != null) {
                // 定位 # 字符
                final int ci = line.indexOf('#');
                if (ci >= 0) {
                    // 截取#之前的内容,#之后的内容为注释,忽略
                    // a=b # ....
                    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) && isIncluded(clazz, includedPackages)
                            && !isExcludedByClassLoader(clazz, classLoader, onlyExtensionClassLoaderPackages)) {
                            // 加载类,并通过 loadClass 方法对类进行缓存
                            loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
                        }
                    } 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);
                    }
                }
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
            type + ", class file: " + resourceURL + ") in " + resourceURL, t);
    }
}

loadClass() 方法用于主要用于操作缓存。

@Adaptive 扩展点自适应:dubbo.apache.org/zh/docs/v2.…

@Activate 扩展点自动激活: dubbo.apache.org/zh/docs/v2.…

private void loadClass(Map<String, Class<?>> 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.");
    }
    // 检测是否有 Adaptive 注解
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        // 缓存 cachedAdaptiveClass 缓存
        cacheAdaptiveClass(clazz, overridden);
    // 检测 clazz 是否是 Wrapper 类型
    } else if (isWrapperClass(clazz)) {
        // 存储 clazz 到 cachedWrapperClasses 缓存中 
        cacheWrapperClass(clazz);
    } else {
        if (StringUtils.isEmpty(name)) {
            // 如果没有name,则解析 @Extension 注解获取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 注解,则使用 names 数组的第一个元素作为键,
             // 存储 name 到 Activate 注解对象的映射关系
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 存储 class 到 name 的映射关系
                cacheName(clazz, n);
                // 存储 name 到 class 的映射关系
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

参考文献

dubbo.apache.org/zh/docsv2.7…