Dubbo源码篇---SPI

516 阅读9分钟

Dubbo的扩展机制是基于Dubbo SPI来是实现的,可以说SPI是Dubbo扩展机制的核心,那么想读懂Dubbo的源码,首先读懂Dubbo SPI源码是一个非常不错的选择。

什么是SPI

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

示例

package org.apache.dubbo.demo.provider.javaspi;
​
    public interface JavaSpi {
​
    void say(String str);
​
}
package org.apache.dubbo.demo.provider.javaspi;
​
public class DemoOneJavaSpi implements JavaSpi{
    @Override
    public void say(String str) {
        System.out.println("one:" + str);
    }
}
package org.apache.dubbo.demo.provider.javaspi;
​
public class DemoTwoJavaSpi implements JavaSpi{
  @Override
  public void say(String str) {
    System.out.println("two:" + str);
  }
}
​

文件配置路径:META-INF/services(此路径是固定的),文件名:org.apache.dubbo.demo.provider.javaspi.JavaSpi

org.apache.dubbo.demo.provider.javaspi.DemoOneJavaSpi
org.apache.dubbo.demo.provider.javaspi.DemoTwoJavaSpi
package org.apache.dubbo.demo.provider.javaspi;
​
import org.junit.jupiter.api.Test;
import java.util.ServiceLoader;
​
public class JavaSpiTest {
​
  @Test
  public void test1() {
    ServiceLoader<JavaSpi> load = ServiceLoader.load(JavaSpi.class);
    load.forEach(item -> item.say("demo"));
  }
​
}
​

Dubbo SPI

Dubbo并未使用Java原生的 SPI 机制,而是对其进行了增强,在满足了API扩展的前提下,提供了依赖注入的功能。与Java SPI不同的是,Dubbo SPI的配置文件是放在META-INF/dubbo目录下,其配置采用key-value的方式,且需扩展的接口需标注@SPI注解。

示例

package org.apache.dubbo.demo.provider.dubbospi;
​
import org.apache.dubbo.common.extension.SPI;
​
@SPI
public interface DubboSpi {
​
  void say(String str);
​
}

文件配置路径:META-INF/dubbo(此路径是固定的),文件名:org.apache.dubbo.demo.provider.javaspi.JavaSpi

one=org.apache.dubbo.demo.provider.dubbospi.DemoOneDubboSpi
two=org.apache.dubbo.demo.provider.dubbospi.DemoTwoDubboSpi
package org.apache.dubbo.demo.provider.dubbospi;
​
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.junit.jupiter.api.Test;
​
import java.util.ServiceLoader;
​
public class DubboSpiTest {
​
    @Test
    public void test2() {
        ExtensionLoader<JavaSpi> extensionLoader =
                ExtensionLoader.getExtensionLoader(DubboSpi.class);
        DubboSpi one = extensionLoader.getExtension("one");
        one.say("test one");
        JavaSpi two = extensionLoader.getExtension("two");
        two.say("test two");
    }
​
}

由上述例子可以大概知道,Dubbo SPI是封装在ExtensionLoader中实现的,是通过ExtensionLoader.getExtensionLoader(Class tClass)方法来获取其扩展的。

源码分析

获取扩展加载器

public class ExtensionLoader<T> {
  /**
   * 加载dubbo spi配置,type必须为interface,且必须有{@link SPI}标记
   */
  @SuppressWarnings("unchecked")
  public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
      throw new IllegalArgumentException("Extension type == null");
    }
    // 非interface抛出参数非法
    if (!type.isInterface()) {
      throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    // 无@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<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;
  }

  /**
   * 构造函数
   */
  private ExtensionLoader(Class<?> type) {
    // 需要扩展的接口类
    this.type = type;
    // 扩展类工厂
    // 此处为获取自适应扩展,在后面将Dubbo SPI自适应扩展机制时再
    // 如果为ExtensionFactory则为null,否则先获取到ExtensionFactory的扩展类加载器ExtensionLoader<ExtensionFactory>
    // 再通过ExtensionLoader<ExtensionFactory>的getAdaptiveExtension方法获取到具体的ExtensionFactory
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
  }
}

