SPI 加载工具 - ServiceLoaderUtil
# SPI 加载工具 - ServiceLoaderUtil
:: tip 简介
SPI(Service Provider Interface)是一种服务发现机制,允许在运行时动态查找和加载服务实现。Java 提供的
ServiceLoader
是 SPI 的核心实现,它通过在ClassPath
路径下的META-INF/services
文件夹查找指定的文件,自动加载文件中定义的类。ServiceLoaderUtil
是对ServiceLoader
的进一步封装,提供了一些便捷的加载方法,例如获取第一个可用服务、加载所有实现等,适用于接口的多实现动态加载场景。
:::
# 1. 使用场景
SPI 机制广泛应用于插件化系统、驱动加载、可插拔框架等场景。它允许在不修改代码的情况下,动态添加或替换实现类,例如数据库驱动、日志框架等。
# 2. 定义一个 SPI 接口及其实现
首先,我们定义一个接口:
package cn.hutool.test.spi;
public interface SPIService {
void execute();
}
2
3
4
5
然后,我们提供两个不同的实现类:
package cn.hutool.test.spi;
public class SpiImpl1 implements SPIService {
@Override
public void execute() {
System.out.println("SpiImpl1.execute()");
}
}
package cn.hutool.test.spi;
public class SpiImpl2 implements SPIService {
@Override
public void execute() {
System.out.println("SpiImpl2.execute()");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
接着,在 classpath
的 META-INF/services
文件夹下创建一个文件,文件名为 cn.hutool.test.spi.SPIService
,内容如下:
cn.hutool.test.spi.SpiImpl1
cn.hutool.test.spi.SpiImpl2
2
# 3. 加载第一个可用的 SPI 实现
loadFirstAvailable
方法用于加载第一个可用的服务实现。如果有多个实现类存在,方法会自动选择第一个可以正常加载的服务,适用于接口的多实现场景。
import cn.hutool.core.util.ServiceLoaderUtil;
import cn.hutool.test.spi.SPIService;
public class ServiceLoaderUtilExample {
public static void main(String[] args) {
// 加载第一个可用的 SPI 实现
SPIService service = ServiceLoaderUtil.loadFirstAvailable(SPIService.class);
service.execute(); // 输出:SpiImpl1.execute()
}
}
2
3
4
5
6
7
8
9
10
loadFirstAvailable(Class<T> service)
:加载第一个可用的服务实现。service
:要加载的接口类型。- 返回值:第一个可用的服务实现对象。
作用: 在接口有多个实现时,快速找到第一个可以使用的实现类。
实际开发场景: 在插件化开发中,可以使用此方法加载多个实现中的一个,例如加载不同的日志框架实现或不同的数据库驱动。
# 4. 加载所有 SPI 实现
load
方法用于加载指定接口的所有实现,并返回 ServiceLoader
对象。可以遍历 ServiceLoader
获取所有实现类实例。
import cn.hutool.core.util.ServiceLoaderUtil;
import cn.hutool.test.spi.SPIService;
import java.util.ServiceLoader;
public class ServiceLoaderUtilExample {
public static void main(String[] args) {
// 加载所有 SPI 实现
ServiceLoader<SPIService> services = ServiceLoaderUtil.load(SPIService.class);
for (SPIService service : services) {
service.execute();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
load(Class<T> service)
:加载指定接口的所有实现。service
:要加载的接口类型。- 返回值:
ServiceLoader
对象,包含所有实现类实例。
作用: 在需要遍历所有实现时使用,例如在插件系统中加载所有可用插件。
实际开发场景: 在需要遍历所有实现类并进行统一处理的场景下,可以使用该方法,例如加载所有日志处理器或事件监听器。
# 5. 加载 SPI 实现或返回默认实现
loadOrDefault
方法用于加载指定接口的第一个实现,如果没有找到任何实现类,则返回默认实现。
import cn.hutool.core.util.ServiceLoaderUtil;
import cn.hutool.test.spi.SPIService;
public class ServiceLoaderUtilExample {
public static void main(String[] args) {
// 提供默认实现
SPIService defaultService = () -> System.out.println("默认实现");
// 加载 SPI 实现,如果未找到则使用默认实现
SPIService service = ServiceLoaderUtil.loadOrDefault(SPIService.class, defaultService);
service.execute(); // 如果有实现类输出相应实现结果,否则输出:默认实现
}
}
2
3
4
5
6
7
8
9
10
11
12
13
loadOrDefault(Class<T> service, T defaultInstance)
:加载指定接口的第一个实现,如果找不到则返回默认实现。service
:要加载的接口类型。defaultInstance
:默认实现,当没有实现类时使用。- 返回值:找到的第一个实现类或默认实现。
作用: 确保在没有实现类时仍然有一个可用的默认实例。
实际开发场景: 在某些插件或配置加载场景下,可以使用此方法确保系统在无实现时仍然能正常工作。
总结
ServiceLoaderUtil
是一个对 SPI 机制的便捷封装,提供了灵活的加载方式,使得在多实现的接口场景下,可以快速加载和使用指定的实现类。无论是加载单个实现、遍历所有实现,还是提供默认实现,都能够帮助开发者更加高效地进行插件化开发或模块化设计。