Dubbo 的 SPI 核心原理机制源码级解析

1. Java-SPI 机制

SPI 全称为(Service Provider Interface),是 Java 提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件,SPI 的作用就是为这些被扩展的 API 寻找服务实现。

常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口是由 Java 核心库来提供,而 SPI 的实现则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)中。

1.1. 基本思想

其实 Java SPI 实际上是 “基于接口的编程+策略模式+配置文件” 组合实现的动态加载机制。

系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。

为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。Java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似 IOC 的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以 SPI 的核心思想就是解耦

1.2. 使用场景

API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。从使用人员上来说,API 直接被应用开发人员使用。

SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。从使用人员上来说,SPI 是被框架扩展人员使用。

1.3. 具体约定

Java SPI 的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在 jar 包的 META-INF/services/ 目录里同时创建一个以服务接口命名的文件,该文件的内容是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该 jar 包 META-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。JDK 提供服务实现查找的一个工具类:java.util.ServiceLoader。

1.4. 代码示例

既然是 SPI,那么就必须先定义好接口。其次,就是定义好接口的实现类。

  1. 新建一个 Promotion 接口
public interface Promotion {

    BigDecimal getDiscount();

}
  1. 新建一个 GrouponPromotion 实现类
public class GrouponPromotion implements Promotion {

    @Override
    public BigDecimal getDiscount() {
        System.out.println("团购优惠一元");
        return new BigDecimal(1);
    }

}
  1. 新建一个 SeckillPromotion 实现类
public class SeckillPromotion implements Promotion {

    @Override
    public BigDecimal getDiscount() {
        System.out.println("秒杀优惠二元");
        return new BigDecimal(2);
    }

}
  1. 在 resources 目录下 建立 META-INF/services/ 文件夹
Dubbo 的 SPI 核心原理机制源码级解析
image.png
  1. 新建一个名为接口全名的文件 com.yly.region.spi.Promotion,内容如下:
com.yly.region.spi.impl.GrouponPromotion
com.yly.region.spi.impl.SeckillPromotion
  1. 新建一个测试类 Test,使用 java.util.ServiceLoader 运行看下结果
public class Test {

    public static void main(String[] agrs) {
        ServiceLoader<Promotion> loaders = ServiceLoader.load(Promotion.class);
        for (Promotion promotion : loaders) {
            promotion.getDiscount();
        }
    }

}
  1. 可以看到运行结果
团购优惠一元
秒杀优惠二元

2. Dubbo 的 SPI 应用与原理

SPI (Service Provider Interface)本质是 将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

想要学习 Dubbo 的源码,SPI 机制务必弄懂。我们已经了解了 Java SPI 的用法,接下来我们了解下 Dubbo SPI 的用法,再分析 Dubbo SPI 的源码。

2.1. getExtensionLoader()

在 Dubbo SPI 示例方法中,我们首先通过 ExtensionLoader 的 getExtensionLoader() 方法获取一个接口的 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension() 方法获取拓展类对象,源码如下,首先是 getExtensionLoader() 方法:

/**
 * {@link org.apache.dubbo.rpc.model.ApplicationModel}, {@code DubboBootstrap} and this class are
 * at present designed to be singleton or static (by itself totally static or uses some static fields).
 * So the instances returned from them are of process or classloader scope. If you want to support
 * multiple dubbo servers in a single process, you may need to refactor these three classes.
 * <p>
 * Load dubbo extensions
 * <ul>
 * <li>auto inject dependency extension </li>
 * <li>auto wrap extension in wrapper </li>
 * <li>default extension is an adaptive instance</li>
 * </ul>
 *
 * @see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider">Service Provider in Java 5</a>
 * @see org.apache.dubbo.common.extension.SPI
 * @see org.apache.dubbo.common.extension.Adaptive
 * @see org.apache.dubbo.common.extension.Activate
 */

public class ExtensionLoader<T{

    // ...
    
