SPI(服务提供者接口)
SPI
(Service Provider Interface)是一种动态替换编码的机制(可以取代代码中大量的if-else
硬编码)。通过SPI
机制,不同的服务提供商可以对服务接口进行不同实现,不需要更改接口的代码,还可以在运行时进行动态的替换。
(一)Java中的实现:ServiceLoader
1.Demo
- 假设我有
JSpiService
接口和它的两个实现类:
1 | public interface JSpiService { |
- 在
resources
目录下新建META-INF.services
目录,根据接口的路径创建spi.JpiService
文件,在文件中写入实现类的路径:
- 通过
ServiceLoader<S> load(Class<S> service)
进行服务调用:
1 | ServiceLoad<JSpiService> services = ServiceLoader.load(JSpiService.class); |
2.部分源码
1 | // ServiceLoader会提供一个迭代器,用于遍历所有服务实现类 |
3.应用:java.sql.Driver
在Java
中定义了接口java.sql.Driver
,但是其实现类都是交给各家厂商来提供的:
mysql-connector-java.jar
的META-INF/services
目录下会有名为java.sql.Driver
的文件,内容是:
1 | com.mysql.jdbc.Driver |
postgresql.jar
的META-INF/services
目录下会有名为java.sql.Driver
的文件,内容是:
1 | org.postgresql.Driver |
- 如果我要使用
MySQL
的驱动:
1 | String url = "jdbc:mysql://localhost:3306/test"; |
- 在
DriverManager
中通过SPI
调用了驱动实现类:
1 | // 通过SPI加载、实例化各实现类 |
4.存在的问题
- 线程不安全
ServiceLoader
在遍历时会通过newInstance()
进行实例化,这就要求服务实现类一定要有一个无参构造函数- 通过迭代器无法指定具体的服务实现类,也就是说会把所有服务实现类都加载并初始化,然后才能筛选出你需要的那个实现类,代价较大
- 破坏了双亲委派模型
(二)SpringBoot SPI
- 配置文件
META-INF/spring.factories
,不同于Java
的地方是,SpringBoot
只有一个配置文件,格式为K/V
存储,其中key是接口名称,value是实现类名称,多个value以逗号分隔;不像Java
每个接口需要一个配置文件。 - 实现类:
SpringFactoriesLoader # loadFactories()/loadFactoryNames()