CodeWalk

自定义SPI实现与动态扩展加载

作者:编译有声 · 2026-05-30 12:55

如何基于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实现带失效的缓存