    /**
     * 扩展类加载器缓存,就是扩展点ExtendsLoader实例缓存;key=扩展接口 value=扩展类加载器
     */

    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);

    @SuppressWarnings("unchecked")
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        // 校验传进的type类是否为空
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        // 校验传进的type类是否为接口
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
        // 校验传进的type类是否有@SPI注解
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }
        // 从ExtensionLoader缓存中查询是否已经存在对应类型的ExtensionLoader实例
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            // 没有就new一个ExtensionLoader实例,并存入本地缓存
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
    
}

getExtensionLoader() 会对传进的接口进行校验,其中包括是否有 @SPI 注解校验,这也是在接口上需加  @SPI 的原因。然后从 EXTENSION_LOADERS 缓存中获取该接口类型的 ExtensionLoader,如果获取不到,则创建一个该接口类型的 ExtensionLoader 放入到缓存中,并返回该 ExtensionLoader。

public class ExtensionLoader<T{

    // ...
    
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        // type通常不为ExtensionFactory类,
        // 则objectFactory为ExtensionFactory接口的默认扩展类AdaptiveExtensionFactory
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
    
}

注意这里创建 ExtensionLoader 对象的构造方法如下:ExtensionLoader.getExtensionLoader() 获取ExtensionFactory 接口的拓展类,再通过 getAdaptiveExtension 从拓展类中获取目标拓展类。它会设置该接口对应的  objectFactory 常量为 AdaptiveExtensionFactory。因为 AdaptiveExtensionFactory 类上加了 @Adaptive 注解,为什么是 AdaptiveExtensionFactory 原因在之后的文章会解释,且 objectFactory 也会在后面用到。

2.2. getExtension()

当通过 ExtensionLoader.getExtensionLoader() 取到接口的加载器 Loader 之后,再通过 getExtension() 方法获取需要拓展类对象。该方法的整个执行流程如下图所示:

Dubbo 的 SPI 核心原理机制源码级解析
1583212844170830.png

参照执行流程图,拓展类对象的获取源码如下:

public class ExtensionLoader<T{

    // ...

   /**
    * 扩展点实例缓存 key=扩展点名称,value=扩展实例的Holder实例
    */

    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    
    /**
     * 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) {
        return getExtension(name, true);
    }

    /**
     * 获取接口拓展类实例
     * 1.检查缓存中是否存在
     * 2.创建并返回拓展类实例
     * @param name  需要获取的配置文件中拓展类的key
     * @return
     */

    public T getExtension(String name, boolean wrap) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            // 获取默认的拓展实现类, 即@SPI注解上的默认实现类, 如@SPI("xxx")
            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;
    }
    
    /**
     * 获取或者创建一个Holder对象
     */

    private Holder<Object> getOrCreateHolder(String name) {
        // 首先通过扩展名从扩展实例缓存中获取Holder对象
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            // 如果没有获取到就new一个空的Holder实例存入缓存
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }
    
}

上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。Dubbo 中包含了大量的扩展点缓存。这个就是典型的 使用空间换时间 的做法。也是 Dubbo 性能强劲的原因之一,包括

  • 扩展点Class缓存 ,Dubbo SPI 在获取扩展点时,会优先从缓存中读取,如果缓存中不存在则加载配置文件,根据配置将 Class 缓存到内存中,并不会直接初始化。
  • 扩展点实例缓存 ,Dubbo 不仅会缓存 Class,还会缓存 Class 的实例。每次取实例的时候会优先从缓存中取,取不到则从配置中加载,实例化并缓存到内存中。

2.3. createExtension()

下面我们来看一下创建拓展对象的过程:

public class ExtensionLoader<T{

    // ...
    
    /**
     * 扩展实例存入内存中缓存起来;key=扩展类 ;value=扩展类实例
     */

    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
    
