SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
本文对比JDK、Spring对SPI机制的实现。
在实际的应用场景。
- jdbc驱动,不同的数据有不同的驱动。

com.mysql.cj.jdbc.Driver- spring 也使用spi机制,用于扩展实现。

org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory- dubbo也通过spi机制实现自定义扩展。
- 日志门面接口实现类加载,SLF4J加载不同提供商的日志是现类。
SPI
演示代码
JDK 中 提供了一个 SPI 的功能,核心类是 java.util.ServiceLoader。其作用就是,可以通过类名获取在"META-INF/services/"下的多个配置实现文件。
ServiceLoader
- META-INF/services下的文件(以Interface全路径命名)中添加具体实现类的全路径名。
- 使用程序使用ServiceLoader动态加载实现类(根据目录META-INF/services下的配置文件找到实现类的全限定名并调用classloader来加载实现类到JVM)
- SPI的实现类必须具有无参数的构造方法
演示代码
目录
└── src
├── main
│ ├── java
│ │ └── cn
│ │ └── z201
│ │ └── spi
│ │ ├── SpeakChinese.java
│ │ ├── SpeakEnglish.java
│ │ ├── SpeakGerman.java
│ │ ├── SpeakSPIService.java // 定义接口
│ │ └── package-info.java
│ └── resources
│ └── META-INF
│ └── services
│ └── cn.z201.spi.SpeakSPIService // 配置文件,文件名以接口包和类名称组成。
接口
public interface SpeakSPIService {
Object echo();
}
实现类
public class SpeakGerman implements SpeakSPIService{
@Override
public Object echo() {
return "german : hallo";
}
}
public class SpeakEnglish implements SpeakSPIService{
@Override
public Object echo() {
return "english : hello";
}
}
public class SpeakChinese implements SpeakSPIService {
@Override
public Object echo() {
return "chinese : 你好";
}
}
配置文件
- SpeakSPIService就是接口的名称
cn.z201.spi.SpeakChinese
cn.z201.spi.SpeakGerman
cn.z201.spi.SpeakEnglish
测试
class SpeakSPIServiceTest {
@Test
public void setUp(){
ServiceLoader<SpeakSPIService> serviceLoader = ServiceLoader.load(SpeakSPIService.class, Thread.currentThread().getContextClassLoader());
Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpeakSPIService speakSPIService = iterator.next();
System.out.println("speakSPIService " + speakSPIService.echo());
}
}
}
- Console
speakSPIService chinese : 你好
speakSPIService german : hallo
speakSPIService english : hello
# 实际工作中这么用肯定太难用了,这里小改动一下。
改进
- 使用注解标注类型,通过注解信息获取对应的实现类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LanguageType {
String language();
}
@LanguageType(language = "chinese")
public class SpeakChinese implements SpeakSPIService {
@Override
public Object echo() {
return "chinese : 你好";
}
}
@LanguageType(language = "english")
public class SpeakEnglish implements SpeakSPIService{
@Override
public Object echo() {
return "english : hello";
}
}
@LanguageType(language = "german")
public class SpeakGerman implements SpeakSPIService{
@Override
public Object echo() {
return "german : hallo";
}
}
// 工具类,根据类注解反射获取数据
public class LanguageTypeTool {
public static SpeakSPIService language(String language){
ServiceLoader<SpeakSPIService> serviceLoader = ServiceLoader.load(SpeakSPIService.class, Thread.currentThread().getContextClassLoader());
Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpeakSPIService speakSPIService = iterator.next();
Class clazz = speakSPIService.getClass();
LanguageType languageType = (LanguageType) clazz.getDeclaredAnnotation(LanguageType.class);
if (null != languageType) {
if (Objects.equals(language,languageType.language())) {
return speakSPIService;
}
}
}
return null;
}
}
- 测试
@Test
public void setUp() {
ServiceLoader<SpeakSPIService> serviceLoader = ServiceLoader.load(SpeakSPIService.class, Thread.currentThread().getContextClassLoader());
Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpeakSPIService speakSPIService = iterator.next();
System.out.println(speakSPIService.echo());
}
SpeakSPIService speakSPIService = LanguageTypeTool.language("chinese");
if (null != speakSPIService) {
System.out.println(speakSPIService.echo());
}
speakSPIService = LanguageTypeTool.language("english");
if (null != speakSPIService) {
System.out.println(speakSPIService.echo());
}
}
- Console
chinese : 你好
german : hallo
english : hello
chinese : 你好
english : hello
ServiceLoaderFactoryBean
通过Java提供的SPI的方式来加载相关实现类并注册到Spring容器中。
public class ServiceLoaderFactoryBean extends AbstractServiceLoaderBasedFactoryBean implements BeanClassLoaderAware {
@Override
protected Object getObjectToExpose(ServiceLoader<?> serviceLoader) {
return serviceLoader;
}
// 这里引用的就是jdk中的ServiceLoader
@Override
public Class<?> getObjectType() {
return ServiceLoader.class;
}
}
演示代码
- 项目引用JDK-SPI的代码
<dependency>
<groupId>cn.z201.spi</groupId>
<artifactId>Jdk-SPI</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
配置类
/**
* @author z201.coding@gmail.com
**/
@Configuration
public class SpeakSPIServiceConfig {
// 指定bean的id ,通过依赖查找或者依赖注入可以找到。
@Bean("initBeanFactoryServiceLoaderFactoryBean")
public ServiceLoaderFactoryBean serviceLoaderFactoryBean() {
ServiceLoaderFactoryBean serviceLoaderFactoryBean = new ServiceLoaderFactoryBean();
serviceLoaderFactoryBean.setServiceType(SpeakSPIService.class);
return serviceLoaderFactoryBean;
}
}
测试
@Test
public void setSpringUp() {
// 创建BeanFactory 容器
AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext();
config.register(SpeakSPIServiceConfig.class);
// 启动上下文
config.refresh();
ServiceLoader<SpeakSPIService> serviceLoader = config.getBean("initBeanFactoryServiceLoaderFactoryBean", ServiceLoader.class);
displayServiceLoader(serviceLoader);
// 关闭上下文
config.close();
}
private void displayServiceLoader(ServiceLoader<SpeakSPIService> serviceLoader){
Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpeakSPIService speakSPIService = iterator.next();
System.out.println("speakSPIService " + speakSPIService.echo());
}
}
- Console
speakSPIService chinese : 你好
speakSPIService german : hallo
speakSPIService english : hello
# 既然用到了spring 当然不会这么简单就结束。
改进
- 接口实现类注册成bean。
@Test
public void setSpringRegisterBeanUp() {
// 创建BeanFactory 容器
AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext();
config.register(SpeakSPIServiceConfig.class);
// 启动上下文
config.refresh();
registerBean(config);
lookupCollectionType(config);
// 关闭上下文
config.close();
}
private void registerBean(AnnotationConfigApplicationContext applicationContext){
ServiceLoader<SpeakSPIService> serviceLoader = applicationContext.getBean("initBeanFactoryServiceLoaderFactoryBean", ServiceLoader.class);
Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpeakSPIService speakSPIService = iterator.next();
Class clazz = speakSPIService.getClass();
LanguageType languageType = (LanguageType) clazz.getDeclaredAnnotation(LanguageType.class);
if (null != languageType) {
applicationContext.registerBean(languageType.language(),clazz);
}
}
}
/**
* 单一类型集合查找
*
* @param beanFactory
*/
private void lookupCollectionType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, SpeakSPIService> beansMap = listableBeanFactory.getBeansOfType(SpeakSPIService.class);
// getBeansOfType 匹配所有类型的 bean,无论是单例、原型还是 FactoryBean , bean name 作为key value 作为对象
beansMap.forEach((key, value) -> {
System.out.println("单一类型集合查找 " + key + " " + value.echo());
});
}
}
- Console
单一类型集合查找 chinese chinese : 你好
单一类型集合查找 german german : hallo
单一类型集合查找 english english : hello