很多人在学习spring boot的时候尤其是涉及到读启动流程时候,发现在启动时为什么通过引入不同的第三方,就可以加载到对应的内容,比如我要加载Spring-cloud-nacos-discovery组件的时候,会发现启动时会自动到当前该加载内容下的META- INF里面读取一个叫做spring.factores。说到这里,先来了解一下java 自己的spi.
SPI(service provider interface)
说在前面,不是api(Application Programming Interface)
哦!
API 是实现方
制定接口并完成对接口的实现,调用方
仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。如下图就是一个典型的api的例子,是由第三方提供给其对接开发人员使用的一种接口。
Spi是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。
举个例子:
我们在做商城项目的时候,最终需要支付操作,但是支付可以选择支付宝,jd支付或者微信支付,同样都是支付,我如何无缝对接呢?注意我说的无缝是指我这个支付操作模块不会因为对接不同支付系统就要去调整不同的代码!
如何实现呢?!
比如支付宝一个支付类,微信也有一个支付类。我们如何去调用?!我们只需要拿到对方提供的maven提供的JAR包下面有一个/meta-inf/xxxxx的配置key=value来加载扫描启动即可。
代码实现:
一般情况下,不同的第三方支付都会自己的独立的类,为了保证统一集成可以考虑把支付接口封装。以下代码只是演示哦
public interface PayService {
public void pay();
}
public class AliPayService implements PayService {
@Override
public void pay(){
System.out.println("这里调用的是ali支付");
}
}
public class WeiChatPayService implements PayService {
@Override
public void pay(){
System.out.println("这里调用的是微信支付");
}
}
内容包含的是:
com.example.alipay.AliPayService
com.example.wechatpay.WeiChatPayService
最后实现:
import java.util.ServiceLoader;
public class PayMain {
public static void main(String[] args) {
ServiceLoader<PayService> pay = ServiceLoader.load(PayService.class);
for (PayService p : pay) {
p.pay();
}
}
}
执行出结果:
SPI原理解析
这里有个关键类ServiceLoader
其中对应的一个load(接口.class)
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
看一下该实现方法说明:使用当前线程的上下文类加载器为给定的服务类型创建新的服务加载器。
可以看到它返回的是又一个ServiceLoader对象,只不过这里面包含了3个参数,第一个通过反射或得到的类,第二个当前调用接口名,第三个是当前线程下获得的上下文类加载。
可以debug看到
最后,写的挺乱的,后续对内容再调整一下
spi的好处:
- 不需去改代码就可以实现水平扩展。
- 实现扩展对原来的代码几乎没有侵入性。
- 只需要添加或者修改配置就可以实现扩展。
目前dubbo spi ,springboot spi其实都是一种参考和衍生。