Issue
Good day
I use, Win11 Pro 64 bit, ENG version, I use Oracle Java 11 or 17(problem on both), Eclipse IDE 2022_06. I would like load JavaFX modules programmatically in my throws project. In any folder on my harddrive i have JavaFX11 libraries. In the same folder i have simple JavaFX11 program(only show window with button, nothing more). This JavaFX program has name "ProgramJavaFX11.jar" and i can start it with command :
java --module-path "D:\Java\X_Package\JavaFX11\lib" --add-modules=javafx.controls,javafx.fxml -cp ProgramJavaFX11.jar javafx.ProgramJavaFX11
and its work of course.
I was use standard JavaAPI with added example, but its not work. If somebody know the answer can help me?
package spust;
import java.lang.module.*;
import java.nio.file.*;
import java.util.*;
public class LoadFXModule {
public static void main(String[] args) {
System.out.println("Start Program loadJAVAFXlibs");
try {
Path [] paths = {Paths.get("D:\\Java\\X_Package\\JavaFX11\\lib\\javafx.base.jar"),Paths.get("D:\\Java\\X_Package\\JavaFX11\\lib\\javafx.controls.jar"),
Paths.get("D:\\Java\\X_Package\\JavaFX11\\lib\\javafx.fxml.jar"),Paths.get("D:\\Java\\X_Package\\JavaFX11\\lib\\javafx.graphics.jar"),
Paths.get("D:\\Java\\X_Package\\JavaFX11\\lib\\javafx.media.jar"),Paths.get("D:\\Java\\X_Package\\JavaFX11\\lib\\javafx.swing.jar"),
Paths.get("D:\\Java\\X_Package\\JavaFX11\\lib\\javafx.web.jar"),Paths.get("D:\\Java\\X_Package\\JavaFX11\\lib\\javafx-swt.jar"),
Paths.get("D:\\JavaProjekty\\Java.Zaklady.JarVJarFXlib\\source\\ProgramJavaFX11.jar")};
ModuleFinder finder = ModuleFinder.of(paths);
ModuleLayer parent = ModuleLayer.boot();
Configuration cf = parent.configuration().resolve(finder, ModuleFinder.of(), java.util.Set.of("javafx.controls","javafx.fxml"));
ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); // Name loader :app
//ClassLoader systemLoader = ClassLoader.getPlatformClassLoader(); // Name loader :platform
System.out.println("Name loader :"+systemLoader.getName());
ModuleLayer layer = parent.defineModulesWithOneLoader(cf, systemLoader);
// HERE ITS CRASH
Class<?> javaFXClass = layer.findLoader("javafx.base").loadClass("javafx.ProgramJavaFX11");
/*
Method metodaMain = javaFXClass.getMethod("main",String[].class);
if(metodaMain == null) System.out.println("methodMain is null");
String [] pole1String = {"",""};
Object argsArray [] = { pole1String };
metodaMain.invoke(null,argsArray);
*/
} catch(Exception e) {
e.printStackTrace(); }
System.out.println("End Program");
}
Program crash with this message. It crash in IDE and runnable.jar too. It is look the classloader(or module loader) do not have access to "ProgramJavaFX11.jar" but i set to Path on beginning of code.
java.lang.ClassNotFoundException: javafx.ProgramJavaFX11
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
at java.base/jdk.internal.loader.Loader.loadClass(Loader.java:544)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
at spust.LoadFXModule.main(LoadFXModule.java:92)
Here is the simple project(without module-info.java, so it is automatic module) runnable jar ProgramJavaFX11.jar (really only window with button) what i want to run. I have change the name of package from "javafx" to "fxpackage". I create standard Project in EclipseIDE,set Oracle JDK11, set Java Build path -> libraries -> classpath and here i added JavaFX11 libraries :
- javafx.base.jar
- javafx.controls.jar
- javafx.fxml.jar
- javafx.graphics.jar
- javafx.media.jar
- javafx.swing.jar
- javafx.web.jar
- javafx.swt.jar
Here is the source code:
package fxpackage;
import javafx.application.*;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class ProgramJavaFX11 extends Application {
private Scene scene;
public static void main(String[] args) {
Application.launch(args);
}
public void start(Stage stage) throws Exception {
Button tlac = new Button("Press me");
tlac.setPrefSize(200,100);
tlac.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent ae) {
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle("Warning");
alert.setContentText("Good day");
alert.show();
}
});
FlowPane flowpane = new FlowPane(10,10);
flowpane.setAlignment(Pos.CENTER);
flowpane.setStyle("-fx-background-color: rgb(193, 122, 68)");
flowpane.getChildren().add(tlac);
scene = new Scene(flowpane, 500, 250);
stage.setScene(scene);
stage.setTitle("Window with JavaFX11");
stage.show();
}
}
I exported this project in Eclipse as runnable jar and can run it with on command-line with command :
java --module-path "D:\Java\X_Package\JavaFX11\lib" --add-modules=javafx.controls,javafx.fxml -cp ProgramJavaFX11.jar fxpackage.ProgramJavaFX11
Solution
I'm not positive why you're getting a ClassNotFoundException
. However, here is an example application that loads its own JavaFX ModuleLayer
and creates a URLClassLoader
for the "real" application JAR file.
import java.lang.module.ModuleFinder;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.Set;
public class Main {
public static void main(String[] args) throws Exception {
if (ModuleLayer.boot().findModule("javafx.graphics").isPresent()) {
throw new IllegalStateException("Using Java run-time that includes JavaFX");
}
var fxLayer = createJavaFXModuleLayer(Path.of(args[0]));
var appLoader = createAppClassLoader(Path.of(args[1]), fxLayer.findLoader("javafx.graphics"));
launchApp(fxLayer, appLoader, args[2]);
}
private static ModuleLayer createJavaFXModuleLayer(Path javafxLib) {
System.out.printf("JavaFX SDK: %s%n", javafxLib.toAbsolutePath().normalize());
var finder = ModuleFinder.of(javafxLib);
var parentLayer = ModuleLayer.boot();
var parentConfig = parentLayer.configuration();
var fxConfig = parentConfig.resolveAndBind(finder, ModuleFinder.of(), Set.of("javafx.controls"));
return parentLayer.defineModulesWithOneLoader(fxConfig, parentLayer.findLoader("java.base"));
}
private static URLClassLoader createAppClassLoader(Path jarFile, ClassLoader parent) throws MalformedURLException {
System.out.printf("Application JAR file: %s%n", jarFile.toAbsolutePath().normalize());
var url = jarFile.toUri().toURL();
return new URLClassLoader("user-app", new URL[] { url }, parent);
}
private static void launchApp(ModuleLayer fxLayer, ClassLoader appLoader, String mainClassName)
throws ReflectiveOperationException {
System.out.printf("Main class: %s%n", mainClassName);
var graphicsMod = fxLayer.findModule("javafx.graphics").orElseThrow();
var appClass = Class.forName(graphicsMod, "javafx.application.Application");
var mainClass = Class.forName(mainClassName, false, appLoader);
if (!appClass.isAssignableFrom(mainClass)) {
throw new IllegalArgumentException("Main class is not a subclass of javafx.application.Application");
}
var launch = appClass.getMethod("launch", Class.class, String[].class);
launch.invoke(null, mainClass, new String[] {});
}
}
Whichever way you decide to run the above, it accepts three arguments:
- A path, relative or absolute, to the JavaFX SDK (the
lib
directory). - A path, relative or absolute, to the JAR file containing the "real" application.
- The name of the main class packaged in the JAR file specified by argument 2.
Some notes:
The above will crash if it detects JavaFX is in the boot layer, so as to avoid having JavaFX modules in two different layers. You could always modify it to use the boot layer in that case, instead of creating your own layer.
The above only specifies the
javafx.controls
module as a root module. If you run into a problem due to a missing module, update the line where I create thefxConfig
configuration and add more root modules (theSet.of("javafx.controls")
argument).The above expects the main class to be a subclass of
javafx.application.Application
. It will fail otherwise.Take note of the parent relationships between
ClassLoader
s, particularly the one created in thecreateAppClassLoader
method. That class loader needs to be able to find the JavaFX classes, which is why a loader from the JavaFX layer is used as the parent.If your JAR file becomes modular, you can forgo the
URLClassLoader
and just add your JAR file to the JavaFXModuleLayer
. You can technically do the same for automatic modules, but in that case the above approach seemed easier.The third command-line argument could be omitted if you're willing to modify the code and inspect the JAR file's manifest to get the main class, assuming the main class attribute is present.
Answered By - Slaw
Answer Checked By - Marie Seifert (JavaFixing Admin)