需要特别说明的是,本篇文章以及本系列其他文章所分析的源码版本均为 dubbo-2.6.4。因此大家在阅读文章的过程中,需注意将代码版本切换到 dubbo-2.6.4 tag 上。

前面简单介绍了 SPI 机制的原理,本节通过一个示例演示 Java SPI 的使用方法。首先,我们定义一个接口,名称为 Robot。

接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。

  1. @Override
  2. public void sayHello() {
  3. System.out.println("Hello, I am Optimus Prime.");
  4. }
  5. }
  6. public class Bumblebee implements Robot {
  7. @Override
  8. public void sayHello() {
  9. System.out.println("Hello, I am Bumblebee.");
  10. }
  11. }

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

  1. org.apache.spi.OptimusPrime
  2. org.apache.spi.Bumblebee

做好所需的准备工作,接下来编写代码进行测试。

  1. public class JavaSPITest {
  2. @Test
  3. public void sayHello() throws Exception {
  4. ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
  5. System.out.println("Java SPI");
  6. serviceLoader.forEach(Robot::sayHello);
  7. }
  8. }

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

从测试结果可以看出,我们的两个实现类被成功的加载,并输出了相应的内容。关于 Java SPI 的演示先到这里,接下来演示 Dubbo SPI。

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

  1. public class DubboSPITest {
  2. @Test
  3. public void sayHello() throws Exception {
  4. ExtensionLoader<Robot> extensionLoader =
  5. ExtensionLoader.getExtensionLoader(Robot.class);
  6. Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
  7. optimusPrime.sayHello();
  8. Robot bumblebee = extensionLoader.getExtension("bumblebee");
  9. bumblebee.sayHello();
  10. }
  11. }

测试结果如下:

源码导读 - Dubbo SPI - 图2

Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性,这些特性将会在接下来的源码分析章节中一一进行介绍。

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

  1. public T getExtension(String name) {
  2. if (name == null || name.length() == 0)
  3. throw new IllegalArgumentException("Extension name == null");
  4. if ("true".equals(name)) {
  5. // 获取默认的拓展实现类
  6. return getDefaultExtension();
  7. }
  8. // Holder,顾名思义,用于持有目标对象
  9. Holder<Object> holder = cachedInstances.get(name);
  10. if (holder == null) {
  11. cachedInstances.putIfAbsent(name, new Holder<Object>());
  12. holder = cachedInstances.get(name);
  13. }
  14. Object instance = holder.get();
  15. // 双重检查
  16. if (instance == null) {
  17. synchronized (holder) {
  18. instance = holder.get();
  19. if (instance == null) {
  20. // 创建拓展实例
  21. instance = createExtension(name);
  22. // 设置实例到 holder 中
  23. holder.set(instance);
  24. }
  25. }
  26. }
  27. return (T) instance;
  28. }

上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。

  1. private T createExtension(String name) {
  2. // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
  3. Class<?> clazz = getExtensionClasses().get(name);
  4. throw findException(name);
  5. }
  6. try {
  7. if (instance == null) {
  8. // 通过反射创建实例
  9. EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
  10. instance = (T) EXTENSION_INSTANCES.get(clazz);
  11. }
  12. // 向实例中注入依赖
  13. injectExtension(instance);
  14. Set<Class<?>> wrapperClasses = cachedWrapperClasses;
  15. if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
  16. // 循环创建 Wrapper 实例
  17. for (Class<?> wrapperClass : wrapperClasses) {
  18. // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
  19. // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
  20. instance = injectExtension(
  21. (T) wrapperClass.getConstructor(type).newInstance(instance));
  22. }
  23. }
  24. return instance;
  25. } catch (Throwable t) {
  26. throw new IllegalStateException("...");
  27. }
  28. }

createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:

  • 通过 getExtensionClasses 获取所有的拓展类
  • 通过反射创建拓展对象
  • 向拓展对象中注入依赖
  • 将拓展对象包裹在相应的 Wrapper 对象中
    以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。在接下来的章节中,将会重点分析 getExtensionClasses 方法的逻辑,以及简单介绍 Dubbo IOC 的具体实现。

我们在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>),之后再根据拓展项名称从映射关系表中取出相应的拓展类即可。相关过程的代码分析如下:

  1. private Map<String, Class<?>> loadExtensionClasses() {
  2. // 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的
  3. final SPI defaultAnnotation = type.getAnnotation(SPI.class);
  4. if (defaultAnnotation != null) {
  5. String value = defaultAnnotation.value();
  6. if ((value = value.trim()).length() > 0) {
  7. // 对 SPI 注解内容进行切分
  8. String[] names = NAME_SEPARATOR.split(value);
  9. // 检测 SPI 注解内容是否合法,不合法则抛出异常
  10. if (names.length > 1) {
  11. throw new IllegalStateException("more than 1 default extension name on extension...");
  12. }
  13. // 设置默认名称,参考 getDefaultExtension 方法
  14. if (names.length == 1) {
  15. cachedDefaultName = names[0];
  16. }
  17. }
  18. }
  19. Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
  20. // 加载指定文件夹下的配置文件
  21. loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
  22. loadDirectory(extensionClasses, DUBBO_DIRECTORY);
  23. loadDirectory(extensionClasses, SERVICES_DIRECTORY);
  24. return extensionClasses;
  25. }

loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件。SPI 注解解析过程比较简单,无需多说。下面我们来看一下 loadDirectory 做了哪些事情。

  1. private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
  2. // fileName = 文件夹路径 + type 全限定名
  3. String fileName = dir + type.getName();
  4. try {
  5. Enumeration<java.net.URL> urls;
  6. ClassLoader classLoader = findClassLoader();
  7. // 根据文件名加载所有的同名文件
  8. if (classLoader != null) {
  9. urls = classLoader.getResources(fileName);
  10. } else {
  11. urls = ClassLoader.getSystemResources(fileName);
  12. }
  13. if (urls != null) {
  14. while (urls.hasMoreElements()) {
  15. java.net.URL resourceURL = urls.nextElement();
  16. // 加载资源
  17. loadResource(extensionClasses, classLoader, resourceURL);
  18. }
  19. }
  20. logger.error("...");
  21. }
  22. }

loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。我们继续跟下去,看一下 loadResource 方法的实现。

  1. ClassLoader classLoader, java.net.URL resourceURL) {
  2. try {
  3. BufferedReader reader = new BufferedReader(
  4. new InputStreamReader(resourceURL.openStream(), "utf-8"));
  5. try {
  6. String line;
  7. // 按行读取配置内容
  8. while ((line = reader.readLine()) != null) {
  9. // 定位 # 字符
  10. final int ci = line.indexOf('#');
  11. if (ci >= 0) {
  12. // 截取 # 之前的字符串,# 之后的内容为注释,需要忽略
  13. line = line.substring(0, ci);
  14. }
  15. line = line.trim();
  16. if (line.length() > 0) {
  17. try {
  18. String name = null;
  19. int i = line.indexOf('=');
  20. if (i > 0) {
  21. // 以等于号 = 为界,截取键与值
  22. name = line.substring(0, i).trim();
  23. line = line.substring(i + 1).trim();
  24. }
  25. if (line.length() > 0) {
  26. // 加载类,并通过 loadClass 方法对类进行缓存
  27. loadClass(extensionClasses, resourceURL,
  28. Class.forName(line, true, classLoader), name);
  29. }
  30. } catch (Throwable t) {
  31. IllegalStateException e = new IllegalStateException("Failed to load extension class...");
  32. }
  33. }
  34. }
  35. } finally {
  36. reader.close();
  37. }
  38. } catch (Throwable t) {
  39. logger.error("Exception when load extension class...");
  40. }
  41. }

loadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作。loadClass 方法用于主要用于操作缓存,该方法的逻辑如下:

如上,loadClass 方法操作了不同的缓存,比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等。除此之外,该方法没有其他什么逻辑了。

到此,关于缓存类加载的过程就分析完了。整个过程没什么特别复杂的地方,大家按部就班的分析即可,不懂的地方可以调试一下。接下来,我们来聊聊 Dubbo IOC 方面的内容。

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

  1. private T injectExtension(T instance) {
  2. try {
  3. if (objectFactory != null) {
  4. // 遍历目标类的所有方法
  5. for (Method method : instance.getClass().getMethods()) {
  6. // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
  7. if (method.getName().startsWith("set")
  8. && method.getParameterTypes().length == 1
  9. && Modifier.isPublic(method.getModifiers())) {
  10. // 获取 setter 方法参数类型
  11. Class<?> pt = method.getParameterTypes()[0];
  12. try {
  13. // 获取属性名,比如 setName 方法对应属性名 name
  14. String property = method.getName().length() > 3 ?
  15. method.getName().substring(3, 4).toLowerCase() +
  16. method.getName().substring(4) : "";
  17. // 从 ObjectFactory 中获取依赖对象
  18. Object object = objectFactory.getExtension(pt, property);
  19. if (object != null) {
  20. // 通过反射调用 setter 方法设置依赖
  21. method.invoke(instance, object);
  22. }
  23. } catch (Exception e) {
  24. logger.error("fail to inject via method...");
  25. }
  26. }
  27. }
  28. }
  29. } catch (Exception e) {
  30. logger.error(e.getMessage(), e);
  31. }
  32. }

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

Dubbo IOC 目前仅支持 setter 方式注入,总的来说,逻辑比较简单易懂。

好了,本篇文章就先到这里了。如果文章中有错误不妥之处,欢迎大家提 issue 进行反馈,或者提 pull request 进行修正。让我们携手共建 Dubbo 社区。