了解主要 class 如何影响 JPMS
Understanding how the main class affects JPMS
我有一个非常基本的 JavaFX 应用程序,如果应用程序 class 不是 主 class:
,它可以完美运行
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
public class Main {
public static void main(String[] args) {
Application.launch(App.class, args);
}
}
public class App extends Application {
@Override
public void start(Stage primaryStage) {
FXMLLoader loader = new FXMLLoader(); // works
}
}
然而,当我将两者合并在一起时(这是大多数教程推荐的方式,包括 OpenJFX's official documentation),模块系统抛出一个 IllegalAccessError
(至少在 OpenJDK 11.0.2 上) :
public class MainApp extends Application {
@Override
public void start(Stage primaryStage) {
FXMLLoader loader = new FXMLLoader(); // throws IllegalAccessError
}
public static void main(String[] args) {
launch(MainApp.class, args);
}
}
例外情况是:
java.lang.IllegalAccessError: class com.sun.javafx.fxml.FXMLLoaderHelper
(in unnamed module @0x642c1a1b
) cannot access class com.sun.javafx.util.Utils
(in module javafx.graphics
) because module javafx.graphics
does not export com.sun.javafx.util
to unnamed module @0x642c1a1b
奇怪的是,我没有主动使用模块系统。我没有在我的项目中添加 module-info.java
。所以我假设一切都应该导出到任何未命名的模块?但这甚至不是重点。
主要问题是:为什么相同的代码分布在两个 class 中时表现不同?在这两种情况下,FXMLLoader
使用 com.sun.javafx.fxml.FXMLLoaderHelper
,后者又使用 com.sun.javafx.util.Utils
。所以我应该在这两种情况下或在 none 中得到异常。有什么区别?
已经发布了一些答案,它们可能部分适用于您的问题,但在这里收集它们并在完整答案中展示它们可能会很方便。
申请class
在 的回答中我解释了为什么当你使用 Application
class 作为你的主要 class 时,你应该使用该模块系统.
总结:
As you can read here:
This error comes from sun.launcher.LauncherHelper
in the java.base module (link).
If the main app extends Application
and has a main
method, the LauncherHelper
will check for the javafx.graphics
module to be present as a named module:
Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);
if (!om.isPresent()) {
abort(null, "java.launcher.cls.error5");
}
If that module is not present, the launch is aborted.
每个 JavaFX 11 jar 都有一个 module-info.class
文件,因此根据定义,这些应该被添加到模块路径中。
但如果您不通过 Application
运行 class,则该检查不会完成。
主要class
Maven 和 Eclipse 启动 JavaFX 11 应用程序的不同行为的另一个答案
解释了为什么当您使用带有 Maven exec:java
插件的 Launcher
class(主要 class 未扩展应用程序)时它在没有模块化系统的情况下工作。
总结:
- 需要使用 Launcher 来解决上述
sun.launcher.LauncherHelper
问题。
- 就像 class 路径中的 maven 插件 运行s 一样,将所有依赖项加载到一个独立的线程中,IntelliJ 在这种情况下也是如此。
如果你在 运行 Main.main()
:
时检查命令行
/path/to/jdk-11.0.2.jdk/Contents/Home/bin/java \
"-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=60556:/Applications/IntelliJ IDEA.app/Contents/bin" \
-Dfile.encoding=UTF-8 \
-classpath /path/to/so-question-54756176-master/target/classes:/path/to/.m2/repository/org/openjfx/javafx-base/11.0.2/javafx-base-11.0.2.jar:.../path/to/.m2/repository/org/openjfx/javafx-fxml/11.0.2/javafx-fxml-11.0.2-mac.jar \
Main
JavaFX SDK 中的所有 JavaFX jar 都已添加到 class 路径,并且您正在 运行安装 classic java -cp ... Main
。
javafx.fxml 缺失
这些对 的其他回答解释了当您在模块系统上 运行 但您没有将 javafx.fxml
添加到 --add-modules
选项时出现的错误。
Caused by: java.lang.IllegalAccessError: class com.sun.javafx.fxml.FXMLLoaderHelper (in unnamed module @0x5fce9dc5) cannot access class com.sun.javafx.util.Utils (in module javafx.graphics) because module javafx.graphics does not export com.sun.javafx.util to unnamed module @0x5fce9dc5
at com.sun.javafx.fxml.FXMLLoaderHelper.<clinit>(FXMLLoaderHelper.java:38)
at javafx.fxml.FXMLLoader.<clinit>(FXMLLoader.java:2056)
你的错误说你正在使用 FXML 但它不能在模块路径中解析,所以它试图通过反射访问但失败了,因为你没有打开那个 javafx.graphics
你的未命名模块。
所以现在你会问:我一开始就没有设置javafx.graphics
!
嗯,你没有,但 IntelliJ 为你做到了!
当你运行 MainApp.main()
:
时检查命令行
/path/to/jdk-11.0.2.jdk/Contents/Home/bin/java \
--add-modules javafx.base,javafx.graphics \
--add-reads javafx.base=ALL-UNNAMED \
--add-reads javafx.graphics=ALL-UNNAMED \
"-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=60430:/Applications/IntelliJ IDEA.app/Contents/bin" \
-Dfile.encoding=UTF-8 \
-classpath /path/to/so-question-54756176-master/target/classes:/path/to/.m2/repository/org/openjfx/javafx-base/11.0.2/javafx-base-11.0.2.jar:.../.m2/repository/org/openjfx/javafx-graphics/11.0.2/javafx-graphics-11.0.2-mac.jar \
MainApp
可以看到IntelliJ默认添加了javafx.base
和javafx.graphics
。所以只缺少 javafx.fxml
(然后你当然应该添加模块路径)。
正如您所指出的,推荐的解决方案在 docs:
在命令行中,使用 --module-path
来包含您的 JavaFX SDK lib 文件夹的路径,并在这种情况下使用 --add-modules
来包含 javafx.fxml
(您没有控制)。
或者使用 Maven 插件。在某些时候,您将不得不离开 IDE,因此您需要使用插件来 运行 应用程序。
Maven 执行程序
关于 Maven exec
插件的最后说明,以防您使用它:
此外,推荐的 Maven 解决方案,直到插件 exec:java
为模块化系统修复(好消息是这是 done as we speak), will be using exec:exec
instead, as explained in this issue,因此您可以指定两个 vm 参数.
我有一个非常基本的 JavaFX 应用程序,如果应用程序 class 不是 主 class:
,它可以完美运行import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
public class Main {
public static void main(String[] args) {
Application.launch(App.class, args);
}
}
public class App extends Application {
@Override
public void start(Stage primaryStage) {
FXMLLoader loader = new FXMLLoader(); // works
}
}
然而,当我将两者合并在一起时(这是大多数教程推荐的方式,包括 OpenJFX's official documentation),模块系统抛出一个 IllegalAccessError
(至少在 OpenJDK 11.0.2 上) :
public class MainApp extends Application {
@Override
public void start(Stage primaryStage) {
FXMLLoader loader = new FXMLLoader(); // throws IllegalAccessError
}
public static void main(String[] args) {
launch(MainApp.class, args);
}
}
例外情况是:
java.lang.IllegalAccessError: class
com.sun.javafx.fxml.FXMLLoaderHelper
(in unnamed module@0x642c1a1b
) cannot access classcom.sun.javafx.util.Utils
(in modulejavafx.graphics
) because modulejavafx.graphics
does not exportcom.sun.javafx.util
to unnamed module@0x642c1a1b
奇怪的是,我没有主动使用模块系统。我没有在我的项目中添加 module-info.java
。所以我假设一切都应该导出到任何未命名的模块?但这甚至不是重点。
主要问题是:为什么相同的代码分布在两个 class 中时表现不同?在这两种情况下,FXMLLoader
使用 com.sun.javafx.fxml.FXMLLoaderHelper
,后者又使用 com.sun.javafx.util.Utils
。所以我应该在这两种情况下或在 none 中得到异常。有什么区别?
已经发布了一些答案,它们可能部分适用于您的问题,但在这里收集它们并在完整答案中展示它们可能会很方便。
申请class
在 Application
class 作为你的主要 class 时,你应该使用该模块系统.
总结:
As you can read here:
This error comes from
sun.launcher.LauncherHelper
in the java.base module (link).If the main app extends
Application
and has amain
method, theLauncherHelper
will check for thejavafx.graphics
module to be present as a named module:
Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);
if (!om.isPresent()) {
abort(null, "java.launcher.cls.error5");
}
If that module is not present, the launch is aborted.
每个 JavaFX 11 jar 都有一个 module-info.class
文件,因此根据定义,这些应该被添加到模块路径中。
但如果您不通过 Application
运行 class,则该检查不会完成。
主要class
Maven 和 Eclipse 启动 JavaFX 11 应用程序的不同行为的另一个答案
解释了为什么当您使用带有 Maven exec:java
插件的 Launcher
class(主要 class 未扩展应用程序)时它在没有模块化系统的情况下工作。
总结:
- 需要使用 Launcher 来解决上述
sun.launcher.LauncherHelper
问题。 - 就像 class 路径中的 maven 插件 运行s 一样,将所有依赖项加载到一个独立的线程中,IntelliJ 在这种情况下也是如此。
如果你在 运行 Main.main()
:
/path/to/jdk-11.0.2.jdk/Contents/Home/bin/java \
"-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=60556:/Applications/IntelliJ IDEA.app/Contents/bin" \
-Dfile.encoding=UTF-8 \
-classpath /path/to/so-question-54756176-master/target/classes:/path/to/.m2/repository/org/openjfx/javafx-base/11.0.2/javafx-base-11.0.2.jar:.../path/to/.m2/repository/org/openjfx/javafx-fxml/11.0.2/javafx-fxml-11.0.2-mac.jar \
Main
JavaFX SDK 中的所有 JavaFX jar 都已添加到 class 路径,并且您正在 运行安装 classic java -cp ... Main
。
javafx.fxml 缺失
这些对 javafx.fxml
添加到 --add-modules
选项时出现的错误。
Caused by: java.lang.IllegalAccessError: class com.sun.javafx.fxml.FXMLLoaderHelper (in unnamed module @0x5fce9dc5) cannot access class com.sun.javafx.util.Utils (in module javafx.graphics) because module javafx.graphics does not export com.sun.javafx.util to unnamed module @0x5fce9dc5
at com.sun.javafx.fxml.FXMLLoaderHelper.<clinit>(FXMLLoaderHelper.java:38)
at javafx.fxml.FXMLLoader.<clinit>(FXMLLoader.java:2056)
你的错误说你正在使用 FXML 但它不能在模块路径中解析,所以它试图通过反射访问但失败了,因为你没有打开那个 javafx.graphics
你的未命名模块。
所以现在你会问:我一开始就没有设置javafx.graphics
!
嗯,你没有,但 IntelliJ 为你做到了!
当你运行 MainApp.main()
:
/path/to/jdk-11.0.2.jdk/Contents/Home/bin/java \
--add-modules javafx.base,javafx.graphics \
--add-reads javafx.base=ALL-UNNAMED \
--add-reads javafx.graphics=ALL-UNNAMED \
"-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=60430:/Applications/IntelliJ IDEA.app/Contents/bin" \
-Dfile.encoding=UTF-8 \
-classpath /path/to/so-question-54756176-master/target/classes:/path/to/.m2/repository/org/openjfx/javafx-base/11.0.2/javafx-base-11.0.2.jar:.../.m2/repository/org/openjfx/javafx-graphics/11.0.2/javafx-graphics-11.0.2-mac.jar \
MainApp
可以看到IntelliJ默认添加了javafx.base
和javafx.graphics
。所以只缺少 javafx.fxml
(然后你当然应该添加模块路径)。
正如您所指出的,推荐的解决方案在 docs:
在命令行中,使用 --module-path
来包含您的 JavaFX SDK lib 文件夹的路径,并在这种情况下使用 --add-modules
来包含 javafx.fxml
(您没有控制)。
或者使用 Maven 插件。在某些时候,您将不得不离开 IDE,因此您需要使用插件来 运行 应用程序。
Maven 执行程序
关于 Maven exec
插件的最后说明,以防您使用它:
此外,推荐的 Maven 解决方案,直到插件 exec:java
为模块化系统修复(好消息是这是 done as we speak), will be using exec:exec
instead, as explained in this issue,因此您可以指定两个 vm 参数.