jLink: Eigenes Image bauen bei nicht modularen Abhängigkeiten

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

Beispielprojekt erstellen

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
(...)

Maßnahmen für eigenes Image

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.

Abhängigkeiten herausfinden

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