上面为获取扩展类加载器的源码解析,没有太复杂的地方,按照流程走下来,无非就是先获取缓存中的ExtensionLoader,不存在则创建并加入缓存。值得注意的是构造函数中为objectFactory赋值的操作,此处为获取自适应扩展,此处先不做分析,待后面分析Dubbo SPI自适应扩展机制时再进行详解。接下里继续分析Dubbo SPI,其入口方法为getExtension(String name)

获取扩展类实例

/**
   * 查找具有给定名称的扩展名。如果找不到指定的名称,则将引发{@link IllegalStateException}
   */
  @SuppressWarnings("unchecked")
  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)) {
			// 获取默认的扩展实现类,默认的扩展由@SPI注解指定,如果未配置,则为null
      return getDefaultExtension();
    }
    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;
  }

getExtension方法内部有两种获取实例的方式,第一种是获取默认的实例,其名称由作用在扩展类上的@SPI注解指定,第二种是根据扩展类名称获取实例,第一种回回归到第二种,所以我们接下来继续分析第二种获取扩展实例的源码。从上面可以看到,第二种获取扩展实例的方式中有双重检验,先获取缓存,缓存不存在则创建。值得注意的是这里的双重校验及加锁操作,为啥锁instance对象能保证线程安全呢?我们可以进入getOrCreateHolder这个方法,虽然这里也没有加锁,但是它使用的是ConcurrentMap,这个map是线程安全的,从而保证了线程的安全。

创建扩展实例

/**
     * 根据名称创建扩展类
     * @param name 扩展类名称
     * @param wrap 是否需要使用包装类
     * @return T
     */
    @SuppressWarnings("unchecked")
    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)) {
                    // 这个方法看似简单,就是简单的遍历,实际上在存在多个包装类的情况下,会将上一次循环获取到的包装类通过构造方法注入到新的包装类
                    // 这样就保证了在存在多个包装类的情况下,多个包装类都可以可行,值得一提的是,可以对不同的包装类指定执行顺序
                    // TODO 如果实现包装类的排序待定
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        // 如果扩展类使用类@Wrapper标注,表示该类不要使用包装类
                        if (wrapper == null
                                || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                            //
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        }
                    }
                }
            }
            // 初始化扩展类,主要是判断该扩展类是否实现类Lifecycle,如果实现了则执行该类的initialize()方法
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

从createExtension方法的方法体中可以看出,这个方法的逻辑显得还是挺复杂的,逻辑顺序为获取扩展类、实例化扩展类、IOC注入、使用代理、初始化扩展类,这几个顺序来进行详细的分析。

获取扩展类
 /**
 * 获取扩展类,此处有双重检查
 */
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;
}

getExtensionClasses方法的代码不难理解,缓存不存在就加载扩展类,并添加到缓存,接下来分析loadExtensionClasses方法。

/**
 * 加载扩展类,在getExtensionClasses中同步
 */
private Map<String, Class<?>> loadExtensionClasses() {
    // 从@SPI中提取并缓存默认扩展名
    cacheDefaultExtensionName();

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

    // 遍历加载策略,加载Dubbo SPI配置文件
    for (LoadingStrategy strategy : strategies) {
        // 此处到了加载并解析Dubbo SPI配置文件的方法
        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
      	// 兼容的老版本,阿里团队将Dubbo捐献给Apach后,包名做了改动
        loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    }

    return extensionClasses;
}

loadExtensionClasses方法做了两个动作,第一个动作是解析扩展类上的@SPI注解,并获取其默认值,默认值不为空的话,则设置为默认的扩展类的实现类的名称,并添加到缓存;第二个是遍历Dubbo SPI的加载策略,通过loadDirectory方法加载并解析Dubbo SPI的配置文件。

/**
 * 加载并解析Dubbo SPI配置
 * @param extensionClasses 扩展类
 * @param dir 需要加载的包名
 * @param type 需要加载类
 * @param extensionLoaderClassLoaderFirst 扩展加载器类加载器优先
 * @param overridden 覆盖
 * @param excludedPackages 排除的包
 */
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                           boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
    // 要加载的文件名 格式如下
    // META-INF/services/org.apache.dubbo.common.context.FrameworkExt
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls = null;
        // 获取类加载器
        ClassLoader classLoader = findClassLoader();

        // 尝试先从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);
            }
        }

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

