SPI 本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。(约定一个标准将实现类配置在一个地方,运行时动态为接口替换实现类)
一、Java SPI
定义一个接口类
public interface Robot {
void sayHello();
}
定义两个实现类:
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
META-INF/services 文件夹下创建名为org.apache.spi.Robot的文件
org.apache.spi.OptimusPrime
org.apache.spi.Bumblebee
编写Java代码测试类:
public class JavaSPITest {
@Test
public void sayHello() throws Exception {
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("Java SPI");
serviceLoader.forEach(Robot::sayHello);
}
}
运行结果:

JavaSPITest源码执行流程及2个重要方法:

hasNextService():
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();//1.读取文件的位置
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);//2.加载配置文件
} catch (IOException x) {
}
}
//3.按行遍历文件内容
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
nextService():
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;//1 获取解析类的类名
nextName = null;
Class<?> c = null;
try {
//2、通过反射根据类名获取class对象
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {}
try {
//3、对象实例化
S p = service.cast(c.newInstance());
//4、放入缓存
providers.put(cn, p);
return p;
} catch (Throwable x) { }
throw new Error();
}
二、Dubbo SPI
META-INF/dubbo 创建文件
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法
public class DubboSPITest {
@Test
public void sayHello() throws Exception {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}

Dubbo SPI源码执行流程:

通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例
通过 ExtensionLoader 的 getExtension 方法获取拓展类对象
2.1 通过 getExtensionClasses 获取所有的拓展类
2.2 clazz.newInstance(),通过反射创建拓展对象
2.3injectExtension(instance) 向拓展对象中注入依赖 (DUbbo的IOC)
2.4instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))将拓展对象包裹在相应的 Wrapper 对象中(Dubbo的AOP)
三、Dubbo IOC
Dubbo IOC 会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。如果有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
// 遍历目标类的所有方法
for (Method method : instance.getClass().getMethods()) {
// 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
....省略代码....
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通过反射调用 setter 方法设置依赖
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method...");
}
}
}
}
} catch (Exception e) {
....省略代码....
}
return instance;
}
Dubbo IOC 目前仅支持 setter 方式注入
四、Dubbo AOP

循环遍历所有的Wrapper类,依次Wrapper对象进⾏依赖注⼊,返回最终的Wrapper对象(类似于俄罗斯套娃,一层套一层)
以DubboProtocol为例:

五、Dubbo SPI 与Java SPI区别
Java SPI | Dubbo SPI |
|
|