知识点:SPI(Service Provider Interfaces)到底是啥玩意,简单一句话概括就是用来被第三方实现的 API。有点抽象,我们先仿照Apollo客户端写个简单例子吧
场景举例
假设有一个短信平台需要对接多家SMS渠道用来发送短信。当然这个场景不一定要用SPI机制来实现,毕竟条条大路通罗马。本案例只是用来介绍SPI的使用方式方法。
工程目录
fg-spi-sms-interface 是接口部分,提供了一个默认实现,fg-spi-sms-provider是自定义的一个实现
fg-spi-sms-interface
接口部分的代码结构如下,本模块假设为通用实现
我们首先提供一个短信发送的接口或者抽象类,下面我们新建一个接口如下:
package com.example.fg.sms;
/**
* @author cattle - 稻草鸟人
* @date 2020/4/23 下午12:49
*/
public interface MessageServiceProvider {
/**
* 发送短息
* @param message 短信内容
*/
void sendMessage(String message);
}
其次我们对这个接口做一个默认实现如下:
package com.example.fg.sms;
/**
* @author cattle - 稻草鸟人
* @date 2020/4/23 下午12:57
*/
public class DefaultMessageServiceProvider implements MessageServiceProvider {
@Override
public void sendMessage(String message) {
System.out.println("default:::" + message);
}
}
再次,我们在resources/META-INF/services目录下新建一个文件,名为com.example.fg.sms.MessageServiceProvider
此名字和接口同名,内容为com.example.fg.sms.DefaultMessageServiceProvider
是我们的默认实现
最后,我们就可以通过ServiceLoader.load
。例如,通过如下代码实现执行我们默认方法实现的方法
package com.example.fg.sms;
import java.util.ServiceLoader;
/**
* @author cattle - 稻草鸟人
* @date 2020/4/23 下午12:52
*/
public class MessageServiceFactory {
private ServiceLoader<MessageServiceProvider> serviceProviders = ServiceLoader.load(MessageServiceProvider.class);
public void sendMessage(String message) {
for (MessageServiceProvider serviceProvider : serviceProviders) {
serviceProvider.sendMessage(message);
}
}
}
接口测试
package com.example.fg.sms;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MessageServiceFactoryTest {
@Test
void sendMessage() {
MessageServiceFactory factory = new MessageServiceFactory();
factory.sendMessage("hello spi");
}
}
输出:
default:::hello spi
fg-spi-sms-provider
本模块是另外一个实现,用来模拟某供应商针对接口实现自己的短信发送方法。
接口实现
package com.example.fg.sms;
/**
* @author cattle - 稻草鸟人
* @date 2020/4/23 下午1:06
*/
public class MyMessageServiceProvider implements MessageServiceProvider {
@Override
public void sendMessage(String message) {
System.out.println("MyMessage:::" + message);
}
}
本模块我们依然要在resources/META-INF/services目录下新建一个名为com.example.fg.sms.MessageServiceProvider
内容就是我们自己的实现,即com.example.fg.sms.MyMessageServiceProvider
接口测试
package com.example.fg.sms;
import org.junit.jupiter.api.Test;
class MessageServiceFactoryTest {
@Test
void sendMessage() {
MessageServiceFactory factory = new MessageServiceFactory();
factory.sendMessage("hello spi");
}
}
输出:
MyMessage:::hello spi
default:::hello spi
其他SPI场景
目前开源的很多框架里面,如果大家仔细观察的话,SPI用到了非常多的地方,比如dubbo,motan,日志处理框架,还有JDBC Driver等等。
总结
经过上面的案例其实已经很清楚了,写一个接口,然后自己去实现,另外在resources/META-INF/services目录下新建一个文件夹,内容是具体的实现。然后通过ServiceLoader.load
之后调用具体的实现。
思考
多个实现的情况下,如何只执行其中一种实现呢?比如
MyMessageServiceProvider
和DefaultMessageServiceProvider
两个实现只执行其中一个sendMessage
方法apollo-client中对于SPI的具体应用,和我们当前的例子是否有差别呢?