    /**
     * 创建拓展类实例,包含如下步骤
     * 1. 通过 getExtensionClasses 获取所有的拓展类,从配置文件加载获取拓展类的map映射
     * 2. 通过反射创建拓展对象
     * 3. 向拓展对象中注入依赖(IOC)
     * 4. 将拓展对象包裹在相应的 Wrapper 对象中(AOP)
     * @param name 需要获取的配置文件中拓展类的key
     * @return 拓展类实例
     */

    @SuppressWarnings("unchecked")
    private T createExtension(String name, boolean wrap) {
        // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的map,
        // 再根据拓展项名称从map中取出相应的拓展类即可
        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);
            }
            // 向实例中注入依赖,通过setter方法自动注入对应的属性实例
            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 实例,形成 Wrapper 包装链
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        if (wrapper == null
                                || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                            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);
        }
    }
    
}

创建拓展类对象步骤分别为:

  1. 通过 getExtensionClasses 从配置文件中加载所有的拓展类,再通过名称获取目标拓展类;
  2. 通过反射创建拓展对象;
  3. 向拓展对象中注入依赖;
  4. 将拓展对象包裹在相应的 Wrapper 对象中

第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。我们先重点分析 getExtensionClasses() 方法的逻辑。

2.4. getExtensionClasses()

在通过 name 获取拓展类之前,首先需要根据配置文件解析出拓展项名称与拓展类的映射 map,之后再根据拓展项名称从 map 中取出相应的拓展类即可。getExtensionClasses() 方法源码如下:

public class ExtensionLoader<T{

    // ...
    
  /**
   * 扩展点Class缓存 key=扩展名 ,value=对应的class对象
   */

    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    
  /**
   * 解析配置文件中接口的拓展项名称与拓展类的映射表map
   * @return
   */

    private Map<String, Class<?>> getExtensionClasses() {
        // 从缓存中获取已加载的拓展点class
        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() 加载拓展类,缓存避免了多次读取配置文件的耗时。

2.5. loadExtensionClasses()

下面分析 loadExtensionClasses() 方法加载配置文件的逻辑:

public class ExtensionLoader<T{

    // ...
    
    private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
    
    /**
     * Load all {@link Prioritized prioritized} {@link LoadingStrategy Loading Strategies} via {@link ServiceLoader}
     *
     * @return non-null
     * @since 2.7.7
     */

    private static LoadingStrategy[] loadLoadingStrategies() {
        return stream(load(LoadingStrategy.class).spliterator(), false)
                .sorted()
                .toArray(LoadingStrategy[]::new)
;
    }
    
    /**
     * 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;
    }
    
}

loadExtensionClasses() 方法总共做了两件事情:

  1. 首先该方法调用 cacheDefaultExtensionName() 对 SPI 注解进行解析,获取并缓存接口的 @SPI 注解上的默认拓展类在 cachedDefaultName
  2. 再调用  loadDirectory() 方法加载指定文件夹配置文件

SPI 注解解析过程比较简单,源码如下。只允许一个默认拓展类。

public class ExtensionLoader<T{

    // ...
    
    /**
     * 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) {
            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));
            }
            // 设置默认拓展类名称
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }
    
}

从源码中可以看出 loadExtensionClasses() 方法加载配置文件的路径有三个,分别为 META-INF/dubbo/internal/, META-INF/dubbo/, META-INF/services/ 三个文件夹。方法源码如下:

public class ExtensionLoader<T{

    // ...
    
    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        loadDirectory(extensionClasses, dir, type, falsefalse);
    }
    
    /**
     * 加载配置文件内容
     * @param extensionClasses 拓展类map
     * @param dir 文件夹路径
     * @param type 接口名称
     * @param extensionLoaderClassLoaderFirst 是否先加载ExtensionLoader的ClassLoader
     */

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                               boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages)
 
{
        // fileName = 文件夹路径 + type 全限定名
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
            // 获取当前线程的类加载器
            ClassLoader classLoader = findClassLoader();

            // try to load from ExtensionLoader's ClassLoader first
            if (extensionLoaderClassLoaderFirst) {
                // 获取加载ExtensionLoader.class这个类的类加载器
                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
                // 如果extensionLoaderClassLoaderFirst=true时,且这两个类加载器不同,
                // 就优先使用 extensionLoaderClassLoader
                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();
                     // 解析并加载配置文件中配置的实现类到extensionClasses中去
                    loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
    
}

首先找到文件夹下的配置文件,文件名需为接口全限定名。利用类加载器获取文件资源链接,再解析配置文件中配置的实现类添加到 extensionClasses 中。我们继续看 loadResource() 是如何加载资源的:

public class ExtensionLoader<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;
                String clazz = null;
                // 按行读取配置内容
                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();
                                clazz = line.substring(i + 1).trim();
                            } else {
                                clazz = line;
                            }
                            if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
                                // 通过反射加载类,并通过 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);
        }
    }
    
}

