Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error launching a JavaFX application #67

Open
aalmiray opened this issue Dec 15, 2020 · 4 comments
Open

Error launching a JavaFX application #67

aalmiray opened this issue Dec 15, 2020 · 4 comments
Labels
documentation Improvements or additions to documentation

Comments

@aalmiray
Copy link
Contributor

aalmiray commented Dec 15, 2020

I noticed a problem when when trying to launch https://github.com/carldea/worldclock/ (from @carldea) using an external Layers config file. The problem stems form the different options there are to launch a JavaFX application. I've replicated the problem with the following code

App.java

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.net.URL;

public class App extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        URL location = getClass().getResource("app.fxml");
        FXMLLoader fxmlLoader = new FXMLLoader(location);
        HBox hbox = fxmlLoader.load();

        Scene scene = new Scene(hbox);
        stage.setScene(scene);
        stage.setTitle("App");
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

module-info.java

module app {
    exports app;
    requires javafx.base;
    requires javafx.graphics;
    requires javafx.controls;
    requires javafx.fxml;
}

layers.toml

[layers.javafx]
    modules = [
        "org.openjfx:javafx-base:jar:{{os.detected.jfxname}}:{{javafx_version}}",
        "org.openjfx:javafx-controls:jar:{{os.detected.jfxname}}:{{javafx_version}}",
        "org.openjfx:javafx-graphics:jar:{{os.detected.jfxname}}:{{javafx_version}}",
        "org.openjfx:javafx-fxml:jar:{{os.detected.jfxname}}:{{javafx_version}}"]
[layers.core]
    modules = [
        "org.kordamp.ikonli:app:{{project_version}}"]
    parents = ["javafx"]
[main]
  module = "app"
  class = "app.App"

versions.properties

project_version = 12.0.1-SNAPSHOT
javafx_version = 11

The error I get when running is

Exception in thread "main" java.lang.RuntimeException: Couldn't run module main class
	at org.moditect.layrry.internal.LayersImpl.run(LayersImpl.java:139)
	at org.moditect.layrry.Layrry.launch(Layrry.java:56)
	at org.moditect.layrry.Layrry.run(Layrry.java:50)
	at org.moditect.layrry.launcher.LayrryLauncher.launch(LayrryLauncher.java:53)
	at org.moditect.layrry.launcher.LayrryLauncher.main(LayrryLauncher.java:35)
Caused by: java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.moditect.layrry.internal.LayersImpl.run(LayersImpl.java:136)
	... 4 more
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: app.App
	at javafx.graphics/javafx.application.Application.launch(Application.java:304)
	at app@12.0.1-SNAPSHOT/app.App.main(App.java:42)
	... 9 more
Caused by: java.lang.ClassNotFoundException: app.App
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:468)
	at javafx.graphics/javafx.application.Application.launch(Application.java:292)

This is caused by invoking launch() in the main() method, as shown before

    public static void main(String[] args) {
        launch();
    }

However if the launch procedure is changed to

    public static void main(String[] args) {
        Application.launch(App.class, args);
    }

Then the application class is found and launched, however it fails due to another problem related to FXML (#68). The original problem appears to be caused by different strategies involving classloaders:

  • launch() relies on the context classloader to find the target class to load, which may be different from the classloader that loaded the class with the main method to begin with.
  • launch(Class, String...) relies on same classloader as the class with the main method.

I wonder if Layrry would have to setup the context classloader as well. I think it does not make any changes as far as I can tell.

@aalmiray aalmiray changed the title Launching JavaFX applications Error launching a JavaFX application Dec 15, 2020
@aalmiray
Copy link
Contributor Author

It's worth mentioning that marking the module as open did not make a difference.

@p-zalejko
Copy link

Hi there,

I've been playing with JavaFX and layryy and I think I know what's wrong. It looks the problem appears only when FXML files are used. From this perspective, it might not be an issue of layrry, it is more about internals and default behaviours of JavaFX classes.

When JavaFX loads FXML files it constructs UI controls like VBox, Labels, Buttons etc. Everything that is within an FXML file. The FXMLLoader class, under the hood, resolves a ClaasLoader that will be used for constructing these UI elements BUT, it turns out, that it is a different ClaasLoader that has loaded the application itself (the main class).  By default, FXMLLoader takes a classLoader from the current thread (Thread.currentThread().getContextClassLoader()) whereas the main class was loaded by module's class loader (jdk.internal.loader.Loader) (right?).  

Fortunately, it looks that a fix for this issue is simple - an FXMLLoader instance must be provided with a ClaasLoader that loaded the main (application) class. It can be done in the following way:

            URL location = getClass().getResource("hello.fxml");
            FXMLLoader fxmlLoader = new FXMLLoader(location);
            fxmlLoader.setClassLoader(getClass().getClassLoader());  // <!--- it's needed
            VBox box = fxmlLoader.load();

I prepared a simple app that shows how to use FXML files + layrry. Here is the link.

In addition to that, I realized that when you have JDK/JRE that contains JavaFX (e.g. https://www.azul.com/downloads/zulu-community/?version=java-15-mts&package=jdk-fx ) then working with JavaFX is much simpler. You do not have to include JavaFX modules in layryy configuration files. Still, the classLoader must be configured for the FXMLLoader because it gives access to the JavaFX classes provided by other modules as well as is able to load controllers that can be used within FXML files (which are classes from your application module).

I hope it helps.

@aalmiray
Copy link
Contributor Author

@p-zalejko thank you for posting this analysis, this particular FXMLLoader setup would have to be documented in Layrry's docs, in a section specific to JavaFX.

Regarding the use of a JDK/JRE + embedded JavaFX, the decisions to use this combination or external JavaFX modules is up to application developers.

@aalmiray aalmiray added the documentation Improvements or additions to documentation label Jan 29, 2021
@JJBRT
Copy link

JJBRT commented Sep 20, 2021

Please take a look at my new article that explain how to export all modules to all modules at runtime in Java 16 and later without using any JVM parameter

SylvainBertrand added a commit to ihmcrobotics/simulation-construction-set-2 that referenced this issue Feb 12, 2024
SylvainBertrand added a commit to ihmcrobotics/simulation-construction-set-2 that referenced this issue Feb 13, 2024
* Trying to change the class loader.

This was suggested here: moditect/layrry#67

* Adding some prints

* More prints

* Might have finally fixed the CI

* Tweak.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

3 participants