Dubbo SPI 源码分析(基于2.7.8-release)

一、SPI介绍

引自Dubbo官方教程:https://dubbo.apache.org/zh/docs/v2.7/dev/source/dubbo-spi/

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

二、JDK SPI

1. 实现原理

JDK 自带的 SPI 是 通过 ServiceLoader 类实现,原理是通过读取 META-INF/services 下的文件名为接口全限定名文件内容来加载实现类,文件内容为接口实现类的全限定名。如 JDBC 就是使用的 SPI,JDK 只负责定义数据库驱动接口,交给第三方来具体实现(如 Oracle、MySQL 等)。

以 MySQL 为例,在mysql-connector-java-5.1.48.jar 包中可看到 META-INF/services 目录下有个java.sql.Driver文件,内容为:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

在 JDK DriverManager 中使用 SPI 加载 JDBC 驱动,此时会加载 MySQL 驱动,以上文件中的两个类都会进行加载。

// java.sql.DriverManager#loadInitialDrivers # 586 行
// SPI 加载 JDBC 驱动
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
while(driversIterator.hasNext()) {
  driversIterator.next();
}

com.mysql.jdbc.Driver 类中,有一段静态代码块会在加载的时候执行,即往 DriverManager 注册驱动:

static {
  try {
    java.sql.DriverManager.registerDriver(new Driver());
  } catch (SQLException E) {
    throw new RuntimeException("Can't register driver!");
  }
}

2. 使用示例

  1. 定义接口 Robot:
public interface Robot {
    void sayHello();
}
  1. 定义两个 Robot 的实现类:
public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}
  1. META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 com.au.spi.Robot。文件内容为实现类的全限定名,如下:
com.au.spi.OptimusPrime
com.au.spi.Bumblebee
  1. 启动类:
public class Main {
    public static void main(String[] args) throws Exception {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(Robot::sayHello);
    }
}
  1. 运行结果:
Java SPI
Hello, I am Optimus Prime.
Hello, I am Bumblebee.

三、Dubbo SPI

1. 实现原理

JDK 自带的 SPI 功能比较简单,并不方便扩展,如 AOP 等,并且不能按需获取,一个文件里面的实现类都会进行顺序加载,事实上并不是所有类都有用,就会导致资源浪费,这不符合 Dubbo 的作风,所以 Dubbo 没有采用 JDK SPI,重新实现了一套功能更强的 SPI 机制,Dubbo SPI 的相关逻辑都是封装在 ExtensionLoader 类中,通过 ExtensionLoader 可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,文件名也是需要扩展的接口的全限定名,配置内容是键值对,如下:

optimusPrime = com.au.spi.OptimusPrime
bumblebee = com.au.spi.Bumblebee

2. 使用示例

  1. 定义接口 Robot:
@SPI
public interface Robot {
    void sayHello();
}
  1. 定义两个 Robot 的实现类:
public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}
  1. META-INF/dubbo 文件夹下创建一个文件,名称为 Robot 的全限定名 com.au.spi.Robot。文件内容为键值对,name (随意) = 实现类的全限定名,如下:
optimusPrime = com.au.spi.OptimusPrime
bumblebee = com.au.spi.Bumblebee
  1. 启动类:
public class Main {
    public static void main(String[] args) throws Exception {
        ExtensionLoader<Robot> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Robot.class);
        System.out.println("Dubbo SPI");
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}
  1. 运行结果:
Dubbo SPI
Hello, I am Optimus Prime.
Hello, I am Bumblebee.

3. 源码分析

注:源码基于 2.7.8-release 分支。源码地址:https://github.com/apache/dubbo

3.1. 获取 ExtensionLoader 实例

ExtensionLoader.getExtensionLoader 入口开始分析,该方法是获取一个 ExtensionLoader 实例,首先会判断扩展类的类型是否为接口,再判断接口上是否有 @SPI 注解,不满足就会报错。然后从缓存中获取该扩展类对应的 ExtensionLoader 实例,如果缓存未命中,则创建一个新的实例,并加入缓存中。

在 Dubbo SPI 中一个扩展点只有一个ExtensionLoader 实例。

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!");
  }
  // 扩展类接口上必须带有 @SPI 注解,否则报错
  if (!withExtensionAnnotation(type)) {
    throw new IllegalArgumentException("Extension type (" + type +
                                       ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
  }

  // private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
  // 从缓存中获取 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;
}
3.2. 获取拓展类对象

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,顾名思义,用于持有目标对象,这里目标对象就是扩展类实例
  // 从缓存中获取Holder对象,如果未命中缓存,则创建一个Holder对象
  final Holder<Object> holder = getOrCreateHolder(name);
  Object instance = holder.get();
  // double check,单例模式
  if (instance == null) {
    synchronized (holder) {
      instance = holder.get();
      if (instance == null) {
        // 创建拓展实例
        instance = createExtension(name, wrap);
        holder.set(instance);
      }
    }
  }
  return (T) instance;
}

