问题
I have a very basic JavaFX application that works flawlessly if the Application class is not the Main 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
}
}
However, when I merge the two together (which is the recommended way in most tutorials, including OpenJFX's official documentation), the module system throws an IllegalAccessError
(at least on 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);
}
}
The exception is:
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
The weird thing is, that I did not actively use the module system. I did not add a module-info.java
to my project. So I assumed everything should get exported to any unnamed modules? But that is not even the point.
The main question is: Why does the same code behave differently if distributed across two classes? In both cases FXMLLoader
uses com.sun.javafx.fxml.FXMLLoaderHelper
, which in turn uses com.sun.javafx.util.Utils
. So either I should get the exception in both cases or in none. What is the difference?
回答1:
There are a few answers already posted that could apply partially to your questions, but it might be convenient to collect them here and present them in a full answer.
Application class
In the answer to Maven Shade JavaFX runtime components are missing I explained the reason why, when you use the Application
class as your main class, you are expected to use the module system.
In summary:
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.
Every JavaFX 11 jar has a module-info.class
file, so by definition, these are expected to be added to the module path.
But if you don't run via Application
class, that check is not done.
Main class
This other answer to Different behaviour between Maven & Eclipse to launch a JavaFX 11 app
explains why it works without the modular system when you use a Launcher
class (a Main class not extending Application) with the Maven exec:java
plugin.
In summary:
- Using a Launcher is required to overcome the mentioned
sun.launcher.LauncherHelper
issue. - Like the maven plugin runs in the classpath, loading all the dependencies into an isolated thread, so does IntelliJ in this case.
If you check the command line when you run 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
All the JavaFX jars from the JavaFX SDK are added to the classpath, and you are running the classic java -cp ... Main
.
javafx.fxml missing
These other answer to IntelliJ IDEA - Error: JavaFX runtime components are missing, and are required to run this application explains the error you get when you run on the module system but you don't add javafx.fxml
to the --add-modules
option.
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)
Your error says that you are using FXML but it can't be resolved in the module-path, so it is trying to access via reflection and that fails since you didn't opened that javafx.graphics
to your unnamed module.
So now you will ask: I didn't set the javafx.graphics
in the first place!
Well, you didn't, but IntelliJ did it for you!
Check the command line when you run 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
You can see that IntelliJ by default adds javafx.base
and javafx.graphics
. So only the javafx.fxml
is missing (and then you should of course add the module-path).
The recommended solution, as you pointed out, is in the docs:
Either on command line, using --module-path
to include the path to your JavaFX SDK lib folder, and --add-modules
to include javafx.fxml
in this case (where you don't have controls).
Or using the Maven plugin. At some point you will have to leave your IDE, so you will need to use a plugin to run the application.
Maven exec
A final note on the Maven exec
plugin, in case you use it:
What's more, the recommended Maven solution, until the plugin exec:java
is fixed for the modular system (and the good news is that this is being done as we speak), will be using exec:exec
instead, as explained in this issue, so you can specify both vm arguments.
来源:https://stackoverflow.com/questions/54756176/understanding-how-the-main-class-affects-jpms