目录
SPI 全称为 Service Provider Interface,是一种服务发现机制。本质是由将接口的实现类的全限定名配置在文件当中,由服务加载,这样可以在运行时,动态的为接口加载实现类。
举个例子,我们在用JDBC连接数据库时,创建连接就能直接获取到Mysql或者Oracle,Java时如何调用到相对应的驱动呢?
我们将连接数据库看作一个扩展点,其他数据都是该点的实现,当我们需要连接相对应的的数据库时,Spi会帮助我们发现有哪些实现并加载。
一、Java的SPI
按照惯例,先写一个例子,先定义一个接口EatFood,其次创建它的两个实现类,KFC和McDonald,然后META-INF/services下创建一个EatFood的全限定名的文件,里面存放着两个实现类的全限定名,然后我们可以在执行一下代码
public interface EatFood {
void eat();
}
public class KFC implements EatFood{
@Override
public void eat() {
System.out.println("吃肯德基");
}
}
public class McDonald implements EatFood{
@Override
public void eat() {
System.out.println("吃麦当劳");
}
}
com.yxl.KFC
com.yxl.McDonald
测试代码
public class JavaSpiTest {
@Test
public void test(){
ServiceLoader<EatFood> eatFoodServiceLoader = ServiceLoader.load(EatFood.class);
Iterator<EatFood> iterator = eatFoodServiceLoader.iterator();
while(iterator.hasNext()){
EatFood eatFood = iterator.next();
eatFood.eat();
}
}
}
很简单的一段代码,这时我们来看一下源码的流程
点进 ServiceLoader.load(EatFood.class)中的load,可以到是为传进去的这个拓展点创建了一个服务加载器
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
然后我们点进eatFoodServiceLoader.iterator()中的iterator,然后点进knownProviders.hasNext()
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();
}
};
}
可以看到这样一段代码,然后点进hasNextService()
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);
}
}
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);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
上图的方法就是判断是否有相对应的服务,可以看到中间的代码
String fullName = PREFIX + service.getName();
前者PREFIX就是就是我们刚刚创建的目录路径
service.getName()就是获取我们传进来的EatFood的全限定名
下面这段代码则是会解析该文件pending = parse(service, configs.nextElement());
然后我们再回到刚刚的迭代器,可以看到如果返回了True,继续获取对应的服务
然后在这边可以看到通过反射创建该服务的实例
这就是java的Spi的流程,下面我们讲一讲Dubbo的Spi
二、Dubbo的SPI
上面我们讲了Java的SPI,机智的小伙伴们肯定已经发现了缺点,就是没有办法选取特定的实例,只能通过迭代的方式进行操作。Dubbo中并没有使用Java的SPI,而是重新编写了一套逻辑,可以满足我们指定相对应的实例。
先上一个例子
@SPI
public interface Robot {
void sayHello();
}
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
public class DubboSpiTest {
//测试dubbo spi机制
@Test
public void sayHello() throws Exception {
//1、获得接口的ExtentionLoader
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
//2、根据指定的名字获(key)取对应的实例
Robot robot = extensionLoader.getExtension("optimusPrime");
robot.sayHello();
/*Robot optimusPrime = extens