Search code examples
javajava-module

Is there a way to update modular library without updating main application?


Before modular application organization, I had one main JavaFX application that load custom created multiple libraries for different options and possibilities in main app.

In old way of implementation, I just send new library to update, main application reads all libraries from folder and it works like a charm. But in a modular system, if my application wants to use new modular library that I send, it needs to update its module-info file, apropos I need to send updates for modular library and for main application. Just imagine, it would be like, chrome need to send browser update for every new plugin that is created. As I can see, with Java modularity system you can't create modular applications.

Is there a way import new module without updating main application or some other way around?


Solution

  • Java has a class for that: ServiceLoader.

    If we assume you have a “service provider” interface named PluginProvider, other modules can declare themselves to provide that service by putting this in their respective module-info.java descriptors:

    provides com.john.myapp.PluginProvider with com.library.MyProvider;
    

    Your application would then state that it uses that service in its own module-info:

    uses com.john.myapp.PluginProvider;
    

    And your application’s code would create a ModuleFinder that looks in the directory (or directories) where you expect those plugin modules to reside, then pass that ModuleFinder to a Configuration which can be used to create a ModuleLayer for the ServiceLoader:

    public class PluginLoader {
    
        private final ServiceLoader<PluginProvider> loader;
    
        public PluginLoader() {
    
            Path pluginDir = Paths.get(System.getProperty("user.home"),
                ".local", "share", "MyApplication", "plugins");
    
            ModuleLayer layer = PluginProvider.class.getModule().getLayer();
            layer = layer.defineModulesWithOneLoader(
                layer.configuration().resolveAndBind(
                    ModuleFinder.of(),
                    ModuleFinder.of(pluginDir),
                    Collections.emptySet()),
                PluginProvider.class.getClassLoader());
    
            loader = ServiceLoader.load(layer, PluginProvider.class);
        }
    
        public Stream<PluginProvider> getAll() {
            return loader.stream();
        }
    
        public void reload() {
            loader.reload();
        }
    }
    

    You might even want to watch the plugin directory for new or removed files:

    try (WatchService watch = pluginDir.getFileSystem().newWatchService()) {
    
        pluginDir.register(watch,
            StandardWatchEventKinds.ENTRY_CREATE,
            StandardWatchEventKinds.ENTRY_DELETE,
            StandardWatchEventKinds.ENTRY_MODIFY,
            StandardWatchEventKinds.OVERFLOW);
    
        WatchKey key;
        while ((key = watch.take()).isValid()) {
            loader.reload();
            key.reset();
        }
    }