SPI简介
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。
下面我们通过代码演示一下什么是SPI,首先使用Java原生的SPI
Java原生的SPI
- 首先我们先定义一个接口,取名为
Robot
/**
* 演示jdk spi的使用
*
* @author hui.wang
* @since 31 January 2019
*/
public interface Robot {
/**
* 测试接口
*/
void sayHello();
}
- 接着我们定义两个实现类,分别为
Bumblebee
和OptimusPrime
/**
* {@link Robot} impl
*
* @author hui.wang
* @since 31 January 2019
*/
public class OptimusPrime implements Robot{
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
/**
* {@link Robot} impl
*
* @author hui.wang09
* @since 31 January 2019
*/
public class Bumblebee implements Robot{
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
- 接着在
META-INF/services
文件夹下创建一个文件,名称为Robot
的全限定名com.hui.wang.dubbo.learn.spi.Robot
,文件的内容为实现类的全限定名
com.hui.wang.dubbo.learn.spi.Bumblebee
com.hui.wang.dubbo.learn.spi.OptimusPrime
- 现在就可以测试了,我这里没有写测试类,直接写了一个main方法
/**
* <b>SPI简介</b>
* SPI 全称为 Service Provider Interface,是一种服务发现机制。
* SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类
*
* @author hui.wang09
* @since 31 January 2019
*/
public class Main {
public static void main(String[] args) {
/**
* ServiceLoader 会加载META-INF/services 接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类
*/
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("Java SPI");
serviceLoader.forEach(Robot::sayHello);
}
}
输出的结果为:
Java SPI
Hello, I am Bumblebee.
Hello, I am Optimus Prime.
到这里Java原生的SPI简单使用已经结束,想必大家已经知道什么是SPI了,这里需要说明的是 ServiceLoader
会加载META-INF/services 接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这就是整个SPI的原理。
Dubbo 的SPI从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。
Dubbo 改进了 JDK 标准的 SPI 的以下问题:
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
- 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
接下来我们看看dubbo的SPI是怎么使用的,和Java的SPI差不多
dubbo SPI 简单使用
- 首先我们先定义一个接口,取名为
Robot
,这里需要加一个com.alibaba.dubbo.common.extension.SPI
的注解
/**
* 演示jdk spi的使用
*
* @author hui.wang
* @since 31 January 2019
*/
@SPI
public interface Robot {
/**
* 测试接口
*/
void sayHello();
}
- 接着我们定义两个实现类,分别为
Bumblebee
和OptimusPrime
,和上面代码一致,这里不再列出代码 - 接着在
META-INF/dubbo
文件夹下创建一个文件,名称为Robot
的全限定名com.hui.wang.dubbo.learn.spi.Robot
,内容如下:
optimusPrime = com.hui.wang.dubbo.learn.spi.Bumblebee
bumblebee = com.hui.wang.dubbo.learn.spi.OptimusPrime
- 现在开始测试,没有写UT测试,写了main方法
public class DubboSPITest {
public static void main(String[] args) {
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
/**
* dubbo SPI支持默认设计
* 配置 {@link com.alibaba.dubbo.common.extension.SPI}的value属性即可
*/
Robot bumblebee = extensionLoader.getDefaultExtension();
bumblebee.sayHello();
}
}
打印结果为
Hello, I am Bumblebee.
Hello, I am Optimus Prime.
dubbo spi aop简单使用
保持上面的代码不变。
- 编写一个
Robot
实现类,取名为RobotWrapper
,具体代码如下:
/**
* <b>关于 dubbo spi aop 的使用</b>
*
* @author hui.wang09
* @since 22 February 2019
*/
public class RobotWrapper implements Robot{
private Robot robot;
public RobotWrapper(Robot robot) {
this.robot = robot;
}
@Override
public void sayHello() {
System.out.println("========================");
System.out.println("before");
System.out.println("========================");
robot.sayHello();
System.out.println("========================");
System.out.println("after");
System.out.println("========================");
}
}
- 接着编辑在
META-INF/dubbo
文件夹下,名为 Robot 的全限定名com.hui.wang.dubbo.learn.spi.Robot文件,内容如下:
bumblebee=com.hui.wang.dubbo.learn.jdkspi.Bumblebee
optimusPrime=com.hui.wang.dubbo.learn.jdkspi.OptimusPrime
robotWrapper=com.hui.wang.dubbo.learn.dubbospi.RobotWrapper
这里新增了robotWrapper=com.hui.wang.dubbo.learn.dubbospi.RobotWrapper
配置
- 编写测试类,代码如下:
/**
* <b>演示dubbo spi aop</b>
* Wrapper class:
* robotWrapper=com.hui.wang.dubbo.learn.dubbospi.RobotWrapper
*
* optimusPrime:
* optimusPrime=com.hui.wang.dubbo.learn.jdkspi.OptimusPrime
*/
@Test
public void testRobotWrapper() {
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
System.out.println(optimusPrime.getClass());
}
打印结果如下:
========================
before
========================
Hello, I am Optimus Prime.
========================
after
========================
class com.hui.wang.dubbo.learn.dubbospi.RobotWrapper
从打印结果可以看出dubbo spi aop的使用和执行过程,dubbo spi 在加载扩展点时,如果加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper 类。Wrapper 类有些类似 AOP,即 Wrapper 代理了扩展点。
到这里,关于dubbo SPI和Java SPI的简单使用已经结束,关于SPI的概念也基本有了了解。
网友评论