核心概念
SPI(Service Provider Interface) 是一种服务发现机制,用于在运行时动态加载接口的实现类,实现面向接口编程和可插拔扩展。
- JDK SPI:Java 原生提供的 SPI 机制
- Dubbo SPI:Dubbo 框架自己实现的增强版 SPI
核心目标:实现框架的高扩展性,让用户可以替换或增加功能模块,而不修改框架源码。
JDK SPI 机制
1. 实现方式
核心类:java.util.ServiceLoader
使用步骤:
(1)定义接口
package com.example;
public interface DataStorage {
void save(String data);
}
(2)实现接口
// MySQL 实现
package com.example.impl;
public class MySQLStorage implements DataStorage {
@Override
public void save(String data) {
System.out.println("保存到 MySQL: " + data);
}
}
// Redis 实现
package com.example.impl;
public class RedisStorage implements DataStorage {
@Override
public void save(String data) {
System.out.println("保存到 Redis: " + data);
}
}
(3)配置文件
# 文件路径:META-INF/services/com.example.DataStorage
# 文件内容(每行一个实现类的全限定名)
com.example.impl.MySQLStorage
com.example.impl.RedisStorage
(4)加载使用
public class JdkSpiDemo {
public static void main(String[] args) {
// 加载所有实现类
ServiceLoader<DataStorage> loader = ServiceLoader.load(DataStorage.class);
// 遍历并使用
for (DataStorage storage : loader) {
storage.save("test data");
}
// 输出:
// 保存到 MySQL: test data
// 保存到 Redis: test data
}
}
2. JDK SPI 的源码实现
public final class ServiceLoader<S> implements Iterable<S> {
private static final String PREFIX = "META-INF/services/";
private final Class<S> service;
private final ClassLoader loader;
private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(service, cl);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = svc;
loader = cl;
reload();
}
public void reload() {
providers.clear();
// 1. 读取配置文件
String fullName = PREFIX + service.getName();
Enumeration<URL> configs = loader.getResources(fullName);
// 2. 解析配置文件
while (configs.hasMoreElements()) {
URL url = configs.nextElement();
BufferedReader reader = new BufferedReader(
new InputStreamReader(url.openStream(), "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
// 3. 反射创建实例
Class<?> clazz = Class.forName(line, false, loader);
S provider = service.cast(clazz.newInstance());
providers.put(line, provider);
}
}
}
@Override
public Iterator<S> iterator() {
return providers.values().iterator();
}
}
3. JDK SPI 的缺点
(1)一次性加载所有实现
// 问题:即使只需要一个实现,也会加载并实例化所有实现类
ServiceLoader<DataStorage> loader = ServiceLoader.load(DataStorage.class);
// 假设有 10 个实现类,全部被实例化
// 造成资源浪费
(2)不支持按需加载
// 无法指定加载某个实现
// 只能遍历所有实现,手动筛选
for (DataStorage storage : loader) {
if (storage instanceof MySQLStorage) {
// 找到需要的实现
storage.save("data");
break;
}
}
(3)不支持依赖注入
// 实现类的构造函数必须是无参的
public class MySQLStorage implements DataStorage {
// 无法通过构造函数注入依赖
public MySQLStorage() { }
}
// 如果需要依赖,只能硬编码
public class MySQLStorage implements DataStorage {
private DataSource dataSource = new HikariDataSource(...); // 硬编码
}
(4)不支持 AOP 和 IOC
// 实例化的对象是普通对象,没有经过 Spring 容器管理
// 无法使用 @Autowired、@Transactional 等注解
(5)获取扩展失败时,处理不友好
// 如果某个实现类加载失败,整个 ServiceLoader 都失败
// 无法跳过错误的实现,继续加载其他实现
Dubbo SPI 机制
1. 实现方式
核心类:org.apache.dubbo.common.extension.ExtensionLoader
使用步骤:
(1)定义接口(添加 @SPI 注解)
package com.example;
import org.apache.dubbo.common.extension.SPI;
@SPI("mysql") // 指定默认实现
public interface DataStorage {
void save(String data);
}
(2)实现接口
// MySQL 实现
package com.example.impl;
public class MySQLStorage implements DataStorage {
@Override
public void save(String data) {
System.out.println("保存到 MySQL: " + data);
}
}
// Redis 实现
package com.example.impl;
public class RedisStorage implements DataStorage {
@Override
public void save(String data) {
System.out.println("保存到 Redis: " + data);
}
}
(3)配置文件(Key-Value 格式)
# 文件路径:META-INF/dubbo/com.example.DataStorage
# 文件内容(Key=Value 格式)
mysql=com.example.impl.MySQLStorage
redis=com.example.impl.RedisStorage
(4)加载使用
public class DubboSpiDemo {
public static void main(String[] args) {
// 1. 获取 ExtensionLoader
ExtensionLoader<DataStorage> loader =
ExtensionLoader.getExtensionLoader(DataStorage.class);
// 2. 按名称加载指定实现(按需加载)
DataStorage mysql = loader.getExtension("mysql");
mysql.save("data1");
DataStorage redis = loader.getExtension("redis");
redis.save("data2");
// 3. 获取默认实现
DataStorage defaultStorage = loader.getDefaultExtension();
defaultStorage.save("data3");
// 输出:
// 保存到 MySQL: data1
// 保存到 Redis: data2
// 保存到 MySQL: data3 (默认实现)
}
}
2. Dubbo SPI 的核心源码
public class ExtensionLoader<T> {
// 扩展加载器缓存
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS =
new ConcurrentHashMap<>();
// 扩展实例缓存(单例)
private final ConcurrentMap<String, Holder<Object>> cachedInstances =
new ConcurrentHashMap<>();
// 扩展类缓存
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
/**
* 获取 ExtensionLoader(单例)
*/
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// 从缓存获取
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
// 创建新的 ExtensionLoader
loader = new ExtensionLoader<>(type);
EXTENSION_LOADERS.putIfAbsent(type, loader);
}
return loader;
}
/**
* 按名称获取扩展实例(单例 + 延迟加载)
*/
public T getExtension(String name) {
// 1. 从缓存获取
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
holder = new Holder<>();
cachedInstances.putIfAbsent(name, holder);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 2. 创建扩展实例
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
/**
* 创建扩展实例
*/
private T createExtension(String name) {
// 1. 加载扩展类
Class<?> clazz = getExtensionClasses().get(name);
// 2. 反射创建实例
T instance = (T) clazz.newInstance();
// 3. 依赖注入(IOC)
injectExtension(instance);
// 4. AOP 包装
instance = wrapExtension(instance);
return instance;
}
/**
* 依赖注入
*/
private T injectExtension(T instance) {
// 遍历所有 setter 方法
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1) {
// 获取依赖类型
Class<?> pt = method.getParameterTypes()[0];
try {
// 自动注入扩展依赖
Object object = objectFactory.getExtension(pt, name);
method.invoke(instance, object);
} catch (Exception e) {
// 注入失败,继续
}
}
}
return instance;
}
/**
* AOP 包装
*/
private T wrapExtension(T instance) {
List<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
// 用 Wrapper 类包装原始实例
instance = (T) wrapperClass.getConstructor(type).newInstance(instance);
}
}
return instance;
}
/**
* 加载扩展类
*/
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 从配置文件加载
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
/**
* 从配置文件加载扩展类
*/
private Map<String, Class<?>> loadExtensionClasses() {
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 1. 读取配置文件(支持多个目录)
String[] directories = new String[] {
"META-INF/dubbo/internal/",
"META-INF/dubbo/",
"META-INF/services/"
};
for (String directory : directories) {
String fileName = directory + type.getName();
Enumeration<URL> urls = classLoader.getResources(fileName);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 2. 解析配置文件(Key=Value 格式)
Properties properties = new Properties();
properties.load(url.openStream());
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String name = (String) entry.getKey();
String className = (String) entry.getValue();
// 3. 加载类
Class<?> clazz = Class.forName(className, true, classLoader);
extensionClasses.put(name, clazz);
}
}
}
return extensionClasses;
}
}
3. Dubbo SPI 的增强特性
(1)按需加载(延迟加载)
ExtensionLoader<DataStorage> loader =
ExtensionLoader.getExtensionLoader(DataStorage.class);
// 只加载需要的实现,不会加载所有实现
DataStorage mysql = loader.getExtension("mysql");
(2)依赖注入(IOC)
@SPI
public interface DataStorage {
void save(String data);
}
public class MySQLStorage implements DataStorage {
// 自动注入依赖(通过 setter)
private Logger logger;
public void setLogger(Logger logger) {
this.logger = logger;
}
@Override
public void save(String data) {
logger.info("保存数据: " + data);
}
}
(3)AOP 支持(Wrapper 机制)
// 原始实现
public class MySQLStorage implements DataStorage {
@Override
public void save(String data) {
System.out.println("保存到 MySQL: " + data);
}
}
// Wrapper 包装类(自动识别并包装)
public class MonitorWrapper implements DataStorage {
private final DataStorage storage;
public MonitorWrapper(DataStorage storage) {
this.storage = storage;
}
@Override
public void save(String data) {
long start = System.currentTimeMillis();
storage.save(data); // 调用原始实现
long cost = System.currentTimeMillis() - start;
System.out.println("耗时: " + cost + "ms");
}
}
// 配置文件
// META-INF/dubbo/com.example.DataStorage
// mysql=com.example.impl.MySQLStorage
// 调用时自动包装
DataStorage storage = loader.getExtension("mysql");
storage.save("data");
// 输出:
// 保存到 MySQL: data
// 耗时: 10ms
(4)自适应扩展(@Adaptive)
@SPI("mysql")
public interface DataStorage {
// 根据 URL 参数动态选择实现
@Adaptive({"storage.type"})
void save(URL url, String data);
}
// 使用
URL url = URL.valueOf("dubbo://localhost/DataStorage?storage.type=redis");
DataStorage storage = loader.getAdaptiveExtension();
storage.save(url, "data"); // 动态选择 RedisStorage
(5)自动激活(@Activate)
@SPI
public interface Filter {
void filter(Invocation invocation);
}
@Activate(group = "consumer", order = -1)
public class ConsumerFilter implements Filter {
// Consumer 端自动激活
}
@Activate(group = "provider", order = 1)
public class ProviderFilter implements Filter {
// Provider 端自动激活
}
// 根据条件自动激活
List<Filter> filters = loader.getActivateExtension(url, "filter", "consumer");
JDK SPI vs Dubbo SPI 对比
| 维度 | JDK SPI | Dubbo SPI |
|---|---|---|
| 加载方式 | 一次性加载所有实现 | 按需加载(延迟加载) |
| 配置格式 | 实现类全限定名(列表) | Key=Value 格式 |
| 获取方式 | 遍历所有实现 | 按名称获取指定实现 |
| 依赖注入 | ❌ 不支持 | ✅ 支持(IOC) |
| AOP | ❌ 不支持 | ✅ 支持(Wrapper) |
| 自适应扩展 | ❌ 不支持 | ✅ 支持(@Adaptive) |
| 自动激活 | ❌ 不支持 | ✅ 支持(@Activate) |
| 扩展缓存 | ❌ 无缓存 | ✅ 单例缓存 |
| 失败处理 | 一个失败全部失败 | 跳过失败的扩展 |
| 性能 | 低(重复加载) | 高(缓存 + 延迟加载) |
实际应用
JDK SPI 的应用
// JDBC 驱动加载
// META-INF/services/java.sql.Driver
// com.mysql.cj.jdbc.Driver
// org.postgresql.Driver
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
Dubbo SPI 的应用
// 负载均衡策略
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
<T> Invoker<T> select(List<Invoker<T>> invokers, Invocation invocation);
}
// META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance
// random=org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
// roundrobin=org.apache.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
// leastactive=org.apache.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
// 使用
@Reference(loadbalance = "leastactive")
private UserService userService;
答题总结
Dubbo SPI 和 JDK SPI 的核心区别:
1. 加载方式:
- JDK SPI:一次性加载所有实现,造成资源浪费
- Dubbo SPI:按需加载,延迟加载,性能更高
2. 配置格式:
- JDK SPI:
META-INF/services/接口全限定名,文件内容是实现类列表 - Dubbo SPI:
META-INF/dubbo/接口全限定名,文件内容是 Key=Value 格式
3. 获取方式:
- JDK SPI:遍历所有实现,手动筛选
- Dubbo SPI:按名称直接获取指定实现
4. 增强特性:
- 依赖注入(IOC):Dubbo SPI 支持,JDK SPI 不支持
- AOP(Wrapper):Dubbo SPI 支持,JDK SPI 不支持
- 自适应扩展(@Adaptive):Dubbo SPI 支持,动态选择实现
- 自动激活(@Activate):Dubbo SPI 支持,根据条件自动加载
- 单例缓存:Dubbo SPI 缓存扩展实例,JDK SPI 每次都创建新实例
5. 使用对比:
// JDK SPI
ServiceLoader<DataStorage> loader = ServiceLoader.load(DataStorage.class);
for (DataStorage storage : loader) {
// 遍历所有实现
}
// Dubbo SPI
ExtensionLoader<DataStorage> loader =
ExtensionLoader.getExtensionLoader(DataStorage.class);
DataStorage storage = loader.getExtension("mysql"); // 按名称获取
面试技巧:强调 Dubbo SPI 是对 JDK SPI 的全面增强,解决了 JDK SPI 的性能问题(一次性加载)、灵活性问题(无法按需加载)和功能缺失(不支持 IOC、AOP)。可以结合 Dubbo 的实际应用(负载均衡、协议、序列化等)说明 SPI 机制的重要性。