// private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
// 从缓存中获取Holder对象,如果未命中缓存,则创建一个Holder对象
private Holder<Object> getOrCreateHolder(String name) {
  Holder<Object> holder = cachedInstances.get(name);
  if (holder == null) {
    cachedInstances.putIfAbsent(name, new Holder<>());
    holder = cachedInstances.get(name);
  }
  return holder;
}
3.3. 创建扩展类对象实例
  1. 通过 getExtensionClasses 获取所有的扩展类
  2. 通过反射创建扩展对象
  3. 向拓展对象中注入依赖,IOC 实现
  4. 将拓展对象包裹在相应的 Wrapper 对象中,即 AOP 的实现
  5. 执行生命周期 initialize 方法
private T createExtension(String name, boolean wrap) {
  // 获取所有的拓展类,也是先从缓存获取,未命中再去读取配置文件,见下面分析
  Class<?> clazz = getExtensionClasses().get(name);
  if (clazz == null) {
    throw findException(name);
  }
  try {
    // private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
    // 先从实例缓存中获取,未命中则通过反射创建实例对象,并放入缓存
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) {
      EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
      instance = (T) EXTENSION_INSTANCES.get(clazz);
    }
    // Dubbo IOC 实现,注入依赖
    injectExtension(instance);

		// 是否需要包装
    if (wrap) {
      List<Class<?>> wrapperClassesList = new ArrayList<>();
      // cachedWrapperClasses 在扩展类加载阶段会进行赋值(loadClass方法中)
      if (cachedWrapperClasses != null) {
        // 如果有包装类,将这些包装类排序、反转
        // @Activate 注解中有 order 字段,字段值越小优先级越低
        wrapperClassesList.addAll(cachedWrapperClasses);
        wrapperClassesList.sort(WrapperComparator.COMPARATOR);
        Collections.reverse(wrapperClassesList);
      }

      if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
        // 循环创建 Wrapper 实例,
        for (Class<?> wrapperClass : wrapperClassesList) {
          Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
          if (wrapper == null
              || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
            // Dubbo AOP 实现
            // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例
            // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
          }
        }
      }
    }
		// 触发生命周期 initialize 方法
    initExtension(instance);
    return instance;
  } catch (Throwable t) {
    throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                                    type + ") couldn't be instantiated: " + t.getMessage(), t);
  }
}
3.4. 获取所有的拓展类

从缓存中获取所有扩展类,如果缓存未命中,则执行 loadExtensionClasses 方法加载所有扩展类。

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;
}
3.5. 加载所有扩展类
  1. 缓存默认扩展名
  2. 遍历加载策略(加载策略是通过 JDK SPI 来加载的,详见 loadLoadingStrategies() 方法,在类加载的时候执行)
  3. 根据加载策略去加载扩展类
private Map<String, Class<?>> loadExtensionClasses() {
  // 缓存默认扩展名,即从 @SPI 注解中获取 value 的值,在 getDefaultExtension() 方法中会用到这个默认扩展名,这个值不能为 ”true“,否则会报错
  cacheDefaultExtensionName();

  Map<String, Class<?>> extensionClasses = new HashMap<>();
	// 通过策略来加载配置文件中的扩展类
  // 这个策略是通过 JDK SPI 来加载的,配置在 resources/META-INF/services/org.apache.dubbo.common.extension.LoadingStrategy
  // 具体详情可以看 loadLoadingStrategies() 方法,比较简单
  // private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
  for (LoadingStrategy strategy : strategies) {
    loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    // 兼容老版本,阿里 dubbo
    loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
  }

  return extensionClasses;
}
3.6. 加载策略中配置的所有目录文件

通过策略中配置的目录 + 扩展类全限定名作为文件名,通过类加载器获取所有 jar 包中的同名文件,然后遍历这些文件,一个一个读取并解析配置,最终加载解析出来的扩展类。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                           boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
  // 策略中配置的目录 + 扩展类全限定名作为文件名
  String fileName = dir + type;
  try {
    Enumeration<java.net.URL> urls = null;
    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);
      }
    }
		// 遍历这些文件,不同 jar 包中可以有多个同名文件
    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);
  }
}
3.7. 读取并解析配置文件

