Dubbo源码分析二、dubbo的SPI

本文深入剖析Dubbo的SPI机制,从ExtensionLoader的使用开始,详细解释了如何通过name获取扩展实现,包括类加载、依赖注入和包装过程。Dubbo的SPI不仅提供简单的接口实现选择,还支持类似Spring的IOC和AOP功能。通过源码分析,展示了SPI扩展类加载、双重检查、setter方法注入以及包装类的代理模式。
摘要由CSDN通过智能技术生成

前言

​ 前文讲到了Dubbo中有十个分层,各分层之间单向依赖。每一层对其他模块的依赖都是通过Dubbo的SPI机制建立的。因此我们首先要了解Dubbo的SPI机制。由于dubbo各层次之间的这种SPI依赖关系,我们可以很方便的实现在各层的扩展,只需要针对性的定制相应SPI接口的实现即可。

​ 要知道的是,dubbo没有直接使用Java SPI,而是重新实现了一套功能更为强大的SPI机制。相关逻辑都在ExtensionLoader 类中。先看一个demo示例。

  1. 先定义一个SPI接口,该接口被@SPI注解标注

    @SPI
    public interface MySpi {
        void test();
    }
    
  2. 然后定义一个配置文件,文件名为上一步接口的全名,放在META-INF/dubbo 路径下,文件内容如下:

spi1=org.apache.dubbo.demo.spi.MySpiSupport
spi2=org.apache.dubbo.demo.spi.MySpiSupport2

其中每一行’=‘前是定义的name别名,’='后是对应该接口的实现类全名

  1. 看一下实现的代码:

    public class MySpiSupport implements MySpi{
        @Override
        public void test() {
            System.out.println("MySpiSupport.test()");
        }
    }
    

    很简单的,就是打印一下

  2. 测试代码:

    public class MySpiTest {
        public static void main(String[] args) {
            ExtensionLoader<MySpi> loader = ExtensionLoader.getExtensionLoader(MySpi.class);
            MySpi spi1 = loader.getExtension("spi1");
            spi1.test();
        }
    }
    

一共两步:

  • 先得到对应SPI接口的extensionLoader
  • 根据name得到对应的实现类对象

从上面的测试代码中可以看到,我们可以通过指定name来得到相应的SPI实现。也就是说我们可以通过注入不同的name可以得到相应模块的不同实现。

实际上Dubbo的SPI还提供了更强大的功能,即类似Spring的IOC和AOP。 这部分我们可以在后面分析代码的过程中看到。

源码分析

ExtensionLoader.getExtensionLoader
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }

    // 缓存中获取 每个类型的扩展加载器通过缓存维护,loader本身不提供公开的实例化方法。
    // 可以看成是按type类型的单例模式
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        // 若缓存未命中,则创建一个新的,并放入到缓存中
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

代码比较简单。首先对type类型进行判断,不能为空,必须是接口,必须有@API注解。然后从缓存中取,没有取到就new一个新的。

看一下构造方法:

private ExtensionLoader(Class<?> type) {
    this.type = type;
    // 如果取的是ExtensionFactory本身的扩展点,则设置objectFactory为空,否则初始化设置objectFactory为ExtensionFactory的扩展点实现
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

getAdaptiveExtension这个是自适应扩展,后面再分析。

从这里我们可以看到。ExtensionLoader只有一个私有的构造方法。它的实例只能通过getExtensionLoader静态方法得到,而且又有缓存。那么得到一个结论,ExtensionLoader类似单例模式的实现方式,只不过会出现多个实例,每个实例对应一个SPI接口,也就是说有多少个SPI接口,也就最多有多少个ExtensionLoader实例(没有调用到的SPI接口,还没有创建出来,缓存中也没有。)

loader.getExtension({name})
public T getExtension(String name) {
    return getExtension(name, true);
}

public T getExtension(String name, boolean wrap) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        // 获取默认的拓展实现类
        return getDefaultExtension();
    }
    // Holder,顾名思义,用于持有目标对象
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
    // 双重检查
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 创建拓展实例
                instance = createExtension(name, wrap);
                // 设置实例到 holder 中
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}
  • wrap代表是否包装。默认不传这个参数的时候是true。 这个包装概念后面代码中会看到。

  • name为“true”时返回默认的实现。这个默认是怎么来的后面分析

  • Holder持有需要获取的扩展对象。主要是使用了volatile关键字保证可见性。

    public class Holder<T> {
    
        private volatile T value;
    
        public void set(T value) {
            this.value = value;
        }
    
        public T get() {
            return value;
        }
    
    }
    
  • 然后就是双重检查,保证线程安全

  • 双重检查未得到的情况下创建对象实例createExtension

