SPI(Service Provider Interface)是java提供的一种服务发现机制。允许你定义一个接口或抽象类,然后由第三方实现这个接口,并在运行时动态加载这些实现类
核心思想是:面向接口编程,解耦接口与实现
核心组件:
接口/抽象类、实现类、配置文件、ServiceLoader(JDK提供的工具类)
//1.定义接口或抽象类
public interface Animal {void speak();
}
//2.编写多个实现类
public class Cat implements Animal {public void speak() {System.out.println("Meow");}
}public class Dog implements Animal {public void speak() {System.out.println("Woof");}
}
//3.创建配置文件,在 resources/META-INF/services/ 目录下创建一个文件:com.example.Animal
com.example.Cat
com.example.Dog
//4.使用ServiceLoader加载实现类
ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);for (Animal animal : serviceLoader) {animal.speak();
}
等价于:
Iterator<Animal> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {Animal animal = iterator.next();animal.speak();
}//输出:
Meow
Woof
工作原理:
实际上就是ServiceLoader.load这一步
首先先理解一下ServiceLoader这个类,这个类实现了Iterable接口
作用是根据接口或抽象类,在运行时动态加载所有实现了该接口的类
《可以看作是一个插件发现器,能自动扫描项目中的jar包,找到符合某个接口的所有实现类,并创建他们的实例》
流程:
读取配置文件中的内容,获取实现类的类名
使用类加载器去加载类
调用无参构造函数去实例化对象(懒加载+无参构造函数)
返回serviceLoader(虽然不是一个iterator,但是可以被增强for循环遍历)
至于这些所有的实现类的实例,会放入serviceloader的一个缓存池中,而且是懒加载的,第一次用到时才加载
应用场景:
优缺点:
优点:
接口和实现分离
新增实现无需修改代码
支持热插拔,插件化系统
标准化配置:统一都使用配置文件META-INF/services配置
缺点:
一次性加载所有的实现类
只能按照配置文件中的顺序加载
不支持IOC容器继承
加载失败不会抛出异常,静默跳过
SPI与springIOC对比:
SPI的使用场景是标准化接口、插件化系统,但是对实例实际上只有创建,没有管理的能力(生命周期、依赖注入等),而且不支持条件加载
springIOC对实例对象有很强的管理以及支持一些自定义的配置,功能非常灵活强大
所以在实际应用中,例如在springboot中对数据库驱动的加载的时候,实际上是底层原理是SPI,将SPI创建出来的数据库驱动实例通过@bean注解返回给IOC容器实现了灵活化管理和应用
SPI和API的区别:
(图来自javaguide)
API是面向“使用者”的,调用方直接使用来实现功能
SPI是面向“实现者”的
我觉得主要意思就是,API我在为了实现这个功能调用这个接口的时候,具体的方法是已经写好的,我直接用就好了,但是SPI是我用这个接口的时候,这个接口只是提供了一种通用的方法,但是具体怎么实现是可以自定义的(不知道是不是这样)
ai一句话总结为:API 是“我调用你”,SPI 是“你实现我”
SPI是一种扩展机制,API是一种调用机制