一文搞懂 Spring 的 SPI 机制
什么是 Java SPI?
SPI 的全称是 Service Provider Interface,它是 Java 提供的一套用于扩展或替换组件的机制。SPI 的主要作用是通过接口编程、策略模式和配置文件,动态加载具体实现,从而实现:
- 解耦:接口和实现分离。
- 高扩展性:支持第三方实现,达到插件式扩展效果。
Java SPI 示例
1. 定义接口
public interface HelloSpi {
String getName();
void handle();
}
2. 定义实现类
public class OneHelloSpiImpl implements HelloSpi {
@Override
public String getName() {
return "One";
}
@Override
public void handle() {
System.out.println(getName() + "执行");
}
}
public class TwoHelloSpiImpl implements HelloSpi {
@Override
public String getName() {
return "Two";
}
@Override
public void handle() {
System.out.println(getName() + "执行");
}
}
3. 配置文件
在 META-INF/services
目录下创建文件,文件名为接口的全类名,内容为实现类的全类名:
com.shuaijie.impl.OneHelloSpiImpl
com.shuaijie.impl.TwoHelloSpiImpl
4. 测试代码
使用 ServiceLoader
加载并执行服务:
@Test
public void testSpi() {
ServiceLoader<HelloSpi> load = ServiceLoader.load(HelloSpi.class);
Iterator<HelloSpi> iterator = load.iterator();
while (iterator.hasNext()) {
HelloSpi next = iterator.next();
System.out.println(next.getName() + " 准备执行");
next.handle();
}
System.out.println("执行结束");
}
5. 执行结果
One 准备执行
One执行
Two 准备执行
Two执行
执行结束
通过结果可以看出,HelloSpi
接口的所有实现类都得到了调用。
ServiceLoader 原理解析
ServiceLoader
是一个简单的服务提供者加载工具,由 JDK6 引入。
关键代码如下:
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
load
方法通过 线程上下文类加载器 加载配置文件,这破坏了双亲委派模型(可以作为面试知识点)。
其 hasNext()
方法通过加载 META-INF/services
路径下的配置文件,获取实现类的全类名:
private static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {
// 省略其他代码
String fullName = PREFIX + service.getName();
configs = loader.getResources(fullName);
pending = parse(service, configs.nextElement());
nextName = pending.next();
return true;
}
常见使用场景
许多框架和库使用了 SPI 机制,例如:
- JDBC
- Dubbo
- Logback
- Spring
什么是 Spring SPI?
Spring 对 Java SPI 进行了封装和增强。通过在 META-INF/spring.factories
中配置接口的实现类名,可以在运行时动态加载接口实现。
示例:使用 Spring SPI 实现服务加载
1. 配置文件
在 META-INF/spring.factories
中添加:
com.shuaijie.impl.OneHelloSpiImpl
com.shuaijie.impl.TwoHelloSpiImpl
2. 测试代码
@Test
public void testSpringSpi() {
List<HelloSpi> helloSpiList = SpringFactoriesLoader.loadFactories(HelloSpi.class, this.getClass().getClassLoader());
Iterator<HelloSpi> iterator = helloSpiList.iterator();
while (iterator.hasNext()) {
HelloSpi next = iterator.next();
System.out.println(next.getName() + " 准备执行");
next.handle();
}
System.out.println("执行结束");
}
3. 执行结果
One 准备执行
One执行
Two 准备执行
Two执行
执行结束
Spring Boot Starter 实现示例
Spring Boot 的 Starter 依赖是通过 Spring SPI 实现的。这种机制允许我们创建自定义 Starter,为项目提供模块化功能。
1. 编写接口和实现
接口:
package com.shuaijie.service;
public interface SpringbootStarterService {
void handle();
}
实现:
package com.shuaijie.service.impl;
public class SpringbootStarterServiceImpl implements SpringbootStarterService {
@Override
public void handle() {
System.out.println("SpringbootStarterServiceImpl执行");
}
}
2. 配置 spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.shuaijie.service.impl.SpringbootStarterServiceImpl
3. 定义 Maven 配置
在 pom.xml
中指定 groupId
、artifactId
和 version
:
<groupId>com.shuaijie</groupId>
<artifactId>spring-boot-starter-shuaijie</artifactId>
<version>1.0.0</version>
4. 测试自定义 Starter
引入 Starter 后,在 Spring Boot 项目中直接使用:
@Autowired
private SpringbootStarterService springbootStarterService;
@Test
public void testSpringbootStarter() {
springbootStarterService.handle();
}
总结
通过 SPI 机制,Java 和 Spring 提供了强大的扩展能力。理解并灵活运用 SPI,可以极大地提高项目的模块化和可扩展性,同时也能为团队开发提供便利。
评论区