1.SPI的使用
1.1 定义SPI接口
package com.demo.spi;
public interface SpiInterface {
void test(String keyword);
}
1.2 SPI接口实现
public class SpiTest1 implements SpiInterface{
@Override
public void test(String keyword) {
System.out.println("this is " + keyword);
}
}
在项目classpath下新建META-INF/services目录,在该目录底下一个以接口全路径命名的文件,文件内写入SPI接口的实现类
1.3 加载SPI
public static void main(String[] args) {
ServiceLoader<SpiInterface> load = ServiceLoader.load(SpiInterface.class);
Iterator<SpiInterface> iterator = load.iterator();
while (iterator.hasNext()) {
SpiInterface next = iterator.next();
next.test("SPI demo");
}
}
Main方法中打印出了this is SPI demo,,可见SpiInterface最终加载了SpiTest,如果SpiInterface有多个实现类,只需要在com.demo.spi.SpiInterface文件中写入全部实现类的全路径即可。
在上述程序中,我们并没有去创建SpiTest1实例,那SPI机制是何时创建SpiTest1,并加载到内存当中的呢。
2 SPI原理分析
SPI实现都是通过ServiceLoader类实现,其比较重要的变量有
public final class ServiceLoader<S> implements Iterable<S>{
//查找文件位置的前缀
private static final String PREFIX = "META-INF/services/";
// 需要加载的接口
private final Class<S> service;
// 类加载器
private final ClassLoader loader;
// 权限控制
private final AccessControlContext acc;
// 缓存
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懒加载迭代器
private LazyIterator lookupIterator;
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
当我们调用ServiceLoader.load()时,该方法仅仅只是将成员变量做初始化操作,当调用ServiceLoader.iterator(),返回的是一个自定义的迭代器,迭代方法都是直接调用LazyIterator的方法。
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
我们在外部进行迭代时,回调用**LazyIterator.hasNextService**方法,
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//拿到文件路径,并解析文件拿到里面的内容
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
//pending为空时,使用一个pending迭代器,来记录文件里的类名
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
调用迭代器的**next()**时,会执行**nextService**方法返回一个实例化对象
private S nextService() {
//先判断是否有下一个
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//将cn加载到JVM中
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
}
通过分析源码,我们懂了**SPI**,通过**类加载**+**反射**的机制来实例化对象
3 SPI在项目中使用
3.1 加载jdbc驱动
在我们引入的mysql连接数据库的jar中,在**serivces**目录下,有个**java.sql.Driver**文件,里面写了**Driver**接口的**mysql**实现
在**DriverManager**类中,通过**SPI**加载的关键代码
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//通过serviceLoad加载驱动接口
try{
while(driversIterator.hasNext()) {
//获取具体的实现类
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
//通过系统变量加载驱动
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
5. SPI的缺点
通过上面分析可以发现,在进行迭代的时候,**SPI**会将所有的实现都实例化了,即使有些对象是我们不需要的,也都给实例化,这会造成了不必要的资源浪费。而且通过SPI,我们也只能实例化一些简单的对象,那种有依赖关系的对象,通**JAVA**原生的**SPI**是实现不了的