Dubbo源码阅读-DUBBO SPI

目录

1.概述

1.1 Java SPI 示例

1.2 Dubbo SPI 示例

2.源码

2.1 获取拓展对象

2.2 创建拓展对象

2.2.1 获取所有扩展类

2.2.2 Dubbo IOC

3.总结

4.参考文章

5.代码地址


1.概述

Dubbo SPI中常用的几个注解:

  1. @Spi:默认拓展实现类
  2. @Adaptive:自适应拓展实现类标志
  3. @Activate:自动激活条件的标记

这一篇我们先讲Dubbo Spi!

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 携程Apollo 就有通过 SPI 机制加载组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。

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

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

1.1 Java SPI 示例

定义接口:

public interface Car {
    String carName();
}

接下来定义两个实现类,分别为:


/**
 * @ClassName : Benz
 * @Description : 奔驰
 * @Author : xueguolin
 * @Date: 2020-10-31 14:52
 */
public class BenzCar implements Car {

    @Override
    public String carName() {
        return "奔驰";
    }
}


/**
 * @ClassName : BMWCar
 * @Description : 宝马
 * @Author : xueguolin
 * @Date: 2020-10-31 14:50
 */
public class BMWCar implements Car {
    @Override
    public String carName() {
        return "BMW";
    }
}

接下来 META-INF/services 文件夹下创建一个文件,名称为 Car 的全类名com.guolin.test.spi.Car, 文件内容为实现类的全限定的类名,如下:

com.guolin.test.spi.impl.BenzCar
com.guolin.test.spi.impl.BMWCar

接下来编写代码进行测试:


/**
 * @ClassName : JavaSpiTest
 * @Description : spi测试
 * @Author : xueguolin
 * @Date: 2020-10-31 14:54
 */
public class JavaSpiTest {

    @Test
    public void testJavaSpi() throws Exception {

        // 方式1:通过 Service.providers 获取实现类的实例
        Iterator<Car> providers = Service.providers(Car.class);
        System.out.println("Java SPI Service.providers");
        while (providers.hasNext()) {
            Car car = providers.next();
            System.out.println(car.carName());
        }

        // 方式2:通过 ServiceLoader.load 获取
        ServiceLoader<Car> load = ServiceLoader.load(Car.class);
        System.out.println("Java SPI ServiceLoader.load");
        Iterator<Car> iterator = load.iterator();
        while (iterator.hasNext()) {
            Car car = iterator.next();
            System.out.println(car.carName());
        }
    }
}

最后来看一下测试结果,如下:

我们的两个实现类被成功的加载,并输出了相应的内容。

1.2 Dubbo SPI 示例

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。

benzCar = com.guolin.test.spi.impl.BenzCar
bmwCar = com.guolin.test.spi.impl.BMWCar

与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。


@SPI
public interface Car {
    String carName();
}

接下来编写代码进行测试。


/**
 * @ClassName : DubboSPITest
 * @Description : dubboSpi测试
 * @Author : xueguolin
 * @Date: 2020-10-31 15:16
 */
public class DubboSPITest {
    @Test
    public void test() throws Exception {
        ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
        System.out.println("DUBBO SPI :");
        Car bmw = extensionLoader.getExtension("bmwCar");
        System.out.println(bmw.carName());
        Car benz = extensionLoader.getExtension("benzCar");
        System.out.println(benz.carName());
    }
}

最后来看一下测试结果,如下:

2.源码

上一章简单演示了 Dubbo SPI 的使用方法。我们首先通过 ExtensionLoader#getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader#getExtension 方法获取拓展类对象。这其中,getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。下面我们从 ExtensionLoader#getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。

2.1 获取拓展对象

首先检查缓存,缓存未命中则创建拓展对象

/**
     * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
     * will be thrown.
     */
    @SuppressWarnings("unchecked")
    public T getExtension(String name) {
        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();
        // dubbo check
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 创建拓展实例
                    instance = createExtension(name);
                    // 放到holder
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

2.2 创建拓展对象

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

    @SuppressWarnings("unchecked")
    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)) {
                // 循环创建 Wrapper 实例
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
                    // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                    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);
        }
    }

2.2.1 获取所有扩展类

 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表:


    private Map<String, Class<?>> getExtensionClasses() {
        // 从缓存中获取已加载的拓展类
        Map<String, Class<?>> classes = cachedClasses.get();
        // dubbo check
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 加载拓展类
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

    /**
     * synchronized in getExtensionClasses
     */
    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

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

    /**
     * extract and cache default extension name if exists
     */
    private void cacheDefaultExtensionName() {
        // 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation == null) {
            return;
        }

        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            // 对 SPI 注解内容进行切分
            String[] names = NAME_SEPARATOR.split(value);
            // 检测 SPI 注解内容是否合法,不合法则抛出异常
            if (names.length > 1) {
                throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            // 设置默认名称,参考 getDefaultExtension 方法
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }
加载指定文件夹下的配置文件

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                               boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
        // fileName = 文件夹路径 + type 全路径名,例如:/home/app/dubbo/META-INF/dubbo/com.test/Car.java
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
            // 获取类加载器:1.thread context ClassLoader 2.this class ClassLoader 3. system ClassLoader
            ClassLoader classLoader = findClassLoader();

            // try to load from ExtensionLoader's ClassLoader first
            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);
                }
            }

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

加载资源


    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();
                            }
                            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);
        }
    }
加载类,并通过 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 注解
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            // 设置 cachedAdaptiveClass缓存
            cacheAdaptiveClass(clazz, overridden);
        }
        // 检测 clazz 是否是 Wrapper 类型
        else if (isWrapperClass(clazz)) {
            // 存储 clazz 到 cachedWrapperClasses 缓存中
            cacheWrapperClass(clazz);
        }
        // 程序进入此分支,表明 clazz 是一个普通的拓展类
        else {
            // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                // 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 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
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                // 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,
                // 存储 name 到 Activate 注解对象的映射关系
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    // 存储 Class 到名称的映射关系
                    cacheName(clazz, n);
                    // 存储名称到 Class 的映射关系
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                }
            }
        }
    }

/**
     * put clazz in extensionClasses
     */
    private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name, boolean overridden) {
        Class<?> c = extensionClasses.get(name);
        if (c == null || overridden) {
            extensionClasses.put(name, clazz);
        } else if (c != clazz) {
            String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName();
            logger.error(duplicateMsg);
            throw new IllegalStateException(duplicateMsg);
        }
    }

2.2.2 Dubbo IOC

Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程对应的代码如下:


    private T injectExtension(T instance) {

        if (objectFactory == null) {
            return instance;
        }

        try {
            // 遍历目标类的所有方法
            for (Method method : instance.getClass().getMethods()) {
                // 判断是不是set方法
                if (!isSetter(method)) {
                    continue;
                }
                // 判断是不是存在不注入的注解 DisableInject
                /**
                 * Check {@link DisableInject} to see if we need auto injection for this property
                 */
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
                    String property = getSetterProperty(method);
                    // 从 ObjectFactory 中获取依赖对象
                    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;
    }

在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。

3.总结

本篇文章简单分别介绍了 Java SPI 与 Dubbo SPI 用法,并对 Dubbo SPI 的加载拓展类的过程进行了分析。另外,在 Dubbo SPI 中还有一块重要的逻辑这里没有进行分析,即 Dubbo SPI 的扩展点(@Adaptive)自适应机制。我们将会在下一篇文章中进行详细的分析。

4.参考文章

《dubbo spi :http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

5.代码地址

https://gitee.com/shandadadada/spi_demo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值