IntelliJ can't recognize JavaFX 11 with OpenJDK 11

前端 未结 4 2127
星月不相逢
星月不相逢 2020-11-22 01:57

I\'m having trouble getting IntellJ to recognize JavaFX packages. With a new JavaFX project, with OpenJDK 11, when trying to build the project, IntelliJ can\'t recognize the

4条回答
  •  你的背包
    2020-11-22 02:18

    Quick summary, you can do either:

    1. Include the JavaFX modules via --module-path and --add-modules like in José's answer.

      OR

    2. Once you have JavaFX libraries added to your project (either manually or via maven/gradle import), add the module-info.java file similar to the one specified in this answer. (Note that this solution makes your app modular, so if you use other libraries, you will also need to add statements to require their modules inside the module-info.java file).


    This answer is a supplement to Jose's answer.

    The situation is this:

    1. You are using a recent Java version, e.g. 13.
    2. You have a JavaFX application as a Maven project.
    3. In your Maven project you have the JavaFX plugin configured and JavaFX dependencies setup as per Jose's answer.
    4. You go to the source code of your main class which extends Application, you right-click on it and try to run it.
    5. You get an IllegalAccessError involving an "unnamed module" when trying to launch the app.

    Excerpt for a stack trace generating an IllegalAccessError when trying to run a JavaFX app from Intellij Idea:

    Exception in Application start method
    java.lang.reflect.InvocationTargetException
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:567)
        at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
        at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:567)
        at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1051)
    Caused by: java.lang.RuntimeException: Exception in Application start method
        at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
        at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
        at java.base/java.lang.Thread.run(Thread.java:830)
    Caused by: java.lang.IllegalAccessError: class com.sun.javafx.fxml.FXMLLoaderHelper (in unnamed module @0x45069d0e) 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 @0x45069d0e
        at com.sun.javafx.fxml.FXMLLoaderHelper.(FXMLLoaderHelper.java:38)
        at javafx.fxml.FXMLLoader.(FXMLLoader.java:2056)
        at org.jewelsea.demo.javafx.springboot.Main.start(Main.java:13)
        at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
        at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
        at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
        at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
        at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    Exception running application org.jewelsea.demo.javafx.springboot.Main
    

    OK, now you are kind of stuck and have no clue what is going on.

    What has actually happened is this:

    1. Maven has successfully downloaded the JavaFX dependencies for your application, so you don't need to separately download the dependencies or install a JavaFX SDK or module distribution or anything like that.
    2. Idea has successfully imported the modules as dependencies to your project, so everything compiles OK and all of the code completion and everything works fine.

    So it seems everything should be OK. BUT, when you run your application, the code in the JavaFX modules is failing when trying to use reflection to instantiate instances of your application class (when you invoke launch) and your FXML controller classes (when you load FXML). Without some help, this use of reflection can fail in some cases, generating the obscure IllegalAccessError. This is due to a Java module system security feature which does not allow code from other modules to use reflection on your classes unless you explicitly allow it (and the JavaFX application launcher and FXMLLoader both require reflection in their current implementation in order for them to function correctly).

    This is where some of the other answers to this question, which reference module-info.java, come into the picture.

    So let's take a crash course in Java modules:

    • https://www.baeldung.com/java-9-modularity

    The key part is this:

    4.9. Opens

    If we need to allow reflection of private types, but we don't want all of our code exposed, we can use the opens directive to expose specific packages.

    But remember, this will open the package up to the entire world, so make sure that is what you want:

    module my.module { opens com.my.package; }
    

    So, perhaps you don't want to open your package to the entire world, then you can do:

    4.10. Opens … To

    Okay, so reflection is great sometimes, but we still want as much security as we can get from encapsulation. We can selectively open our packages to a pre-approved list of modules, in this case, using the opens…to directive:

    module my.module { opens com.my.package to moduleOne, moduleTwo, etc.; }

    So, you end up creating a src/main/java/module-info.java class which looks like this:

    module org.jewelsea.demo.javafx.springboot {
        requires javafx.fxml;
        requires javafx.controls;
        requires javafx.graphics;
        opens org.jewelsea.demo.javafx.springboot to javafx.graphics,javafx.fxml;
    }
    

    Where, org.jewelsea.demo.javafx.springboot is the name of the package which contains the JavaFX Application class and JavaFX Controller classes (replace this with the appropriate package name for your application). This tells the Java runtime that it is OK for classes in the javafx.graphics and javafx.fxml to invoke reflection on the classes in your org.jewelsea.demo.javafx.springboot package. Once this is done, and the application is compiled and re-run things will work fine and the IllegalAccessError generated by JavaFX's use of reflection will no longer occur.

    But what if you don't want to create a module-info.java file

    If instead of using the the Run button in the top toolbar of IDE to run your application class directly, you instead:

    1. Went to the Maven window in the side of the IDE.
    2. Chose the javafx maven plugin target javafx.run.
    3. Right-clicked on that and chose either Run Maven Build or Debug....

    Then the app will run without the module-info.java file. I guess this is because the maven plugin is smart enough to dynamically include some kind of settings which allows the app to be reflected on by the JavaFX classes even without a module-info.java file, though I don't know how this is accomplished.

    To get that setting transferred to the Run button in the top toolbar, right-click on the javafx.run Maven target and choose the option to Create Run/Debug Configuration for the target. Then you can just choose Run from the top toolbar to execute the Maven target.

提交回复
热议问题