在Java中动态加载模块(类)的最佳方法

时间:2018-02-12 08:39:02

标签: java java-9 java-module

我目前正在编写一个需要在不同类型的设备上运行的应用程序。我的方法是创建一个“模块化”应用程序,它可以根据操作所需的设备动态加载不同的类。

为了使应用程序易于扩展,我的目标是为其他模块(.jar或.class文件)分配特定路径,使核心程序保持原样。当不同的客户需要不同的模块时(不必为每个模块编译不同的应用程序),这将是至关重要的。

这些模块将实现一个通用接口,而“核心”应用程序可以使用接口上定义的这些方法,让单个实现完成工作。 根据需要加载它们的最佳方法是什么?我正在考虑使用URLClassLoader,但我不知道这种方法是否是根据新模式和Java趋势更新的,就像我想的那样喜欢避免设计不良的应用程序和弃用的技术。 使用JDK 9构建模块化且易于扩展的应用程序的另一种最佳方法(可以通过将模块文件添加到文件夹来扩展)?

3 个答案:

答案 0 :(得分:3)

听起来你可能想要使用自Java 6以来一直可用的ServicerLoader接口。但是,请记住,如果你想使用Spring依赖注入,这可能不是你想要的

答案 1 :(得分:3)

除了@SeverityOne给出的ServicerLoader用法之外,您还可以使用module-info.java来声明接口的不同实例,使用"使用" /"提供"关键字。

然后使用模块路径而不是类路径,它会加载包含模块的所有目录,不需要创建特定的classLoader

serviceLoader用法:

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"));
}

在用户项目中:

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

在提供者项目中:

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

最后是CLI使用

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

这是一个例子; Github example(感谢&#34; tfij /&#34;存储库初始示例)

编辑,我意识到存储库已经提供了解耦示例: https://github.com/tfij/Java-9-modules---reducing-coupling-of-modules

答案 2 :(得分:0)

有两种情况。

  1. 实现jar位于类路径上
        在这种情况下,您可以简单地使用ServiceLoader API(请参阅@pdem答案)
  2. 实现jar不在类路径上     让我们假设BankController是您的接口,CoreController是您的实现。
    如果要从动态路径动态加载其实现,请c创建一个新的模块层并加载类。

请参考以下代码:

        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;
        }