SPI全名为Service Provider Interface
系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。
简单实现SPI思想
java-spi<pom.xml>
<artifactId>java-spi</artifactId>
<packaging>pom</packaging>
<modules>
<module>service-common</module>
<module>ali-pay</module>
<module>wx-pay</module>
<module>main-test</module>
</modules>
提供服务接口
public interface PayService {
public void pay();
}
ali-pay、wx-pay分别给予实现
ali-pay<pom.xml>
<dependencies>
<dependency>
<artifactId>service-common</artifactId>
<groupId>com.ff</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
public class AliPay implements PayService {
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
wx-pay<pom.xml>
<dependencies>
<dependency>
<artifactId>service-common</artifactId>
<groupId>com.ff</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
public class WXPay implements PayService {
@Override
public void pay() {
System.out.println("微信支付");
}
}
测试
ServiceLoader:一个简单的服务提供商加载工具。(具体可查API)
此时打印控制台打印肯定是什么都没有的,因为我们还没有加载具体服务
public class MainTest {
public static void main(String[] args) {
ServiceLoader<PayService> load = ServiceLoader.load(PayService.class);
for (PayService payService : load) {
System.out.println(payService);
}
}
}
来看看ServiceLoader
按照规范创建文件
wx-pay同理
main-test还需要添加相关依赖
<dependencies>
<dependency>
<artifactId>service-common</artifactId>
<groupId>com.ff</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.ff</groupId>
<artifactId>ali-pay</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.ff</groupId>
<artifactId>wx-pay</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
此时测试,服务就可以得到具体的实现
Java SPI 规范
- 当服务提供者提供了接口的一种具体实现后,在 jar 包的 META-INF/services 目录下创建一个以“接口全路径名”为命名的文件,内容为实现类的全限定名;
- 接口实现类所在的 jar 包放在主程序的 classpath 中;
- 主程序通过 java.util.ServiceLoder 动态装载实现模块,它通过扫描 META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到 JVM;
- SPI 的实现类必须携带一个不带参数的构造方法;