• Dubbo源码学习之-SPI介绍


    前言

    学习之路还是要戒骄戒躁,一以贯之的积累前行。之前的公司部门技术达人少,自己总向往那些技术牛人多的团队,想象自己进去之后能跟别人学到多少东西。如今进到一个这样的团队之后,却发现之前自己的想法过于幼稚。且不说由于人与人之间性格不合导致的难以深入相处,即使相处融洽,别人也不会给你太多的帮扶,更多的还是靠自己去学习去探究。学习的道路上没有什么捷径,且会有很多的心魔需要自己去克服。闲话少叙,今天主要是说一下Dubbo中SPI的基本内容,自适应拓展的部分后面单独成文。

    什么是SPI

    要说Dubbo的SPI,则必须先说说Java原生的SPI。可能很多道友都没有听说过SPI,它是Service Provider Interface 即服务提供接口的简称,顾名思义,它就是用来提供服务的。

    在Java中是如何提供服务的呢?简要来说,就是在资源文件目录下(即resource目录下)的META-INF/services文件夹下,建立文件名为接口的全路径名的文件,文件内容为此接口的实现类全路径名。然后在代码中通过ServiceLoader类获取这些配置的实现类,然后就可以自由的使用这么实现类了。下面是我在本地写的一个小Demo:

    代码结构如下所示:

    接口代码:

    1 package spipackage;
    2 public interface SpiInterface {
    3     void getName();
    4 }

    两个实现类代码:

    1 package spipackage;
    2 public class SpiImpl implements SpiInterface{
    3     @Override
    4     public void getName() {
    5         System.out.println("SpiImpl");
    6     }
    7 }
    1 package spipackage;
    2 public class SpiImplTwo implements SpiInterface {
    3     @Override
    4     public void getName() {
    5         System.out.println("SpiImplTwo");
    6     }
    7 }

    资源文件:

    1 spipackage.SpiImpl
    2 spipackage.SpiImplTwo

    测试类:

     1 package spipackage;
     2 import java.util.Iterator;
     3 import java.util.ServiceLoader;
     4 public class SpiTestClient {
     5     public static void main(String[] args) {
     6         ServiceLoader<SpiInterface> spiInterfaces = ServiceLoader.load(SpiInterface.class);
     7         // 循环调用实现类中的方法
     8         spiInterfaces.forEach(SpiInterface::getName);
     9         // 获取某个实现类进行调用
    10         Iterator<SpiInterface> iterator = spiInterfaces.iterator();
    11         while (iterator.hasNext()) {
    12             SpiInterface next = iterator.next();
    13             if (next instanceof SpiImplTwo) {
    14                 next.getName();
    15             }
    16         }
    17     }
    18 }

    测试结果:

    什么是Dubbo的SPI

    从java原生SPI的使用上可知,它是一次性加载整个资源文件中的数据,当你要获取其中某个实现类时也只能通过遍历来得到。而Dubbo的开发人员们显然要让其更加灵活,所以Dubbo中的SPI是在Java原生SPI基础上做了改造升级。首先可以按需加载,需要用哪个就加载哪个,这是通过键值对来配置实现类做到的,相当于给每个实现类打上了标签;其次还实现了依赖注入,即如果实现类A中需要注入实现类B,则dubbo在获取实现类A时会自动将B注入进去。

    具体的本地代码测试跟上述类似,此处就不在贴出来了,只是需将ServiceLoader换成Dubbo的ExtensionLoader,且接口需带有@SPI注解,并且资源文件也可放入META-INF/dubbo目录下。

    下面简要讲一下ExtensionLoader中的源码实现。Dubbo的ExtensionLoader类中,获取服务类的主要方法是getExtension方法,而在这个方法中,核心方法是createExtension,此方法很重要,代码如下所示:

     1 private T createExtension(String name) {
     2         // 1、先获取class类
     3         Class<?> clazz = getExtensionClasses().get(name);
     4         if (clazz == null) {
     5             throw findException(name);
     6         }
     7         try {
     8             T instance = (T) EXTENSION_INSTANCES.get(clazz);
     9             if (instance == null) {
    10                 // 2、通过反射创建实例,且存入缓存
    11                 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
    12                 instance = (T) EXTENSION_INSTANCES.get(clazz);
    13             }
    14             // 3、注入依赖,类似spring的依赖注入
    15             injectExtension(instance);
    16             // 4、将扩展对象包进wrapper对象中
    17             Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    18             if (CollectionUtils.isNotEmpty(wrapperClasses)) {
    19                 for (Class<?> wrapperClass : wrapperClasses) {
    20                     instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    21                 }
    22             }
    23             return instance;
    24         } catch (Throwable t) {
    25             throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
    26                     type + ") couldn't be instantiated: " + t.getMessage(), t);
    27         }
    28     }

    分四步获取了类的实例对象。其中第一步中包含了主要的逻辑,它读取配置文件,是通过类加载器加载文件获取输入流,然后一行一行读取的,其中包括了对空格的处理、对注释的处理。之前总感觉读取配置文件的实现很神奇,现在慢慢的可以一窥其中究竟了,觉得也没多高大上,都是很实际的操作。

    小结:SPI的作用

    通过SPI实现的功能扩展,更类似于插拔式的扩展。增加了某些功能类之后,通过配置文件引入,然后在某些地方获取,调用即可。SPI机制是Dubbo的基础,了解了它才能更加清楚的看清Dubbo的框架设计。另外,通过对SPI的了解,个人感觉SPI有点类似于Spring的IOC实现,也可以说Spring通过XMl配置文件或者注解实现了一种另类的SPI机制,让你不用关注实例对象的创建,只是用的时候获取到用即可,当然Spring实现的功能内容更多更易于扩展。

    只要每天都有进步,都在朝目标前行,就可心安。戒骄戒躁,努力前行!

  • 相关阅读:
    Python基础-迭代器
    Python基础-生成器
    Python基础-装饰器
    Python基础-函数
    Python基础-文件操作
    Python基础-集合
    jfinal任务调度quartz(cron) 定时任务 QuartzPlugin
    ServletRequest.getRequestDispatcher
    QuartZ Cron表达式
    Jax-WS WebService实现
  • 原文地址:https://www.cnblogs.com/zzq6032010/p/11144350.html
Copyright © 2020-2023  润新知