Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 549
rnayabed Avatar asked Jan 25 '23 22:01

rnayabed


1 Answers

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.

like image 120
ZhekaKozlov Avatar answered Jan 29 '23 20:01

ZhekaKozlov