java spi 与 dubbo spi

缺乏、安全感 2022-10-22 07:40 268阅读 0赞

传统java spi

在java 中我们要动态通过配置文件制定实现类该如何实现呢?使用java spi机制即可,我们来看下面这个例子

  1. public interface Robot {
  2. void sayHello();
  3. }

然后定义两个实现类。分别为 OptimusPrime 和 Bumblebee

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

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

  1. org.apache.dubbo.OptimusPrime
  2. org.apache.dubbo.Bumblebee

文件名为接口Robot的 包路径+名称
文件内容则为接口实现类的 包路径+名称

在这里插入图片描述

然后运行我们的测试用例

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

在这里插入图片描述

可以看到两个实现类被成功的加载,并输出了相应的内容。

java spi源码分析

可以看到核心类是ServiceLoader
类结构
在这里插入图片描述

可以看到 ServiceLoader实现了 Iterable,所以可以循环遍历,因为一个接口有多个实现类,所以是一个集合。
而其中我们为什么我们要强制在META-INF/services这个路径创建文件呢。源码中我们看这个方法找到了答案
首先第一行代码中

  1. ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);

我们深入到源码底层发现只是new 了一个LazyIterator
在这里插入图片描述
并没有初始化接口的实现,可见是懒加载,在遍历ServiceLoader才会加载实现类。LazyIterator也实现了Iterator
在这里插入图片描述
我们看看 hasNextService()方法
在这里插入图片描述
可以看到fullName = PREFIX + service.getName()
而PREFIX 是写死的常量
在这里插入图片描述
这里初始化了所有的实现类名称

而真正初始化实现类的方法则是在nextService()
在这里插入图片描述

很多开源jar都使用了java 的spi比如日志,jdb驱动等
在这里插入图片描述
在这里插入图片描述

java spi 缺点

java spi缺点也很明显

  1. 多个并发多线程使用ServiceLoader类的实例是不安全的
  2. 每次获取元素需要遍历全部元素,不能按需加载。
  3. 加载不到实现类时抛出并不是真正原因的异常,错误很难定位
  4. 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类

基于以上缺点 dubbo重新实现了一套功能更强的 SPI 机制,Dubbo SPI 支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性

dubbo SPI

Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下

  1. optimusPrime = org.apache.spi.OptimusPrime
  2. bumblebee = org.apache.spi.Bumblebee

在这里插入图片描述
同时在Robot接口我们需要加入注解@SPI

测试使用

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

测试也是OK的

dubbo spi源码分析

源码版本:2.7.9

我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析

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

上面代码主要是基于一个双重检查的单例去获取对象。

下面我们看看instance = createExtension(name, wrap);的核心逻辑

  1. @SuppressWarnings("unchecked")
  2. private T createExtension(String name, boolean wrap) {
  3. // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
  4. Class<?> clazz = getExtensionClasses().get(name);
  5. if (clazz == null) {
  6. throw findException(name);
  7. }
  8. try {
  9. T instance = (T) EXTENSION_INSTANCES.get(clazz);
  10. if (instance == null) {
  11. // 通过反射创建实例
  12. EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
  13. instance = (T) EXTENSION_INSTANCES.get(clazz);
  14. }
  15. // 向实例中注入依赖
  16. injectExtension(instance);
  17. if (wrap) {
  18. List<Class<?>> wrapperClassesList = new ArrayList<>();
  19. if (cachedWrapperClasses != null) {
  20. wrapperClassesList.addAll(cachedWrapperClasses);
  21. wrapperClassesList.sort(WrapperComparator.COMPARATOR);
  22. Collections.reverse(wrapperClassesList);
  23. }
  24. if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
  25. // 循环创建 Wrapper 实例
  26. for (Class<?> wrapperClass : wrapperClassesList) {
  27. Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
  28. if (wrapper == null
  29. || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
  30. // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
  31. // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
  32. instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
  33. }
  34. }
  35. }
  36. }
  37. initExtension(instance);
  38. return instance;
  39. } catch (Throwable t) {
  40. throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
  41. type + ") couldn't be instantiated: " + t.getMessage(), t);
  42. }
  43. }

创建扩展类的核心步骤如下:

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

后续IOC和AOP源码由于篇幅关系就不在这里展开说明了,不过官网有详细说明,这里给出官网说明链接

dubbo官网关于duubo spi源码解析

发表评论

表情:
评论列表 (有 0 条评论,268人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Dubbo SPI

    前言 前面已经讲过[JDK SPI][], dubbo SPI主要基于JDK SPI机制,进行了增强,实现可扩展 为什么不直接用JDK的SPI呢,主要有哪些改变