在 Java 中动态加载模块 (类) 的最佳方法
Best approach to dynamically load modules (classes) in Java
我目前正在编写一个需要在不同类型的设备上运行的应用程序。我的方法是制作一个 "modular" 应用程序,它可以根据需要运行的设备动态加载不同的 classes。
为了使应用程序易于扩展,我的目标是为附加模块(.jar 或 .class 文件)分配一个特定路径,而核心程序保持原样。当不同的客户需要不同的模块(无需为每个客户编译不同的应用程序)时,这将是至关重要的。
这些模块将实现一个公共接口,而 "core" 应用程序可以使用接口上定义的这些方法,并让单个实现完成工作。 按需加载它们的最佳方式是什么?我正在考虑使用 URLClassLoader,但我不知道这种方法是否符合新模式和 Java 趋势,因为我想避免设计不当的应用程序和弃用的技术。 使用 JDK9 制作模块化且易于扩展的应用程序的最佳替代方法是什么(只需将模块文件添加到文件夹即可扩展)?
听起来您可能想要使用 ServicerLoader 界面,该界面自 Java 6 起可用。但是,请记住,如果您想使用 Spring依赖注入,这可能不是你想要的。
除@SeverityOne 给出的 ServicerLoader 用法外,您还可以使用模块-info.java 声明接口的不同实例,使用 "uses"/"provides" 关键字。
然后你使用模块路径而不是类路径,它加载所有包含你的模块的目录,不需要创建特定的类加载器
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(感谢 "tfij/" 存储库初始示例)
编辑,我发现存储库已经提供了解耦示例:
https://github.com/tfij/Java-9-modules---reducing-coupling-of-modules
有两种情况。
- 实施 jar 在 class路径
在这种情况下,您可以简单地使用 ServiceLoader API(参考@pdem 回答)
- 实现 jar 不在 class路径上
假设 BankController 是您的接口,而 CoreController 是您的实现。
如果你想从动态路径动态加载它的实现,c创建一个新的模块层并加载class。
参考下面一段代码:
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;
}
参考:https://docs.oracle.com/javase/9/docs/api/java/lang/module/Configuration.html
我目前正在编写一个需要在不同类型的设备上运行的应用程序。我的方法是制作一个 "modular" 应用程序,它可以根据需要运行的设备动态加载不同的 classes。
为了使应用程序易于扩展,我的目标是为附加模块(.jar 或 .class 文件)分配一个特定路径,而核心程序保持原样。当不同的客户需要不同的模块(无需为每个客户编译不同的应用程序)时,这将是至关重要的。
这些模块将实现一个公共接口,而 "core" 应用程序可以使用接口上定义的这些方法,并让单个实现完成工作。 按需加载它们的最佳方式是什么?我正在考虑使用 URLClassLoader,但我不知道这种方法是否符合新模式和 Java 趋势,因为我想避免设计不当的应用程序和弃用的技术。 使用 JDK9 制作模块化且易于扩展的应用程序的最佳替代方法是什么(只需将模块文件添加到文件夹即可扩展)?
听起来您可能想要使用 ServicerLoader 界面,该界面自 Java 6 起可用。但是,请记住,如果您想使用 Spring依赖注入,这可能不是你想要的。
除@SeverityOne 给出的 ServicerLoader 用法外,您还可以使用模块-info.java 声明接口的不同实例,使用 "uses"/"provides" 关键字。
然后你使用模块路径而不是类路径,它加载所有包含你的模块的目录,不需要创建特定的类加载器
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(感谢 "tfij/" 存储库初始示例)
编辑,我发现存储库已经提供了解耦示例: https://github.com/tfij/Java-9-modules---reducing-coupling-of-modules
有两种情况。
- 实施 jar 在 class路径
在这种情况下,您可以简单地使用 ServiceLoader API(参考@pdem 回答) - 实现 jar 不在 class路径上
假设 BankController 是您的接口,而 CoreController 是您的实现。
如果你想从动态路径动态加载它的实现,c创建一个新的模块层并加载class。
参考下面一段代码:
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;
}
参考:https://docs.oracle.com/javase/9/docs/api/java/lang/module/Configuration.html