什么是SPI机制

SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。
Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦。

SPI整体机制图如下:

当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。

demo

1
2
3
4
5
6
7
8
9
    @Test
public void testSPI() {
ServiceLoader<IUser> serviceLoader = ServiceLoader.load(IUser.class);
serviceLoader.forEach(IUser::showName);
}

--------------------------------
this is Student Y
this is Teacher X

ServiceLoader

ServiceLoader其实是读取配置文件中实现类的全路径类名,通过反射创建对象,并放入providers容器中。

  • 调用过程
  • 优点
    使用Java SPI 机制的优势是实现解耦,使得接口的定义与具体业务实现分离,而不是耦合在一起。
    应用进程可以根据实际业务情况启用或替换具体组件。
  • 缺点
    1. 不能按需加载。虽然ServiceLoader 做了延迟载入,但是基本只能通过遍历全部获取,也就是接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
    2. 获取某个实现类的方式不够灵活,只能通过Iterator 形式获取,不能根据某个参数来获取对应的实现类。
    3. 多个并发多线程使用ServiceLoader 类的实例是不安全的。
    4. 加载不到实现类时抛出并不是真正原因的异常,错误很难定位。

SPI机制的应用