JAVASE-16 SPI
最近写业务代码的时候,遇到了SPI的概念,是个遗漏的知识点,总结一下
1.SPI概念
SPI 是英文Service Provider Interface的缩写, 即服务提供商接口。
以jdbc为例,在jdk中提供了jdbc的接口类,不同厂商分别提供对应的实现类,如:mysql-jdbc,oralce-jdbc,sqlserver-jdbc等。
JDBC的接口类是java的基础类库,是由引导类加载器加载进jvm的;实现类是在运行时从厂家的jar包中加载得到的,因此至少是通过系统类加载器加载进jvm内存。
这样路是堵死的,引导类加载器加载的类根本看不到系统类加载器加载的类,因此无法调用。此时需要使用到线程上下文类加载器。
SPI(服务提供商接口),顾名思义:jdk提供规范,厂商实现。因此,它和线程上下文类加载器有着天然的联系。
线程上下文类加载器的介绍 可参考博客:JVM-3 类加载机制
2.规范
- 当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
- 接口实现类所在的jar包放在主程序的classpath中;
- 主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置 文件找到实现类的全限定名,把类加载到JVM;
- SPI的实现类必须携带一个不带参数的构造方法;
3.实例
代码目录情况如下:
提供一个接口CarSpi接口,以及3个实现类:AudiSpiImpl,BenciSpiImpl ,BmwSpiImpl
以及一个配置文件
package com.shengyu.service;
public interface CarSpi {
void print();
}
实现类1:AudiSpiImpl
package com.shengyu.service.impl;
import com.shengyu.service.CarSpi;
public class AudiSpiImpl implements CarSpi {
public void print() {
System.out.println("------audi----");
}
}
实现类2:BenciSpiImpl
package com.shengyu.service.impl;
import com.shengyu.service.CarSpi;
public class BenciSpiImpl implements CarSpi {
public void print() {
System.out.println("------benci----");
}
}
实现类3:BmwSpiImpl
package com.shengyu.service.impl;
import com.shengyu.service.CarSpi;
public class BmwSpiImpl implements CarSpi {
public void print() {
System.out.println("------bmw----");
}
}
配置文件:resources/META-INF/services文件夹下 com.shengyu.service.CarSpi文件
com.shengyu.service.impl.AudiSpiImpl
com.shengyu.service.impl.BenciSpiImpl
com.shengyu.service.impl.BmwSpiImpl
客户端代码
package com.shengyu;
import com.shengyu.service.CarSpi;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
public class Application {
public static void main(String[] args) {
ServiceLoader serviceLoader = ServiceLoader.load(CarSpi.class, CarSpi.class.getClassLoader());
Iterator<CarSpi> iterator = serviceLoader.iterator();
List<CarSpi> carSet = new ArrayList<>();
while (iterator.hasNext()) {
carSet.add(iterator.next());
}
carSet.stream().forEach(carSpi -> {
carSpi.print();
});
}
}
4.总结
1.SPI天生具有良好的扩展性。如果需要添加新的功能,直接引入CarSpi的实现类即可,并直接修改配置文件即可,客户端代码不需要编译,满足开闭原则;
2.SPI可以用来生成一组CarSpiI-mpl (表示CarSpi的实现类);