Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best approach to dynamically load modules (classes) in Java

I'm currently writing an application that requires to operate on different type of devices. My approach would be to make a "modular" application that can dynamically load different classes according to the device they need to operate on.

To make the application easily extensible, my goal is to assign a specific path to the additional modules (either .jar or .class files) leaving the core program as it is. This would be crucial when having different customers requiring different modules (without having to compile a different application for each of them).

These modules would implement a common interface, while the "core" application can use these methods defined on the interface and let the single implementations do the work. What's the best way to load them on demand? I was considering the use of URLClassLoader but i don't know if this approach is up-to-date according to new patterns and Java trends, as I would like to avoid a poorly designed application and deprecated techniques. What's an alternative best approach to make a modular and easily extensible application with JDK 9 (that can be extended just by adding module files to a folder) ?

like image 798
Mastarius Avatar asked Feb 12 '18 08:02

Mastarius


People also ask

What class is used to load classes dynamically in Java?

The Java ClassLoader is a part of the Java Runtime Environment that dynamically loads Java classes into the Java Virtual Machine. The Java run time system does not need to know about files and file systems because of classloaders.

What are dynamic loaded modules?

Dynamic Load Modules. Dynamic load modules provide the following functions: Load, refresh, and delete installation load modules, which are not part of the IBM® base JES2 code, after JES2 initialization processing. The dynamic table pairs and exit routine addresses are updated as needed.

What takes a string class name and loads the necessary class dynamically at run time?

Class loaders are responsible for loading Java classes dynamically to the JVM (Java Virtual Machine) during runtime. They're also part of the JRE (Java Runtime Environment). Therefore, the JVM doesn't need to know about the underlying files or file systems in order to run Java programs thanks to class loaders.

How do you dynamically import a module using a function?

To load dynamically a module call import(path) as a function with an argument indicating the specifier (aka path) to a module. const module = await import(path) returns a promise that resolves to an object containing the components of the imported module. } = await import(path);


3 Answers

Additionnaly to the ServicerLoader usage given by @SeverityOne, you can use the module-info.java to declare the different instanciation of the interface, using "uses"/"provides" keywords.

Then you use a module path instead of a classpath, it loads all the directory containing your modules, don't need to create a specific classLoader

The serviceLoader usage:

public static void main(String[] args) {
    ServiceLoader<IGreeting> sl = ServiceLoader.load(IGreeting.class);
    IGreeting greeting = sl.findFirst().orElseThrow(NullPointerException::new);
    System.out.println( greeting.regular("world"));
}

In the users project:

module pl.tfij.java9modules.app {
    exports pl.tfij.java9modules.app;
    uses pl.tfij.java9modules.app.IGreeting;
}

In the provider project:

module pl.tfij.java9modules.greetings {
    requires pl.tfij.java9modules.app;
    provides pl.tfij.java9modules.app.IGreeting
            with pl.tfij.java9modules.greetings.Greeting;
}

And finally the CLI usage

java --module-path mods --module pl.tfij.java9modules.app

Here is an example; Github example (Thanks for "tfij/" repository initial exemple)

Edit, I realized the repository already provides decoupling examples: https://github.com/tfij/Java-9-modules---reducing-coupling-of-modules

like image 109
pdem Avatar answered Oct 16 '22 06:10

pdem


It sounds like you might want to use the ServicerLoader interface, which has been available since Java 6. However, bear in mind that, if you want to use Spring dependency injection, this is probably not what you want.

like image 21
SeverityOne Avatar answered Oct 16 '22 06:10

SeverityOne


There are two scenarios.

  1. Implementation jar's are on classpath
    In this scenario you can simply use ServiceLoader API (refer to @pdem answer)
  2. Implementation jar's not on classpath Lets Assume BankController is your interface and CoreController is your implementation.
    If you want to load its implementation dynamically from dynamic path,c create a new module layer and load class.

Refer to the following piece of code:

        private final BankController loadController(final BankConfig config) {
            System.out.println("Loading bank with config : " + JSON.toJson(config));
            try {
                //Curent ModuleLayer is usually boot layer. but it can be different if you are using multiple layers
                ModuleLayer currentModuleLayer       = this.getClass().getModule().getLayer(); //ModuleLayer.boot();
                final Set<Path> modulePathSet        = Set.of(new File("path of implementation").toPath());
                //ModuleFinder to find modules 
                final ModuleFinder moduleFinder      = ModuleFinder.of(modulePathSet.toArray(new Path[0]));
                //I really dont know why does it requires empty finder.
                final ModuleFinder emptyFinder       = ModuleFinder.of(new Path[0]);
                //ModuleNames to be loaded
                final Set<String>  moduleNames       = moduleFinder.findAll().stream().map(moduleRef -> moduleRef.descriptor().name()).collect(Collectors.toSet());
                // Unless you want to use URLClassloader for tomcat like situation, use Current Class Loader 
                final ClassLoader loader             = this.getClass().getClassLoader();
                //Derive new configuration from current module layer configuration
                final Configuration  configuration   = currentModuleLayer.configuration().resolveAndBind(moduleFinder, emptyFinder, moduleNames);
                //New Module layer derived from current modulee layer 
                final ModuleLayer    moduleLayer     = currentModuleLayer.defineModulesWithOneLoader(configuration, loader);
                //find module and load class Load class 
                final Class<?>       controllerClass = moduleLayer.findModule("org.util.npci.coreconnect").get().getClassLoader().loadClass("org.util.npci.coreconnect.CoreController");
                //create new instance of Implementation, in this case org.util.npci.coreconnect.CoreController implements org.util.npci.api.BankController
                final BankController bankController  = (BankController) controllerClass.getConstructors()[0].newInstance(config);
                return bankController;
            } catch (Exception e) {BootLogger.info(e);}
            return null;
        }

Reference : https://docs.oracle.com/javase/9/docs/api/java/lang/module/Configuration.html

enter image description here

like image 4
krishna Telgave Avatar answered Oct 16 '22 06:10

krishna Telgave