《阅读源码系列》—— Dubbo SPI

267 阅读2分钟

前言

Dubbo SPI 机制大家都清楚,通过在META-INF/dubbo.internal 下配置类的全路径,来加载文件中的某一个实现。例如下图: image.png

这篇笔记以注册中心的加载为例,通过源码来看一下dubbo内部是如何使用SPI 机制的

注册中心的调用

首先说下注册中心的初始化步骤,如下图 image.png

  • DubboBootstrap启动器在初始化的时候会去构建一个注册中心复合类CompositeDynamicConfiguration, 可以将其理解为注册中心的父类
  • dubbo调用注册中心的工厂类DynamicConfigurationFactory,用于加载具体的注册中心实现
  • DynamicConfigurationFactory 使用SPI机制,通过ExtensionLoader.getExtensionLoader 来获取注册中心实现类

ExtensionLoader

DynamicConfigurationFactory 做的事情

    static DynamicConfigurationFactory getDynamicConfigurationFactory(String name) {
        //获取我们配置的SPI文件名称,即:org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory
        Class<DynamicConfigurationFactory> factoryClass = DynamicConfigurationFactory.class;
        //获取加载器
        ExtensionLoader<DynamicConfigurationFactory> loader = getExtensionLoader(factoryClass);
        //传入key值,获取具体的实现类
        //这里name=nacos,对应SPI文件中的 nacos=org.apache.dubbo.configcenter.support.nacos.NacosDynamicConfigurationFactory
        return loader.getOrDefaultExtension(name);
    }

ExtensionLoader.getOrDefaultExtension 的调用流程如下: image.png

getOrDefaultExtension 会去加载SPI类,如果找到想要的(name)类,就通过反射去创建类的实例,如果没有就去加载默认的实现类。

首先从系统中加载SPI实现:loadExtensionClasses

private Map<String, Class<?>> loadExtensionClasses() {
    cacheDefaultExtensionName();
    //这里就是SPI文件中的key、value。
    //value是通过Class.forName(line, true, classLoader) 转换成了class对象
    //这个map结果集会被缓存起来
    Map<String, Class<?>> extensionClasses = new HashMap<>();

    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    }

    return extensionClasses;
}

从map中寻找想要的类:containsKey

    private boolean containsExtension(String name) {
        //getExtensionClasses 返回的就是上面的map结果集
        return getExtensionClasses().containsKey(name);
    }

通过反射创建SPI类的实例:createExtension

private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            //先尝试从缓存获取
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                //缓存没有的话,就反射创建实例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

总结

  • SPI 的核心处理类是ExtensionLoader,dubbo将其封装成了一个工具类,只要传入class 和name 就能获取需要的对象。
  • 我们可以看出,只有在使用的时候dubbo才会去加载需要的SPI对象,而且是将class的创建和实例的创建分开处理,只对你需要的类创建实例。
  • 相较于JAVA SPI机制的全量加载实例,dubbo的SPI设计无疑是高效的。