4

I have been working on a software with a plugin based system, where users can write their own plugins. I am very new to JMPS, but I would like to make this using JMPS and not OSGi. Made a separate API module and even created a Test Plugin.

The plugins are stored with the filename "someplugin.jar" in a directory.

How do I load all these jars (none of them are automodules but well-defined modules with module-info.class) during runtime? The reason I want to load them dynamically during runtime is that the user will be having an option to change the directory to search for plugins, and change it without having to restart the application.

rnayabed
  • 113
  • 10

1 Answers1

12

To load modules dynamically, you need to define a new ModuleLayer. The new module layer will inherit the boot layer:

enter image description here

This means that in your boot layer (where your main module is), you cannot directly refer to classes in the plugins layer. However, you can use your plugins layer through services.

Here is the code that you can use as a starting point:

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`
...

Module layers are considered an advanced topic but I don't find it really difficult. The only key point you need to understand is that module layers are inherited. This means that from a child layer, you can only refer to classes of the parent layer but not vice versa. To do the opposite, you have to use the inversion of control which is implemented in the Java module system by ServiceLoader.

ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155
  • 1
    Thanks a lot for your answer. This is exactly what I needed. Have a good day! – rnayabed Jul 07 '20 at 07:04
  • 1
    Very neat example! However, is it possible to create a new layer and add java root modules to it (when they are not loaded in the boot layer)? I've got your example running, however not if the module loaded is dependent on java.sql module when the boot layer has only loaded java.base. It simply states that the module java.sql cannot be found and it does not seem to be able to resolve it. – chrillof Sep 10 '21 at 19:33