一行一行读取,先把注释去掉(# 后面的为注释),再找到等号进行分割,去掉空格,这样就得到了 name 和 line 两个字符串,分别是扩展名和扩展类全限定类名,得到这两个后再去加载扩展类。

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(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);
  }
}
3.8. 加载单个扩展类
  1. 先判断配置文件中的类是否和 type 同类型,不是则报错(都不同类型还扩展啥)。
  2. 判断是否存在 @Adaptive 注解,如果存在,则缓存这个类为自适应类(注意,一种扩展类型只能有一个 @Adaptive 标注的类,否则会报错)。
  3. 判断是否包装类,如果是,则缓存这个类为包装类,用来实现 AOP 功能。
  4. 以上都不是,走到 else 分支,说明这个类是普通的扩展类。先执行扩展类的默认构造器,再判断配置文件中的 key 是否为空,如果为空,则尝试从 @Extension 注解中获取 name,或使用小写的类名作为 name。最后通过逗号分割 name,存储 name 到 Activate 注解对象的映射关系、Class 到名称的映射关系、名称到 Class 的映射关系(名称到 Class 的映射关系即回填 loadExtensionClasses() 的返回值-所有的扩展类)。
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 注解,如果存在,则缓存这个类为自适应类
  // 注意,一种扩展类型只能有一个 @Adaptive 标注的类,否则会报错,可以点进去 cacheAdaptiveClass 方法看看
  if (clazz.isAnnotationPresent(Adaptive.class)) {
    cacheAdaptiveClass(clazz, overridden);
  } else if (isWrapperClass(clazz)) {
    // 是否包装类,如果是,则缓存这个类为包装类,用来实现 AOP 功能
    cacheWrapperClass(clazz);
  } else {
    // 执行扩展类的默认构造器,所以扩展类不能将默认构造器设为私有,否则会报错
    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 的映射关系,即回填 loadExtensionClasses() 的返回值-所有的扩展类
        saveInExtensionClass(extensionClasses, clazz, n, overridden);
      }
    }
  }
}

// 查找注解 @Extension 的 value 值,如果没有,则取类名的小写
private String findAnnotationName(Class<?> clazz) {
  org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
  if (extension != null) {
    return extension.value();
  }

  String name = clazz.getSimpleName();
  if (name.endsWith(type.getSimpleName())) {
    name = name.substring(0, name.length() - type.getSimpleName().length());
  }
  return name.toLowerCase();
}
3.9. Dubbo IOC 实现

Dubbo IOC 是通过类的 set 方法来注入依赖的,没有解决循环依赖的问题。

  1. 如果对象工厂 objectFactory 为空,直接返回 instance。
  2. 遍历实例的所有方法,找到 set 开头并且只有一个入参的 public 方法,获取属性名(比如 setName 方法对应属性名 name)作为 bean 名字到对象工厂中获取到依赖对象。
  3. 通过反射执行 set 方法进行依赖注入。
private T injectExtension(T instance) {
	// 对象工厂为空,不执行直接返回
  if (objectFactory == null) {
    return instance;
  }

  try {
    // 遍历实例的所有方法
    for (Method method : instance.getClass().getMethods()) {
      // 找到 set 开头并且只有一个入参的 public 方法
      if (!isSetter(method)) {
        continue;
      }
      /**
       * Check {@link DisableInject} to see if we need auto injection for this property
       */
      // 如果有方法上面有 DisableInject 注解,则不进行注入
      if (method.getAnnotation(DisableInject.class) != null) {
        continue;
      }
      Class<?> pt = method.getParameterTypes()[0];
      // 如果是原始类型,不执行,如 Array、Integer、String、Boolean、Charater、Number、Date
      if (ReflectUtils.isPrimitives(pt)) {
        continue;
      }

      try {
        // 获取属性名,比如 setName 方法对应属性名 name
        String property = getSetterProperty(method);
        // 从 ObjectFactory 中获取依赖对象
        // ObjectFactory 是在创建 ExtensionLoader 对象时候进行初始化的,涉及到 SPI 扩展点自适应机制的内容
        // objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展
        Object object = objectFactory.getExtension(pt, property);
        if (object != null) {
          // 通过反射执行 set 方法进行依赖注入
          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;
}
3.10. 获取默认的扩展类对象实例
  1. 获取所有的拓展类,这个过程中会存储默认的扩展类名,上面分析过。
  2. 执行 getExtension 方法获取默认扩展类。
public T getDefaultExtension() {
  getExtensionClasses();
  if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
    return null;
  }
  return getExtension(cachedDefaultName);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值