Contents

apache shenyu的spi机制

什么是spi

SPI就是Service Provider Interface,是一种动态的服务发现机制。 可以基于运行时动态的加载接口的实现类。通过(接口编程 + 策略模式 + 配置文件) 实现解耦扩展

/img/source/spi/spi.png

应用

  • 数据库驱动
    • 基于该接口的实现有MySQL,PostgreSQL,SqlServer等。
  • SLF4J日志框架
  • DubboSPI扩展
    • dubbo
    • apache shenyu
  • spring boot starter

jdbc的应用 /img/source/spi/spi_jdbc.png

日志的应用 /img/source/spi/spi_log.png

apache shenyu的spi应用 /img/source/spi/spi_shenyu.png

dubbo的spi应用 /img/source/spi/spi_dubbo_extention.png

实现方式

市面上常见的SPI的实现方式有:

  • JDKSPI实现
  • DubboSPI实现、ShenYuSPI
  • SpringSPI实现

JDKSPI实现流程为

  • 在类路径的META-INF/services目录创建一个以接口全限定名称命名的文件(本质是一个properties)文件,例如命名为java.sql.Driver
  • 该文件中可以指定具体的实现类,也就是每个实现类的全类型限定名为单独一行
    • 例如 META-INF/services/java.sql.Driver
    •   # META-INF/services/java.sql.Driver文件内容
        com.mysql.jdbc.Driver
        org.postgresql.Driver
      
  • 最后通过java.util.ServiceLoader对该文件进行加载,实例化接口的对应实现类(这里隐含了一个约定,所有实现类必须提供无参构造函数,用来反射调用)

底层的实现涉及到类加载、双亲委派等内容,这里不展开。基于这种设计思路,很多主流框架实现了一套SPI扩展,例如DubboSPI扩展模块,就是读取类路径 下META-INF/services/dubbo目录的文件内容进行类加载。ShenYu-SPI模块也是沿用类似的设计思路。

JDK SPI 的代码示例

  • 接口定义

public interface Search {
    public List<String> searchDoc(String keyword);   
}
  • 实现类

public class FileSearch implements Search{
  @Override
  public List<String> searchDoc(String keyword) {
    System.out.println("文件搜索 "+keyword);
    return null;
  }
}

public class DatabaseSearch implements Search{
  @Override
  public List<String> searchDoc(String keyword) {
    System.out.println("数据搜索 "+keyword);
    return null;
  }
}
  • resources 接下来可以在resources下新建META-INF/services/目录,然后新建接口全限定名的文件:com.xxx.Search,里面加上我们需要用到的实现类

com.xxx.FileSearch
  • 测试方法

public class TestCase {
    public static void main(String[] args) {
        ServiceLoader<Search> s = ServiceLoader.load(Search.class);
        Iterator<Search> iterator = s.iterator();
        while (iterator.hasNext()) {
           Search search =  iterator.next();
           search.searchDoc("hello world");
        }
    }
}
  • 总结

可以看到输出结果:文件搜索 hello world如果在com.xxx.Search文件里写上两个实现类,那最后的输出结果就是两行了。 这就是因为ServiceLoader.load(Search.class)在加载某接口时,会去META-INF/services下找接口的全限定名文件, 再根据里面的内容加载相应的实现类。这就是spi的思想,接口的实现由provider实现,provider只用在提交的jar包里的META-INF/services下根据平台定义的接口新建文件,并添加进相应的实现类内容就好。

ShenYu SPI

源码分析

ShenYu-SPI模块十分精炼,代码结构如下


- shenyu-spi[module]
  - org.apache.shenyu.spi[package]
    -- ExtensionFactory
    -- ExtensionLoader
    -- Join
    -- SPI
    -- SpiExtensionFactory

这些类的功能如下

  • ExtensionFactory: SPI加载工厂,本身也是一个SPI,用于基于SPI机制加载ExtensionLoader实例同时基于ExtensionLoader实例获取默认标记SPI标识接口的实现
  • SpiExtensionFactory: ExtensionFactory的一个实现类
  • SPI: 标识注解,用于标识SPI,用于接口上
  • Join: 标识注解,用于实现类上,用来给SPI的实现进行排序。
  • ExtensionLoader: SPI加载器,类比java.util.ServiceLoader,用于加载SPI中接口的实现类