Java SPI 机制
2021-01-12 本文已影响0人
爱健身的兔子
1 什么是SPI
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了spi接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔。
2 SPI的使用场景
JDBC就是典型的SPI的使用场景。通常各大厂商(如Mysql、Oracle)会根据一个统一的规范(java.sql.Driver)开发各自的驱动实现逻辑。客户端使用jdbc时不需要去改变代码,直接引入不同的spi接口服务即可。Mysql的则是com.mysql.jdbc.Driver
,Oracle则是oracle.jdbc.driver.OracleDriver
。
3 SPI实战
3.1 定义接口
public interface Driver {
void upload(String url);
}
3.2 定义接口的实现
public class MysqlDriver implements Driver{
@Override
public void upload(String url) {
System.out.println("upload to Mysql:"+url);
}
}
public class OracleDriver implements Driver{
@Override
public void upload(String url) {
System.out.println("upload to Oracle:"+url);
}
}
3.3 创建接口文件
在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件,并在文件中添加实现类。
com.spi.MysqlDriver
com.spi.OracleDriver
3.4 加载SPI实现类
public static void main(String[] args) {
ServiceLoader<Driver > driver= ServiceLoader.load(Driver .class);
for (Driver d : driver) {
d.upload("dirPath");
}
}
4 SPI原理
4.1 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 java.util.ServiceLoader.LazyIterator lookupIterator;
// 私有内部类,提供对所有的service的类的加载与实例化
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
String nextName = null;
//...
private boolean hasNextService() {
if (configs == null) {
try {
//通过相对路径读取classpath中META-INF目录的文件,也就是读取服务提供者的实现类全限定名
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
//...
}
//....
}
}
private S nextService() {
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//反射加载类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
}
try {
//实例化
S p = service.cast(c.newInstance());
//放进缓存
providers.put(cn, p);
return p;
} catch (Throwable x) {
//..
}
//..
}
}
}
4.2 SPI的加载过程
ServiceLoader.load()方法只是创建了ServiceLoader对象。
//初始化一个ServiceLoader,load参数分别是需要加载的接口class对象,当前类加载器
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
LazyIterator的hasNext()方法将META-INFO下面的类文件去出来。
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
LazyIterator的next()方法通过上面读取的实现类的全限定名称将类加载进来。
public S next() {
if (acc == null) {//用来判断serviceLoader对象是否完成初始化
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}