loadDirectory方法按流程下来先是解析完整的配置文件路径,获取ClassLoader,然后通过ClassLoader获取所有资源链接,最后通过loadResource加载资源。

/**
 * 加载并解析Dubbo SPI配置
 * @param extensionClasses 扩展类
 * @param classLoader 类加载器
 * @param resourceURL SPI资源文件
 * @param overridden 是否覆盖
 * @param excludedPackages 排除的包
 */
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) {
                            // 获取Dubbo SPI配置文件的key
                            name = line.substring(0, i).trim();
                            // 获取Dubbo SPI配置文件的value,如one=org.apache.dubbo.demo.provider.javaspi.DemoOneDubboSpi
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
                            // 加载类
                            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);
    }
}

loadResource方法只做了两件事,首先是读取资源信息,按照Dubbo SPI配置文件的约定,解析出每一行的扩展类实现的名称及类名,最后调用loadClass方法加载对应的扩展类。

/**
 *
 * @param extensionClasses 扩展类
 * @param resourceURL 资源文件
 * @param clazz 需要加载的类
 * @param name 需要加载类的名称
 * @param overridden 是否覆盖
 */
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)) {
        // 设置缓存
        cacheAdaptiveClass(clazz, overridden);
     // 检测类是否是wrapper类型
    } else if (isWrapperClass(clazz)) {
        // 设置缓存
        cacheWrapperClass(clazz);
    // 到了这里表示这是一个普通的扩展类
    } else {
        // 校验该类是否有默认的构造方法,没有则抛出异常
        clazz.getConstructor();
        // 如果name为null,则尝试从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 注解,则使用 names 数组的第一个元
            // 存储 name 到 Activate 注解对象的映射关系
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 存储 Class 到名称的映射关系
                cacheName(clazz, n);
                // 存储名称到Class的映射关系
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

loadClass方法分为了三种情况来加载扩展类:扩展类是否有@Adaptive注解、是否为包装类、一个普通的扩展类,这里没有太复杂的逻辑,无非就是操作各种缓存如cacheAdaptiveClass和cacheWrapperClasses、cacheNames等,值得注意的是前两种情况,现在来看的话,我们并不知道这样做的目的是啥,其实第一种情况是用于Dubbo SPI的自适应扩展机制,这一部分我们将在Dubbo SPi自适应扩展中有详解,不管是第一种还是第二种,在Wrapper和Adaptive详解中有讲解其作用、使用方式、原理。

至此,获取扩展类阶段结束,我们再回到获取扩展类实例往下看,在getExtensionClasses方法执行完毕后,就是通过反射实例化该扩展类,然后通过injectExtension方法对实例进行以来注入

Dubbo IOC
/**
     * IOC,基于setter方法来实现
     * @param instance 扩展类实例
     */
    private T injectExtension(T instance) {

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

        try {
          	// 遍历该实例的方法
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {
                    continue;
                }
                /**
                 * 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 {
                    String property = getSetterProperty(method);
                    Object object = objectFactory.getExtension(pt, property);
                    if (object != null) {
                        // 注入
                        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;
    }

如上,Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。其中不需要注入的依赖,可用@DisableInject注解作用在该setter方法上。

我们再回到创建扩展类实例可以看到,在结束了IOC注入后,将执行是否使用包装类的逻辑,在后面的Dubbo源码篇---Wrapper详解中将会详细的分析。

最后对Dubbo SPI的加载流程做个总结:

  1. 通过ExtensionLoader.getExtensionLoader(Class clazz)方法获取到扩展类加载器
  2. 通过ExtentsionLoader中的getExtensionClass()方法对Dubbo SPI配置文件进行解析并加入缓存(包括不限于名称、扩展类)
  3. 通过getExtension(String name)实例化扩展类并加入缓存
  4. 对已实例化的扩展类进行IOC注入,基于setter方式
  5. 返回扩展类实例