1 概述
SPI的官方文档说明:Dubbo SPI | Apache Dubbo
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
dubbo通过SPI完成的功能:
1)程序运行时切换实现类
2)支持用户扩展,我们可以定义自己的实现类,并且使用该类
3)依赖注入,实现类依赖的属性自动注入,类似spring的依赖注入。
4)支持AOP功能,支持类似spring的AOP功能,在dubbo中AOP功能的类要求以Wrapper名字结尾类。
2 功能举例
2.0 项目准备
使用官方例子,这里直接上代码了。maven工程,保证有以下4个文件。
1个接口Robot,2个实现类Bumblebee、OptimusPrime,1个配置文件org.example.test.exLoader.Robot。
1个接口Robot
package org.example.test.exLoader;
import org.apache.dubbo.common.extension.SPI;
/**
* 机器人
*/
@SPI
public interface Robot {
void sayHello();
}
2个实现类
package org.example.test.exLoader;
/**
* 大黄蜂
*/
public class Bumblebee implements Robot{
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
package org.example.test.exLoader;
/**
* 擎天柱
*/
public class OptimusPrime implements Robot{
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
1个配置文件
文件:resources/META-INF/dubbo/org.example.test.exLoader.Robot。
等号前面的是名字(文件内唯一, 代码中靠名字来找到对应的类),等号后面的是实现类,文件名是接口的全名。
optimusPrime = org.example.test.exLoader.OptimusPrime
bumblebee = org.example.test.exLoader.Bumblebee
maven依赖
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
2.1 程序运行时切换实现类
package org.example.test.exLoader;
import org.apache.dubbo.common.extension.ExtensionLoader;
/** 动态切换实现类 */
public class DubboSPIChangeImplTest {
public static void main(String[] args) {
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
System.out.println(optimusPrime.getClass());
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
System.out.println(bumblebee.getClass());
bumblebee.sayHello();
}
}
代码运行结果如下图,可以看到通过optimusPrime名字可以拿到对应类的实例。
2.2 支持用户扩展
dubbo框架中的SPI都定义在dubbo.jar的/META-INF/dubbo.internal文件夹下。我们来看一下LoadBalance的SPI。
可以看到LoadBalance SPI已经有5个实现了。我们扩展一个自己的。
1)定义一个LoadBalance实现类AlfLoadBalance
package org.example.test.exExtend;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import java.util.List;
/** 自定义实现类*/
public class AlfLoadBalance implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
return null;
}
}
2)创建一个配置文件
文件名:org.apache.dubbo.rpc.cluster.LoadBalance
alf=org.example.test.exExtend.AlfLoadBalance
3)编写测试代码
package org.example.test.exExtend;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.cluster.LoadBalance;
/** 动态切换实现类 */
public class DubboSPIExtendTest {
public static void main(String[] args) {
ExtensionLoader<LoadBalance> extensionLoader = ExtensionLoader.getExtensionLoader(LoadBalance.class);
LoadBalance alf = extensionLoader.getExtension("alf");
System.out.println(alf.getClass().getSimpleName());
LoadBalance random = extensionLoader.getExtension("random");
System.out.println(random.getClass().getSimpleName());
}
}
运行结果:AlfLoadBalance类我们自己定义,RandomLoadBalance类dubbo自带的。
读到这里,可能有个疑问,dubbo想调用我的实现,如果通过类似LoadBalance alf = extensionLoader.getExtension("alf") 的方法,岂不需要我去修改dubbo源码才能实现?这肯定是不现实的。dubbo通过@Adaptive("loadbalance")自适应SPI+URL参数来解决这个问题的,另起一篇单说吧。
2.3 依赖注入
依赖注入,实现类依赖的属性自动注入,类似spring的依赖注入。
如果字段不想不注入,可以在set方法上使用DisableInject注解。
被依赖注入的属性类的方法必须有@Adaptive注解,即这个类是一个自适应SPI。
本例中OptimusPrime类新添加了属性weapon也是一个扩展。所以该属性会被自动注入到OptimusPrime类的对象中。
例子总体文件图
OptimusPrime类
package org.example.test.exLoader;
/**
* 擎天柱
*/
public class OptimusPrime implements Robot{
private Weapon weapon;
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
Weapon类是接口(dubbo中的扩展),注意@Adaptive("weaponKey")必须有,否则程序会报错。
package org.example.test.exLoader;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI("aWeapon")
public interface Weapon {
@Adaptive("weaponKey")
void open(URL url);
}
WeaponA、WeaponB是两个武器的实现类
public class AWeapon implements Weapon{
@Override
public void open(URL url) {
System.out.println("AWeapon");
}
}
public class BWeapon implements Weapon{
@Override
public void open(URL url) {
System.out.println("BWeapon");
}
}
org.example.test.exLoader.Weapon 文件
aWeapon = org.example.test.exLoader.AWeapon
bWeapon = org.example.test.exLoader.BWeapon
测试类
package org.example.test.exLoader;
import org.apache.dubbo.common.extension.ExtensionLoader;
/** 动态切换实现类 */
public class DubboSPI_DITest {
public static void main(String[] args) {
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
System.out.println(optimusPrime.getClass());
Weapon weapon = ((OptimusPrime)optimusPrime).getWeapon();
System.out.println(weapon.getClass());
}
}
运行结果:Weapon$Adaptive表示是自适应SPI。
依赖注入主要的代码逻辑在ExtensionLoader类的createExtension方法中,感兴趣的同学可以调试以下。
2.4 支持AOP功能
支持类似spring的AOP功能,在dubbo中AOP功能的类要求以Wrapper名字结尾类。
例子总体文件图
Robot是接口,OptimusPrime类是Robot接口的实现类,这两个类上面介绍了,没有修改。Robotwrapper1是一个AOP功能的类,也实现了Robot接口。org.example.test.exLoader.Robot文件是扩展的配置文件。DubboSPI_WrapperTest类是运行类。
Robotwrapper1类
package org.example.test.exLoader;
/** Robot的wapper */
public class RobotWapper1 implements Robot{
private Robot robot;
public RobotWapper1(Robot robot){
this.robot = robot;
}
@Override
public void sayHello() {
System.out.println("Before do something!!!");
this.robot.sayHello();
System.out.println("After do something!!!");
}
}
org.example.test.exLoader.Robot文件
Wrapper前面的名字gaga可以不写,直接写org.example.test.exLoader.RobotWapper1也行。
DubboSPI_WrapperTest类
package org.example.test.exLoader;
import org.apache.dubbo.common.extension.ExtensionLoader;
/** 动态切换实现类 */
public class DubboSPI_WrapperTest {
public static void main(String[] args) {
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
System.out.println(optimusPrime.getClass().getSimpleName());
}
}
运行结果:
3 关于配置文件加载路径
请参考LoadingStrategy这个接口的实现类。dubbo会加载3个路径:
ServicesLoadingStrategy类: 加载"META-INF/services/"
DubboLoadingStrategy类: 加载“META-INF/dubbo/”
DubboInternalLoadingStrategy类:加载“META-INF/dubbo/internal/”
4 源码位置
ExtensionLoader类。
5 参考资源
Dubbo SPI | Apache Dubbo
03 Dubbo SPI 精析,接口实现两极反转(上)_哔哩哔哩_bilibili
03 Dubbo SPI 精析,接口实现两极反转(上) - 打工人技术合集