Freitag, 1. September 2023 - Lesezeit: 3 Minuten
Aktuell arbeite ich an einem Projekt mit JavaFX. Das Plugin javafx-maven-plugin bietet mit dem Goal "javafx:jlink" die Möglichkeit, direkt im Maven Buildprozess ein eigenes Image für die Applikation zu bauen.
Das funktioniert allerdings nur so lange, bis eine nicht modulare Dependency dazukommt.
Wie man trotzdem ein eigenes Image zusammenbaut, zeige ich hier
Zuerst erstellen wir uns ein Beispielprojekt mit dem JavaFX Archetype:
mvn archetype:generate \
-DarchetypeGroupId=org.openjfx \
-DarchetypeArtifactId=javafx-archetype-simple \
-DarchetypeVersion=0.0.3 \
-DgroupId=de.itwerkstatt \
-DartifactId=jlinkexample \
-Dversion=1.0.0 \
-Djavafx-version=20
In der pom.xml fügen wir jetzt eine nicht modulare Abhängigkeit hinzu:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
Außerdem müssen wir das in der module-info.java entsprechend als erforderlich kennzeichnen:
module de.itwerkstatt {
requires javafx.controls;
requires org.apache.commons.lang3;
exports de.itwerkstatt;
}
Versuchen wir jetzt, mit jlink ein Image zu erstellen, bekommen wir folgende Meldung:
[dominik@dominikpc jlinkexample]$ mvn clean javafx:jlink
(...)
Error: automatic module cannot be used with jlink: org.apache.commons.lang3
(...)
Wir benötigen zwei Plugins. Ab damit in die pom.xml:
<plugin>
<!-- Bau eine JAR mit Classpathangaben und Hauptklasse -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>de.itwerkstatt.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<!-- Kopiert alle Abhängigkeiten in ein lib Verzeichnis -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
</execution>
</executions>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</plugin>
Mit "mvn clean package" bekommen wir jetzt im Verzeichnis "target" eine Datei "jlinkexample-1.0.0.jar" und ein Verzeichnis "lib", in dem sich alle Abhängigkeiten als JAR-Datei befinden.
Im Verzeichnis "target" analysieren wir jetzt mit dem Tool "jdeps" unsere jar und dessen Abhängigkeiten:
dominik@dominikpc target]$ jdeps --module-path lib jlinkexample-1.0.0.jar
de.itwerkstatt
[file:///tmp/jlinkexample/target/jlinkexample-1.0.0.jar]
requires mandated java.base
requires javafx.controls (@20)
requires org.apache.commons.lang3
de.itwerkstatt -> java.base
de.itwerkstatt -> javafx.controls
de.itwerkstatt -> javafx.graphics
de.itwerkstatt -> java.lang java.base
de.itwerkstatt -> java.lang.invoke java.base
de.itwerkstatt -> javafx.application javafx.graphics
de.itwerkstatt -> javafx.scene javafx.graphics
de.itwerkstatt -> javafx.scene.control javafx.controls
de.itwerkstatt -> javafx.scene.layout javafx.graphics
de.itwerkstatt -> javafx.stage javafx.graphics
Mit diesen Informationen bauen wir uns unser eigenes Image:
[dominik@dominikpc target]$ jlink --module-path lib --add-modules java.base,javafx.controls,javafx.graphics --output custom-runtime
[dominik@dominikpc target]$ cp lib/commons-lang3-3.13.0.jar custom-runtime/lib/
[dominik@dominikpc target]$ cp jlinkexample-1.0.0.jar custom-runtime/
Mit dem Copy-Befehlen kopiere ich die nicht modulare Abhängigkeit in das lib-Verzeichnis in unserem Image und unsere Jar in das Hauptverzeichnis unseres Images.
Jetzt können wir unsere Applikation mit unserem eigenen Image starten:
[dominik@dominikpc custom-runtime]$ ./bin/java -jar jlinkexample-1.0.0.jar