深入理解SPI机制
一. 什么是SPI
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的
META-INF/services
文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。我们先通过一个很简单的例子来看下它是怎么用的.
小例子
首先,我们需要定义一个接口,SPIService
public interface SPIService {
void execute();
}
然后,定义两个实现类
public class SpiImpl1 implements SPIService{
public void execute() {
System.out.println("SpiImpl1.execute()");
}
}
public class SpiImpl2 implements SPIService{
public void execute() {
System.out.println("SpiImpl2.execute()");
}
}
最后呢,要在ClassPath路径下配置添加一个文件。文件名字是接口的全限定类名,内容是实现类的全限定类名,多个实现类用换行符分隔。
文件路径如下:
内容就是实现类的全限定类名:
cloud.tianai.rpc.demo.SpiImpl1
cloud.tianai.rpc.demo.SpiImpl2
使用JAVA自带的类查找 SPI 扩展
然后我们就可以通过ServiceLoader.load或者Service.providers
方法拿到实现类的实例。其中,Service.providers
包位于sun.misc.Service
,而ServiceLoader.load
包位于java.util.ServiceLoader
。
public static void main(String[] args) {
// 方式 1
Iterator<SPIService> providers = Service.providers(SPIService.class);
while(providers.hasNext()) {
SPIService ser = providers.next();
ser.execute();
}
// 方式 2
ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);
Iterator<SPIService> iterator = load.iterator();
while(iterator.hasNext()) {
SPIService ser = iterator.next();
ser.execute();
}
}
DUBBO 的 SPI机制
Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。
Dubbo 改进了 JDK 标准的 SPI 的以下问题:
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过
getName()
获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。 - 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
约定:
在扩展类的 jar 包内 [1],放置扩展点配置文件 META-INF/dubbo/接口全限定名
,内容为:配置名=扩展实现类全限定名
,多个实现类用换行符分隔。
示例:
首先,我们定义一个接口,名称为 Robot。
public interface Robot {
void sayHello();
}
接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。
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/dubbo
文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
做好所需的准备工作,接下来编写代码进行测试。
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源码分析 参考 http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html
总结:
-
合理使用 SPI 机制可以大大降低项目的耦合度,
-
这一功能对第三方架构定做非常有用,
-
在自身的 业务中台 或其他架构中使用SPI 后扩展会非常方便
-
著名框架 SpringBoot的自动化部署也是实现了一个类似于 SPI 架构的功能才得以无感知扩展
----------------------------------------- 广告时间 -----------------------------------------
各位看官, 欢迎关注公众号,每天推送有意思的小东西哦!!!