1 Java SPI介绍
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。提供了为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。
1.1 使用场景
概括地说,适用于调用者根据实际使用需要启用、扩展、或者替换框架的实现策略。比较常见的例子:
- 数据库驱动加载接口实现类的加载:JDBC加载不同类型数据库的驱动
- 日志门面接口实现类加载:SLF4J加载不同提供商的日志实现类
- Spring:Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
- Dubbo:Dubbo中也大量使用SPI的方式实现框架的扩展,不过Dubbo自己实现了一套SPI的机制。
1.2 使用介绍
要使用Java SPI,需要遵循如下约定:
- 当服务提供者提供了接口的一种具体实现后,在jar包的
META-INF/services
目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名 - 接口实现类所在的jar包放在主程序的classpath中
- 主程序通过
java.util.ServiceLoder
动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM - SPI的实现类必须携带一个不带参数的构造方法
1.3 简单使用
# 1 准备文件
- META-INFO/services/
- com.ankoye.People # 文件:与接口全限定名一致
- com.ankoye.BlackPeople # 内容:实现接口的类
- com.ankoye.WhitePeople
# 2 测试
ServiceLoader<People> peoples = ServiceLoader.load(People.class);
for (People people : peoples) {
// 两个
}
2 Dubbo SPI
2.1 介绍
Dubbo SPI提供了比Java SPI更加强大的功能,主要有:
- 可以获取单个指定的实现类
- 添加了类似AOP的Wrapper功能
- 实现了类似DI的Adaptive功能
2.2 简单使用
# 1 准备文件
- META-INFO/dubbo/
- com.ankoye.People # 文件:与接口全限定名一致
- black=com.ankoye.BlackPeople # 内容:实现接口的类
- white=com.ankoye.WhitePeople
# 2 测试
ExtensionLoader<People> extensionLoader = ExtensionLoader.getExtensionLoader(People.class);
People people = extensionLoader.getExtension("black");
3 应用
3.1 Wrapper
# 1 准备文件
- META-INFO/dubbo/
- com.ankoye.People
- black=com.ankoye.BlackPeople
- white=com.ankoye.WhitePeople
- com.ankoye.PeopleWrapper
# 2 Wrapper类,这个类需要实现接口,并且需要有个接口类型成员变量和构造
public class PeopleWrapper implements People {
private People people;
public PeopleWrapper(People people) {
this.people = people;
}
}
# 3 测试
ExtensionLoader<People> extensionLoader = ExtensionLoader.getExtensionLoader(People.class);
People people = extensionLoader.getExtension("black"); // 获取到的是Wrapper对象
3.2 @SPI
@SPI("black") // black为默认的实现
public interface People {
}
3.3 @Extention
@Extension("black") // 指定前缀,不推荐
public class BlackPeople implements People {
}
3.4 @Adaptive
@Adaptive
与依赖注入有关,比如我们需要在People
类中注入一个Address
对象,则Address对象需要加@SPI
且每个方法需要加@Adaptive
注解。被注入的Address对象是一个代理对象。
@SPI
public interface Address {
@Adaptive
String getAddress(URL url);
}
public class BlackPeople implements People {
private Address address;
public void setAddress(Address address) {
this.address = address;
}
}
4 源码解析