You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java应用插件加载器开发故障:ServiceLoader返回空列表

Java插件系统加载故障排查

我正在开发Java应用的插件系统,需求是:指定一个存放插件jar包的目录,应用启动时自动加载其中的插件。目前已完成插件结构依赖并准备了测试插件,但加载系统出现异常。参考过一篇博客的类加载器实现,但无法正常工作。

期望的插件系统目录结构

C:.
├───commands
│   ├───plugin
│   │    └plugin.jar
│   └───plugin2
│        └plugin2.jar
└───src
    └───Java app
         ├───controller
         │   └───loaders
         ├───model
         └───view

loaders包中的实现类

PluginLoader.java

public class PluginLoader {
    private final Map<String, CommandFactory> commandFactoryMap = new HashMap<>();
    private final File pluginsDir;
    private final AtomicBoolean loading = new AtomicBoolean();

    public PluginLoader(final File pluginsDir) {
        this.pluginsDir = pluginsDir;
    }

    public void loadCommands() {
        if (!pluginsDir.exists() || !pluginsDir.isDirectory()) {
            System.err.println("Skipping Plugin Loading. Plugin dir not found: " + pluginsDir);
            return;
        }

        if (loading.compareAndSet(false, true)) {
            final File[] files = requireNonNull(pluginsDir.listFiles());
            for (File pluginDir : files) {
                if (pluginDir.isDirectory()) {
                    loadCommand(pluginDir);
                }
            }
        }
    }

    private void loadCommand(final File pluginDir) {
        System.out.println("Loading plugin: " + pluginDir);
        final URLClassLoader pluginClassLoader = createPluginClassLoader(pluginDir);
        final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(pluginClassLoader);
// 此处循环无数据返回
            for (OmenChatPlugin omenChatPlugin : ServiceLoader.load(OmenChatPlugin.class, pluginClassLoader)) {
                installPlugin(omenChatPlugin);
            }
        } finally {
            Thread.currentThread().setContextClassLoader(currentClassLoader);
        }
    }


    private void installPlugin(final OmenChatPlugin plugin) {
        System.out.println("Installing plugin: " + plugin.getClass().getName());
        for (CommandFactory c : plugin.getCommandFactories()) {
            commandFactoryMap.put(c.name(), c);
        }
    }

    private URLClassLoader createPluginClassLoader(File dir) {
        final URL[] urls = Arrays.stream(Optional.of(dir.listFiles()).orElse(new File[]{}))
                .sorted()
                .map(File::toURI)
                .map(this::toUrl)
                .toArray(URL[]::new);

        System.out.println("cpcl: " + Arrays.toString(urls));

        return new PluginClassLoader(urls, getClass().getClassLoader());
    }

    private URL toUrl(final URI uri) {
        try {
            return uri.toURL();
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }

    public CommandFactory getCommandFactory(String name) {
        return commandFactoryMap.get(name);
    }
}

PluginClassLoader.java

public class PluginClassLoader extends URLClassLoader {
    public static final List<String> SHARED_PACKAGES = Arrays.asList(
            "com.github.OMEN44",
            "com.github.omen"
    );

    private final ClassLoader parentClassLoader;

    public PluginClassLoader(URL[] urls, ClassLoader parentClassLoader) {
        super(urls, null);
        this.parentClassLoader = parentClassLoader;
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        // has the class loaded already?
        Class<?> loadedClass = findLoadedClass(name);
        if (loadedClass == null) {
            final boolean isSharedClass = SHARED_PACKAGES.stream().anyMatch(name::startsWith);
            if (isSharedClass) {
                loadedClass = parentClassLoader.loadClass(name);
            } else {
                loadedClass = super.loadClass(name, resolve);
            }
        }

        if (resolve) {      // marked to resolve
            resolveClass(loadedClass);
        }
        return loadedClass;
    }
}

问题现象

启动应用时,createPluginClassLoader方法打印的URL正常,但标注的for循环中,ServiceLoader.load(OmenChatPlugin.class, pluginClassLoader)返回空列表,无法加载到插件实例。寻求解决方案或更优的插件系统实现思路。

内容的提问来源于stack exchange,提问作者Huon Swales

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 19:35:22