如何从 Java 9+ 中的目录动态加载模块

How do I dynamically load modules from a directory in Java 9+

我一直在开发一个基于插件系统的软件,用户可以在其中编写自己的插件。我对 JMPS 很陌生,但我想使用 JMPS 而不是 OSGi 来实现。制作了一个单独的 API 模块,甚至创建了一个测试插件。

插件以文件名“someplugin.jar”存储在目录中。

如何在运行时加载所有这些 jar(其中 none 是自动模块,但使用模块 info.class 定义明确的模块)?我想在运行时动态加载它们的原因是用户可以选择更改目录以搜索插件,并且无需重新启动应用程序即可更改它。

要动态加载模块,您需要定义一个新的ModuleLayer。新的模块层将继承引导层:

这意味着在你的boot层(你的主模块所在的地方),你不能直接引用plugins层中的类。但是,您可以通过 services.

使用您的插件层

这是您可以用作起点的代码:

Path pluginsDir = Paths.get("plugins"); // Directory with plugins JARs

// Search for plugins in the plugins directory
ModuleFinder pluginsFinder = ModuleFinder.of(pluginsDir);

// Find all names of all found plugin modules
List<String> plugins = pluginsFinder
        .findAll()
        .stream()
        .map(ModuleReference::descriptor)
        .map(ModuleDescriptor::name)
        .collect(Collectors.toList());

// Create configuration that will resolve plugin modules
// (verify that the graph of modules is correct)
Configuration pluginsConfiguration = ModuleLayer
        .boot()
        .configuration()
        .resolve(pluginsFinder, ModuleFinder.of(), plugins);

// Create a module layer for plugins
ModuleLayer layer = ModuleLayer
        .boot()
        .defineModulesWithOneLoader(pluginsConfiguration, ClassLoader.getSystemClassLoader());

// Now you can use the new module layer to find service implementations in it
List<Your Service Interface> services = ServiceLoader
        .load(layer, <Your Service Interface>.class)
        .stream()
        .map(Provider::get)
        .collect(Collectors.toList());

// Do something with `services`
...

模块层被认为是一个高级主题,但我并不觉得它真的很难。您需要了解的唯一关键点是模块层是继承的。这意味着从子层只能引用父层的类,反之则不行。相反,您必须使用 inversion of control,它由 ServiceLoader 在 Java 模块系统中实现。