自定义SPI实现与动态扩展加载
如何基于Java SPI机制实现一个可动态扩展的插件系统?包括ServiceLoader的高级用法、自定义ClassLoader隔离、懒加载策略以及如何结合工厂模式实现SPI选择器。
回答
编译有声
1. 插件系统架构设计
// 插件接口
public interface Plugin {
void init();
void execute(Context ctx);
void destroy();
}
// 插件管理器
public class PluginManager {
private ServiceLoader<Plugin> loader;
public void loadPlugins() {
loader = ServiceLoader.load(Plugin.class);
}
public List<Plugin> getActivePlugins() {
List<Plugin> plugins = new ArrayList<>();
for (Plugin p : loader) { // 懒加载触发
plugins.add(p);
}
return plugins;
}
public void reload() {
loader.reload(); // 重新加载
}
}
2. 自定义ClassLoader隔离
- 不同插件使用独立的ClassLoader加载,避免类冲突
URLClassLoader pluginLoader = new URLClassLoader(
new URL[]{new File("plugins/plugin.jar").toURI().toURL()},
getParentLoader()
);
ServiceLoader<Plugin> loader = ServiceLoader.load(
Plugin.class, pluginLoader);
3. 排序与优先级
- 通过@Order注解或接口继承Priority
public interface OrderedPlugin extends Plugin {
default int getOrder() { return 0; }
}
// 使用Stream排序
plugins.stream()
.sorted(Comparator.comparingInt(OrderedPlugin::getOrder))
.collect(Collectors.toList());
4. 工厂模式结合
public class PluginFactory {
private static final Map<String, Plugin> cache = new HashMap<>();
static {
ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class);
for (Plugin p : loader) {
cache.put(p.getClass().getSimpleName(), p);
}
}
public static Plugin getPlugin(String name) {
return cache.get(name);
}
}
5. 性能优化
- 首次加载后缓存ServiceLoader实例
- 使用
loader.iterator()的hasNext()提前检查 - 结合Guava的
LoadingCache实现带失效的缓存