loadResource() 方法用于读取和解析配置文件,按行读取配置文件,每行以等于号 = 为界,截取键与值,并通过反射加载类,最后通过 loadClass() 方法加载扩展点实现类的 class 到 map 中,并对加载到的 class 进行分类缓存。loadClass() 方法实现如下:

public class ExtensionLoader<T{

    // ...

    /**
     * 加载扩展点实现类的class到map中,并对加载到的class进行分类缓存
     * 比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等
     * @param extensionClasses 装载配置文件类的容器
     * @param resourceURL 配置文件资源URL
     * @param clazz 扩展点实现类的class
     * @param name  扩展点实现类的名称,配置文件一行中的key
     * @throws NoSuchMethodException
     */

    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden)
 throws NoSuchMethodException 
{
        // 判断配置的实现类是否是实现了type接口
        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 注解,表示这个类就是一个自适应实现类,缓存到cachedAdaptiveClass
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            // 检测 clazz 是否是 Wrapper 类型,判断依据是是否有参数为该接口类的构造方法,缓存到cachedWrapperClasses
            cacheAdaptiveClass(clazz, overridden);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
            clazz.getConstructor();
            // 如果配置文件中key的name 为空,则尝试从Extension注解中获取 name,或使用小写的类名作为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分割为字符串数组
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                // 如果扩展点配置的实现类使用了@Activate注解,就将对应的注解信息缓存起来
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    // 缓存扩展点实现类class和扩展点名称的对应关系
                    cacheName(clazz, n);
                    // 最后将class存入extensionClasses
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                }
            }
        }
    }
    
}

loadClass() 方法实现了扩展点的分类缓存功能,如包装类,自适应扩展点实现类,普通扩展点实现类等分别进行缓存。

2.6. @Adaptive

需要注意的是自适应扩展点实现类 @Adaptive 注解,该注解源码如下:

/**
 * Provide helpful information for {@link ExtensionLoader} to inject dependency extension instance.
 *
 * @see ExtensionLoader
 * @see URL
 */

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    /**
     * Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
     * in the URL, and the parameter names are given by this method.
     * <p>
     * If the specified parameters are not found from {@link URL}, then the default extension will be used for
     * dependency injection (specified in its interface's {@link SPI}).
     * <p>
     * For example, given <code>String[] {"key1", "key2"}</code>:
     * <ol>
     * <li>find parameter 'key1' in URL, use its value as the extension's name</li>
     * <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
     * <li>use default extension if 'key2' doesn't exist either</li>
     * <li>otherwise, throw {@link IllegalStateException}</li>
     * </ol>
     * If the parameter names are empty, then a default parameter name is generated from interface's
     * class name with the rule: divide classname from capital char into several parts, and separate the parts with
     * dot '.', for example, for {@code org.apache.dubbo.xxx.YyyInvokerWrapper}, the generated name is
     * <code>String[] {"yyy.invoker.wrapper"}</code>.
     *
     * @return parameter names in URL
     */

    String[] value() default {};

}

