SPI
SPI(Service Provider Interface)是 JDK 内置的服务发现机制,用于在运行时动态加载实现类。核心思想:接口由调用方定义,实现由提供方决定。
适用场景
数据库驱动加载、日志框架切换、插件化架构、框架扩展点设计。
SPI vs API
| 对比 | API | SPI |
|---|---|---|
| 定义方 | 实现方提供接口 | 调用方定义接口 |
| 调用关系 | 实现类被调用方直接调用 | 实现类被调用方间接调用 |
| 编译时绑定 | 编译时确定 | 运行时才发现 |
| 扩展方式 | 需要修改代码 | 直接添加实现即可 |
// API 模式:调用方依赖实现方
interface UserService { void save(User user); }
class UserServiceImpl implements UserService {} // 实现方提供
// SPI 模式:调用方定义接口,实现方提供
interface DataSource { Connection getConnection(); }
// mysql-connector.jar / oracle-connector.jar 各自提供实现SPI 使用流程
1. 定义接口
public interface AnimalService {
void eat();
}2. 实现接口
在 resources/META-INF/services/ 目录下创建文件,文件名为接口全限定名:
# 文件名:com.example.spi.AnimalService
com.example.spi.impl.DogService
com.example.spi.impl.CatServicepublic class DogService implements AnimalService {
@Override
public void eat() { System.out.println("狗吃骨头"); }
}
public class CatService implements AnimalService {
@Override
public void eat() { System.out.println("猫吃鱼"); }
}3. 加载服务
public class SpiMain {
public static void main(String[] args) {
ServiceLoader<AnimalService> loader = ServiceLoader.load(AnimalService.class);
for (AnimalService animal : loader) {
animal.eat();
}
}
}
// 输出:狗吃骨头 猫吃鱼SPI 加载流程
JDBC 中的 SPI
JDBC 使用 SPI 自动加载数据库驱动,无需手动 Class.forName():
// mysql-connector-java.jar 中有 META-INF/services/java.sql.Driver
// 只需加载驱动,SPI 会自动发现并加载 Driver 实现
Connection conn = DriverManager.getConnection(url, username, password);原理:DriverManager 初始化时调用 ServiceLoader.load(Driver.class) 扫描所有 JAR 包的 META-INF/services/。
Spring SPI
Spring 使用 spring.factories 文件扩展 SPI:
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationList<String> configs = SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class, classloader
);| 对比 | JDK SPI | Spring SPI |
|---|---|---|
| 配置文件 | META-INF/services/ | META-INF/spring.factories |
| 加载方式 | ServiceLoader.load() | SpringFactoriesLoader.loadFactoryNames() |
Dubbo SPI
Dubbo 扩展了原生 SPI,支持自适应扩展、自动包装、自动激活、懒加载。
@SPI
public interface Protocol {
void export();
void refer();
}
public class DubboProtocol implements Protocol { /* ... */ }
// 4. 在 Dubbo 扩展配置文件中注册
# META-INF/dubbo/org.apache.dubbo.rpc.Protocol
dubbo=com.apache.dubbo.rpc.protocol.dubbo.DubboProtocol| 注解 | 作用 |
|---|---|
@SPI | 标记接口为 SPI 接口 |
@Adaptive | 运行时根据参数选择实现 |
@Activate | 满足条件时自动加载 |
| 扩展接口 | 说明 |
|---|---|
Protocol | RPC 协议(Dubbo/Hessian/JSON) |
Filter | 过滤器(限流、日志) |
LoadBalance | 负载均衡(Random/RoundRobin) |
Cluster | 集群策略(Failover/Failfast) |
Registry | 注册中心(ZooKeeper/Nacos) |
自定义 ServiceLoader
public class ServiceLoaderUtil {
// 获取第一个可用实现
public static <T> T getFirst(Class<T> clazz) {
ServiceLoader<T> loader = ServiceLoader.load(clazz);
for (T service : loader) { return service; }
return null;
}
// 根据名称获取特定实现
public static <T> T getByName(Class<T> clazz, String name) {
ServiceLoader<T> loader = ServiceLoader.load(clazz);
for (T service : loader) {
if (service.getClass().getName().endsWith(name)) { return service; }
}
return null;
}
}应用场景
| 场景 | 说明 |
|---|---|
| 数据库驱动 | JDBC 自动发现 java.sql.Driver |
| 日志框架 | SLF4J 发现具体实现 |
| Servlet 容器 | ServletContainerInitializer |
| Spring Boot 自动配置 | spring.factories 中的 EnableAutoConfiguration |
| Dubbo RPC | 可插拔的协议实现 |
注意事项
- 性能:
ServiceLoader每次调用都重新扫描 classpath,频繁调用有性能开销 - 顺序不保证:多个实现类的加载顺序不确定
- 单例问题:
ServiceLoader本身不是线程安全的 - 类加载器隔离:不同 ClassLoader 下的 SPI 无法互相发现