createExtension(name, wrap)
private T createExtension(String name, boolean wrap) {
    // 从配置文件中加载拓展类,可得到“配置项名称”到“配置类”的映射关系表
    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);
       
        if (wrap) {
            List<Class<?>> wrapperClassesList = new ArrayList<>();
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }
            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                // 循环创建 Wrapper 实例
                for (Class<?> wrapperClass : wrapperClassesList) {
                    // 获取对应的wapper注解
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    /**
                     * 如果没有注解,则默认按上述条件认为是包装类
                     * 如果有注解,则需要满足注解的matches条件和mismatches条件
                     */
                    if (wrapper == null
                            || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                        // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
                        // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                        /**
                         * 原来的instance被替换成包装类,同时把原instance作为构造方法参数注入,
                         * 其他扩展属性通过setter注入
                         */
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                    }
                }
            }
        }

        /**
         * 这里执行初始化方法,即实现了Lifecycle接口的类调用initialize方法
         * Lifecycle接口,具有生命周期特性的(init ->start -> destory)
         */
        initExtension(instance);

        /**
         * 到此,SPI的扩展类初始化完成
         */
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

这里做了几件事情:

  1. 获得当前name所对应的Class类型
  2. 反射创建对象
  3. 通过setter方法进行依赖注入
  4. 如果是需要包装的进行包装,并将原对象注入到包装对象中
  5. 如果当前要拿的扩展对象是Lifecycle接口的实现类,调用它的initialize方法
getExtensionClasses

这部分就是SPI扩展类加载过程。

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

双重检查

private Map<String, Class<?>> loadExtensionClasses() {
    cacheDefaultExtensionName();

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

    /**
     * strategies LoadingStrategy的实现类数组,看一下初始化方法,
     * 使用的是JDK的SPI机制
     */
    // 加载指定文件夹下的配置文件
    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        // 兼容老版本。将原com.alibaba的扩展转换成org.apache的扩展接口
        loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    }

    return extensionClasses;
}

这里的strategies加载器是使用的java的SPI机制得到的。可以看一下它的初始化方法:

private static LoadingStrategy[] loadLoadingStrategies() {
    return stream(load(LoadingStrategy.class).spliterator(), false)
            .sorted()
            .toArray(LoadingStrategy[]::new);
}

其中load(LoadingStrategy.class)这一句是ServiceLoader的。加载了哪些LoadingStrategy的实现可以在META-INF/services 目录下找到,每个实现类指定了dubboSPI的几个加载目录。一般我们只使用META-INF/dubbo目录定义自己的SPI扩展,其他目录是dubbo内部使用的。

好了回来继续看loadDirectory方法,上面调用了两次,实际上就是兼容以前dubbo版本

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                           boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {

    // fileName = 文件夹路径 + type 全限定名
    // /MATE-INF/dubbo/org.apache.xxx.Xxx
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls = null;

        // 获取顺序thread context 的 ClassLoader => 当前class的 ClassLoader => bootstrap的ClassLoader
        ClassLoader classLoader = findClassLoader();

        // try to load from ExtensionLoader's ClassLoader first
        // 先尝试通过ExtensionLoader的ClassLoader获取资源
        // 这里的配置相当于把ExtensionLoader的classLoader放到前面
        if (extensionLoaderClassLoaderFirst) {
            ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
            if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                urls = extensionLoaderClassLoader.getResources(fileName);
            }
        }

        if (urls == null || !urls.hasMoreElements()) {
            // 根据文件名加载所有的同名文件
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
        }

        // 取得urls时
        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                // 加载资源
                loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}

可以看到,开始找文件了,看一下loadResource方法

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                          java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
    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();
                        }
                        // 过滤掉excludedPackages属性定义的
                        if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
                            // 加载类,并通过 loadClass 方法对类进行缓存
                            loadClass(extensionClasses, resourceURL, Class.forName(line, 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);
    }
}

先去掉#定义的注释行

根据‘=’分隔,左边是name,右边是class。然后是loadClass方法:

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 注解
    //  AdaptiveCompiler 和 AdaptiveExtensionFactory 目前只有这两个类加了Adaptive注解
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        // 设置 cachedAdaptiveClass缓存。 cachedAdaptiveClass是一个实例属性,class是上面两个类时才会设置值
        cacheAdaptiveClass(clazz, overridden);
    }
    // 检测 clazz 是否是 Wrapper 类型
    // 即判断是否存在构造方法,参数是当前类型本身的
    else if (isWrapperClass(clazz)) {
        // 存储 clazz 到 cachedWrapperClasses 缓存中
        // 这里将wapperClass缓存起来了,后面在获取具体的扩展对象时,默认情况下会使用这个wapper类包装起来
        cacheWrapperClass(clazz);
    } else {
        // 程序进入此分支,表明 clazz 是一个普通的拓展类
        // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
        clazz.getConstructor();
        // 扩展文件中没有“=”时,即只定义了扩展类的,name为空。
        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);
            }
        }

        // 切分 name 按 ‘,’分隔,即一个扩展有多个name定义
        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 双向存储缓存数据,方便获取
                // 存储 Class 到名称的映射关系
                cacheName(clazz, n);
                // 存储名称到 Class 的映射关系
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

