在 Java 中,SPI(Service Provider Interface)全称为服务提供者接口,它是一种用于实现框架扩展和插件化的机制。
一、SPI 作用
允许在运行时动态地为接口查找服务实现,而不需要在代码中显式地指定具体的实现类。
这使得框架具有更好的可扩展性,第三方可以通过实现特定的接口来为框架提供额外的功能。这在很多框架和库中都有广泛的应用,例如数据库驱动、日志框架等。
二、基本原理
1、定义服务接口:首先定义一个接口或抽象类,这是服务的规范。
2、实现服务接口:编写接口的具体实现类。
3、注册服务实现:在 META-INF/services 目录下创建一个以接口全限定名为文件名的文件,并在该文件中列出所有实现类的全限定名。
4、加载服务实现:使用 ServiceLoader 类来加载和使用服务实现。
三、代码样例
1、定义服务接口
假设现在有一个权威机构,比如 Java,它需要对数据存储进行规范。
它定义了一个数据存储接口,和一个加载实现类的工具类。
package com.storage.specification;/*** 数据存储接口*/
public interface StorageProvider {void save(String data);
}
---------------------------------------------------
package com.storage.specification.spi;import com.storage.specification.StorageProvider;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;/*** 加载 存储服务 工具*/
public class ProviderLoader {public static StorageProvider getStorageProvider(ClassLoader classLoader) {// 从类路径 resources/META-INF/services 下加载 存储服务提供者ServiceLoader<StorageProvider> loader = ServiceLoader.load(StorageProvider.class, classLoader);Iterator<StorageProvider> iterator = loader.iterator();List<StorageProvider> providers = new ArrayList<>();while (iterator.hasNext()) {providers.add(iterator.next());}return providers.get(0);}
}
步骤:
1)用IDEA 新建一个 maven 项目,取名叫 storage-specification
2)新建上述两个类
3)install 项目到本地仓库,以便后面项目可以用 gav 坐标引用到
2、实现服务接口
现在有两个供应商 Mysql 和 Redis,它们拿到 Java 给的规范,分别提供他们的 二进制文件 存储实现和内存数据库实现。
Mysql 实现:
1)用IDEA 新建一个 maven 项目,取名叫 mysql-storage-provider
2)pom 文件依赖 存储规范 gav
3) 编写实现接口
package com.mysql.storage;import com.storage.specification.StorageProvider;public class MysqlStorageProvider implements StorageProvider {@Overridepublic void save(String s) {System.out.println("mysql save " + s);} }
4)在项目 resources/META-INF/services 目录下
创建一个以接口全限定名
com.storage.specification.StorageProvider 为文件名的文件(注意不要带后缀),
并在该文件中列出所有实现类的全限定名 com.mysql.storage.MysqlStorageProvider
5)install 项目到本地仓库,以便后面项目可以用 gav 坐标引用到
以同样的方式创建一个 Redis 项目,取名 redis-storage-provider
区别只在
1)实现类不同
package com.redis.storage;import com.storage.specification.StorageProvider;public class RedisStorageProvider implements StorageProvider {@Overridepublic void save(String s) {System.out.println("redis save " + s);} }
2) 实现类的全限定名不同
3)同样 install 项目到本地仓库,以便后面项目可以用 gav 坐标引用到
3、定义服务使用者
1)用 IDEA 新建一个 maven 项目,取名叫 storage-user
2)pom 依赖引入 规范 gav 和 mysql gav
<dependencies><dependency><groupId>com.storage.specification</groupId><artifactId>storage-specification</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.mysql.storage</groupId><artifactId>mysql-storage-provider</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
3) 调用 mysql 存储
4)切换 redis 依赖
<dependencies><dependency><groupId>com.storage.specification</groupId><artifactId>storage-specification</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.redis.storage</groupId><artifactId>redis-storage-provider</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
5)调用 redis 存储
可以看到,我们只修改了依赖的实现包,并没有修改代码,就实现了服务的替换。
而且我们的代码中并没有显示地调用服务的实现类。
四、总结
1、SPI 是 JAVA 提供给我们的一种用于实现框架扩展和插件化的机制。它可以让我们在不修改代码的时候,很轻松地替换服务的实现。
2、Spring 有自己的 SPI,Spring 的 SPI 路径是 resources/META-INF/spring.factories。
3、SPI 机制在框架集成中很常见。像 javax.validation 就是通过 SPI 的方式集成了 HibernateValidator 实现。