什么是java spi机制?
SPI全称Service Provider Interface ,是java提供的一个功能,用于加载接口在本地的第三方实现,常用于框架的拓展和可替换组件的实现。
spi的核心思想是解耦,比如说 框架里面定义了一个LoadBalance接口,是用于实现负载均衡的接口, 但是具体的负载均衡算法的实现有许多种,比如说随机,轮询,一致性哈希,最小连接数等等。 然后比如说你使用了这个框架,但是要根据你的业务场景使用一个新的负载均衡算法。框架本身并不支持,怎么办?
如果这个框架内部的LoadBalance接口使用了spi机制,那么你就很容易拓展这个功能。
举个例子
具体步骤看如下:
定义接口CarInterface,里面是一个获取车的颜色的方法。
public interface CarInterface {
void getColor();
}
我们需要在resources文件夹里 建立一个META-INF文件夹,然后再建立一个services文件夹。
里面以接口的全路径命名一个文件 ,即包路径
实现类的代码如下
public class BlackCar implements CarInterface {
@Override
public void getColor() {
System.out.println("black");
}
}
public class redCar implements CarInterface {
@Override
public void getColor() {
System.out.println("red");
}
}
main方法里面 去调用
public class CarDemo {
public static void main(String[] args) {
//ServiceLoader 用于加载CarInterface接口的实现 返回一个iterator
ServiceLoader serviceLoader=ServiceLoader.load(CarInterface.class);
Iterator iterator=serviceLoader.iterator();
while (iterator.hasNext()){
CarInterface carInterface=iterator.next();
carInterface.getColor();
}
}
}
ServiceLoader的作用是去查找CarInterface接口的实现类,查到之后,就会创建这个接口的实现类对象, 并返回一个列表。
这个列表内就有CarInterface接口在META-INF services 文件夹里的同名文件内部写的实现类的对象实例。
那么回到之前的那个问题,假设这个是个框架, 如果我要拓展接口的实现,新增我自己的实现, 只需要创建一个新的接口的实现类,然后在META-INF的service文件夹里面,创建对应的配置文件和填写实现类的路径即可。
框架内部就会使用ServiceLoader去加载我的实现类。
最常见的,可以看mysql的驱动jar包里面的MATA-INF文件夹里面同样有这样的配置,因此JDBC加载不同类型数据库的驱动,也是用了同样的原理。
ServiceLoader的原理:
读取META-INF文件夹内的services文件
然后解析文件,找到对应的实现类的路径
通过Class.forName加载进来
newInstance()反射创建对象
并存到缓存和列表里面去
public final class ServiceLoader implements Iterable{
private static final String PREFIX = "META-INF/services/";
// 代表被加载的类或者接口
private final Class service;
// 用于定位,加载和实例化providers的类加载器
private final ClassLoader loader;
// 创建ServiceLoader时采用的访问控制上下文
private final AccessControlContext acc;
// 缓存providers,按实例化的顺序排列
private LinkedHashMap providers = new LinkedHashMap<>();
// 懒查找迭代器
private LazyIterator lookupIterator;
......
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class> c = null;
try {
//加载实现类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//创建对象实例
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
这里,java spi机制的优点是解耦,但是缺点也有,比如说,不能指定获取哪个实现类,写在配置里面的都会被加载进来。
其次,如果我的实现类里面, 依赖了其他类,不能支持依赖注入,同理,也不支持切面,如果要给多个现实类都加上相同的功能的话,要修改原来的代码。
因此,在dubbo spi中,对这三个缺点进行了优化。
首先是,支持指定获取实现类
首先是,支持切面
首先是,支持依赖注入