https://blog.csdn.net/qq_20397315/article/details/106095184
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
使用入门
创建接口和其实现类
public interface DbInterface {
void say();
}
public class Db1 implements DbInterface {
@Override
public void say() {
System.out.println("DB1");
}
}
public class Db2 implements DbInterface {
@Override
public void say() {
System.out.println("DB2");
}
}
在
resource
目录下创建META-INF/services
目录,并在改目录下创建一个名为接口全限定名的文件
我这里是com.zy.spi.DbInterface
。
com.zy.spi.DbInterface
文件中填写接口实现类的全限定名;
com.zy.spi.Db1
com.zy.spi.Db2
使用
@Test
public void t1() {
//java.util.ServiceLoader
ServiceLoader<DbInterface> load = ServiceLoader.load(DbInterface.class);
load.forEach(DbInterface::say);
/*for (DbInterface dbInterface : load) {
dbInterface.say();
}*/
}
可以看到,java.util.ServiceLoader
类位于java.util
包下,却加载到了我们自己定义的Db1和DB21类
。
SPI机制原理
https://blog.csdn.net/qq_20397315/article/details/106095184
ServiceLoader.load方法
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
可以看到使用的是线程上下文类加载器来打破双亲委派模型;
ServiceLoader实现了Iterable,只有在我们迭代ServiceLoader对象时,才会去加载META-INF/services
目录下文件中的记录的类(懒加载);
最终调用的方法是hasNextService()
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//PREFIX : private static final String PREFIX = "META-INF/services/";
//service.getName() 即接口的全限定名
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);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
MySQL驱动使用了SPI机制,因此在jdbc4及以后,可以不用显示的加载数据库驱动类;