该注解的作用是决定哪个自适应拓展类被注入,该目标拓展类是由 URL 中的参数决定,URL 中参数 key 由该注解的 value 给出,该 key 的 value 作为目标拓展类名称。

  • 如果注解中有多个值,则根据下标从小到大去 URL 中查找有无对应的 key,一旦找到就用该 key 的 value 作为目标拓展类名称。
  • 如果这些值在 url 中都没有对应的 key,使用 SPI 上的默认值。

@Adaptive 注解可以作用的类上与方法上, 绝大部分情况下,该注解是作用在方法上,@Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时, Dubbo 则会为该方法生成代理类。@Adaptive 注解在接口方法上,表示拓展的加载逻辑需由框架自动生成。注解在类上,表示拓展的加载逻辑由人工编码完成。

上述的 loadClass() 扫描的是作用在类上。在 Dubbo 中,仅有两个类被 @Adaptive 注解了,分别是  AdaptiveCompiler 和  AdaptiveExtensionFactory。loadClass() 方法设置缓存 cacheAdaptiveClass 会导致接口的 cacheAdaptiveClass 不为空,后面都会默认用这个拓展类,优先级最高。

回到主线,当执行完 loadClass() 方法,配置文件中的所有拓展类已经被加载到 map 中,到此,关于缓存类加载的过程就分析完了。

2.7. Dubbo AOP

当执行完 injectExtension(T instance) 方法,在 createExtension(String name)就开始执行 wrapper 的包装,类似于 Spring 中的 AOP,Dubbo 运用了装饰器模式

    if (wrap) {

        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);
                if (wrapper == null
                        || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
        }

    }

这里的 cachedWrapperClasses 通过前面的分析已经知道,就是在解析配置文件时判断是否是 Wrapper 类型的拓展类,++ 判断依据为构造方法中是否有参数为该接口类 ++,则缓存到 cachedWrapperClasses。

执行 wrapperClass.getConstructor(type).newInstance(instance) 将获取包装类的构造方法,方法的参数就是该接口类型,并通过反射生成一个包含该拓展类实例的包装对象,再通过 injectExtension 注入包装对象的依赖。如此循环,得到成 Wrapper 包装链。这里需注意的是, 配置文件中内容靠后的包装类会包装在相对外层

我们用 Dubbo AOP 类似的方法,改造在 章节1 中的代码,试试效果:

  1. 先添加 POM 文件依赖
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.9</version>
        </dependency>
  1. 修改 Promotion 接口,加上 @SPI 标签
import org.apache.dubbo.common.extension.SPI;
import java.math.BigDecimal;

@SPI
public interface Promotion {

    BigDecimal getDiscount();

}
  1. 新建一个 PromotionWrapper 包装类
public class PromotionWrapper implements Promotion {

    private Promotion promotion;

    public PromotionWrapper(Promotion promotion) {
        this.promotion = promotion;
    }

    @Override
    public BigDecimal getDiscount() {
        System.out.println("操作之前");
        BigDecimal result = promotion.getDiscount();
        System.out.println("操作之后");
        return result;
    }

}
  1. 修改文件 com.yly.region.spi.Promotion,内容如下
groupon=com.yly.region.spi.impl.GrouponPromotion
seckill=com.yly.region.spi.impl.SeckillPromotion
com.yly.region.spi.PromotionWrapper
  1. 新建一个测试类 TestAOP,使用 Dubbo 的 ExtensionLoader 运行看下结果
public class TestAOP {

    public static void main(String[] agrs) {
        ExtensionLoader<Promotion> extensionLoader = ExtensionLoader.getExtensionLoader(Promotion.class);
        Promotion promotion = extensionLoader.getExtension("groupon");
        System.out.println(promotion.getDiscount());
    }

}
  1. 可以看到运行结果
操作之前
团购优惠一元
操作之后
1

运行结果与我们预想的一致,实现了 Wrapper 类的切面功能。


原文始发于微信公众号(白菜说技术):Dubbo 的 SPI 核心原理机制源码级解析

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/172720.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!