这里有几个缓存,@Adaptive注解标注的、wrapper类型的、普通的,分别有对应的缓存容器存储。

看一下isWrapperClass

private boolean isWrapperClass(Class<?> clazz) {
    try {
        clazz.getConstructor(type);
        return true;
    } catch (NoSuchMethodException e) {
        return false;
    }
}

就是判断clazz是否存在构造方法,方法参数为type类型。

这里wrapper是干什么用的呢?看一下如下测试代码:

public class MyExtensionWapper implements MyExtension{

    private MyExtension extension;

    public MyExtensionWapper(MyExtension extension){
        this.extension = extension;
    }

    @Override
    public void test() {
        System.out.println(extension);
        extension.test();
    }
}

很明显,典型的代理模式。也就是说可以定义一个wrapper类对原实现进行代理,进行功能扩展。像不像Spring的AOP思想?

injectExtension通过setter方法进行依赖注入

好了,现在回来看依赖注入部分

private T injectExtension(T instance) {

    /**
     * objectFactory 是ExtensionFactory接口的实现
     * ExtensionFactory 本身也是一个扩展接口,两个实现SpiExtensionFactory、SpringExtensionFactory
     * 即通过SPI获取和通过Spring管理的Bean获取,默认是SpiExtensionFactory。
     * getLoader时初始化,如果扩展类型type是ExtensionFactory,这个是null,即ExtensionFactory是没有注入
     */
    if (objectFactory == null) {
        return instance;
    }

    try {
        // 遍历目标类的所有方法
        for (Method method : instance.getClass().getMethods()) {
            // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
            // 只保留setter方法执行下面逻辑
            if (!isSetter(method)) {
                continue;
            }
            /**
             * Check {@link DisableInject} to see if we need auto injection for this property
             * @DisableInject 注解,指定该setter方法不进行依赖注入。 即忽略掉有@DisableInject注解的方法
             */
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }
            // 获取 setter 方法参数类型
            Class<?> pt = method.getParameterTypes()[0];
            // 参数是否是基础类型,如果是基础类型,则不注入
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                // 获取属性名,比如 setName 方法对应属性名 name
                String property = getSetterProperty(method);
                // 从 ObjectFactory 中获取依赖对象。
                // 如果是通过spring的,那就是取容器中对象了,需要提前设置好spring容器context。
                // name 为属性名。 而属性名是通过setter方法名截取出来的。 也就是说在这种情况下这个setter方法名不能随便定义。
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    // 通过反射调用 setter 方法设置依赖
                    method.invoke(instance, object);
                }
            } catch (Exception e) {
                logger.error("Failed to inject via method " + method.getName()
                        + " of interface " + type.getName() + ": " + e.getMessage(), e);
            }

        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

实际上就是取setter方法,取出属性名和类型,通过objectFactory得到对应的扩展类实现,然后通过method.invoke方法进行注入。从这里我们得到几点:

  1. dubbo支持自身SPI的注入和Spring的bean注入,使用Spring注入时需提前给SpringExtensionFactory设置spring的context–这个spring启动的时候设置进去就可以了。
  2. 使用的是setter方法的注入,也就是说需要在自己的扩展中定义setter方法。注意方法名不能随便写,name是通过方法名截取出来的。需要和你要注入的对象name值一致(spring的方式要beanName,SPI的方式就是对应扩展的name)
包装过程

前面已经分析过了包装是干什么的。简单来说就是类似spring的AOP机制,对原扩展进行代理,使功能更强大。

if (wrap) {

    List<Class<?>> wrapperClassesList = new ArrayList<>();
    if (cachedWrapperClasses != null) {
        wrapperClassesList.addAll(cachedWrapperClasses);
        wrapperClassesList.sort(WrapperComparator.COMPARATOR);
        Collections.reverse(wrapperClassesList);
    }

    if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
        // 循环创建 Wrapper 实例
        for (Class<?> wrapperClass : wrapperClassesList) {
            // 获取对应的wapper注解
            Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
            /**
             * 如果没有注解,则默认按上述条件认为是包装类
             * 如果有注解,则需要满足注解的matches条件和mismatches条件
             */
            if (wrapper == null
                    || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
                // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                /**
                 * 原来的instance被替换成包装类,同时把原instance作为构造方法参数注入,
                 * 其他扩展属性通过setter注入
                 */
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
    }
}

首先检查包装类。在前面加载class的时候已经写到缓存中了。

取@Wrapper注解,如果没有注解直接包装,如果有注解需要根据注解定义的规则和name进行匹配,满足条件的才进行包装。

包装的过程就是把原类型作为参数反射调用包装类的构造方法。然后再进行setter注入。

总结

至此Dubbo的SPI基本分析完了。简单总结一下:

  • 如何加载SPI配置的?
  • 如何进行依赖注入的?
  • 如何进行包装的?包装的目的是什么?

篇幅有点长了,下篇分析自适应扩展

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值