Java 和 SpringBoot 的 SPI 机制
1. JDK 原生的 SPI
JDK的SPI通过在/resources/META-INF/services/
目录下放置全限定路径文件
来指定哪些类实现了给定的服务接口。
示例:
// 1. 定义SPI接口(与方案1相同)
public interface PaymentService {
void pay(String orderId);
}
// 2. 各支付实现类(与方案1相同)
public class AlipayService implements PaymentService { /*...*/ }
public class WechatPayService implements PaymentService { /*...*/ }
// 3. 注册服务提供者(新增支付方式只需添加文件)
/*
META-INF/services/com.example.PaymentService
文件内容:
com.example.AlipayService
com.example.WechatPayService
*/
// 4. 动态加载服务(核心优势)
public class PaymentGateway {
public void processPayment(String orderId) {
ServiceLoader<PaymentService> services = ServiceLoader.load(PaymentService.class);
// 自动发现所有支付方式
for (PaymentService service : services) {
service.pay(orderId);
}
}
}
// 使用示例(完全解耦)
public class OrderController {
public void createOrder() {
PaymentGateway gateway = new PaymentGateway();
gateway.processPayment("ORDER_456");
}
}
使用
import com.unravely.spi.annotation.Singleton;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/**
* 提供服务加载功能的支持类,特别是处理单例服务
*/
public class ServiceSupport {
/**
* 存储单例服务的映射,确保每个服务只有一个实例
*/
private final static Map<String, Object> SINGLETON_SERVICES = new HashMap<>();
/**
* 加载单例服务实例
* 本方法通过ServiceLoader加载服务,并确保加载的是单例服务实例
* 如果无法找到对应的服务实例,将抛出ServiceLoadException
*
* @param service 服务类的Class对象,用于指定需要加载的服务类型
* @param <S> 服务类的类型参数,表示服务类型的泛型
* @return 单例服务实例,返回指定类型的单例服务对象
* @throws RuntimeException 如果找不到服务实例,将抛出此自定义异常
*/
public synchronized static <S> S load(Class<S> service) {
// 使用ServiceLoader加载服务提供者,并通过stream进行处理
// 调用ServiceSupport中的singletonFilter方法对服务实例进行过滤,确保是单例实例
// findFirst方法找到第一个符合条件的服务实例
// 如果没有找到服务实例,则抛出ServiceLoadException异常
return StreamSupport.stream(ServiceLoader.load(service).spliterator(), false)
.map(ServiceSupport::singletonFilter)
.findFirst().orElseThrow(RuntimeException::new);
}
/**
* 加载所有服务实例
*
* @param service 服务类的Class对象
* @param <S> 服务类的类型参数
* @return 所有服务实例的集合
*/
public synchronized static <S> Collection<S> loadAll(Class<S> service) {
// 使用ServiceLoader加载服务实例,并通过stream进行处理
// 使用ServiceSupport的singletonFilter方法筛选服务实例
// 最终将筛选后的服务实例收集到一个集合中
return StreamSupport.stream(ServiceLoader.load(service).spliterator(), false)
.map(ServiceSupport::singletonFilter).collect(Collectors.toList());
}
/**
* 对服务实例进行单例过滤
*
* @param service 服务实例
* @param <S> 服务类的类型参数
* @return 单例过滤后的服务实例,如果该服务是单例的并且已有实例存在,则返回已存在的实例
*/
@SuppressWarnings("unchecked")
private static <S> S singletonFilter(S service) {
// 检查服务类是否被标记为单例
if(service.getClass().isAnnotationPresent(Singleton.class)) {
// 获取服务类的全限定名
String className = service.getClass().getCanonicalName();
// 使用Double-checked locking原则,确保只有一个实例
Object singletonInstance = SINGLETON_SERVICES.putIfAbsent(className, service);
// 如果之前没有实例存在,则返回当前实例;否则返回已存在的实例
return singletonInstance == null ? service : (S) singletonInstance;
} else {
// 如果不是单例,则直接返回服务实例
return service;
}
}
}
2. SpringBoot 的 SPI 机制
SpringBoot 3.x之后Spring.factories被弃用
2.7.x 之前版本
文件路径:/resources/META-INF/spring.factories
3.x 以后版本
文件路径:/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports