Mit Java 9 wurde das Modularisierungssystem "Java Platform Module System" (= "JPMS") (entsprechend JSR 376) eingeführt, besser bekannt unter dem Namen Jigsaw (wörtlich übersetzt: Stichsäge oder Puzzle). Die Modularisierung bezieht sich auf zwei verschiedene Bereiche:
Die Modularisierung von Java SE selbst (mit "Platform Modules").
Die Modularisierung von Bibliotheken und Anwendungen (mit "Application Modules").
Wichtige Ziele von Jigsaw sind:
Skalierbarkeit von Java SE (z.B. für IoT).
Erhöhung der Sicherheit und Wartbarkeit von Java.
Verbesserte Performance.
Förderung gut konstruierter und wartbarer Bibliotheken und Anwendungen, sowohl für Java SE als auch Java EE.
Explizite Abhängigkeitskontrolle, Definition lose gekoppelter Services und zuverlässige Konfiguration ("Reliable Configuration").
Einfache Handhabbarkeit.
Mit Jigsaw definierte Module sind voneinander abgekapselt. Abhängigkeiten müssen explizit deklariert werden (mit requires und exports). Das hat Auswirkungen auf viele Bereiche wie beispielsweise: Entwicklung, Kompilierung, Testen, Packaging, Deployment und Laufzeitumgebung. Außer direkten Abhängigkeiten können auch lose gekoppelte "Service Consumer" (mit uses) und "Service Provider" (mit provides) definiert werden.
Anders als OSGi bietet Jigsaw keine Services Registry, kein Life-Cycle-Modell, keine dynamische Versionierung und kein "Hot Deployment". Dafür ist Jigsaw einfacher und bietet Modularisierung in allen Phasen, insbesondere bereits beim Kompilieren.
Durch Java 9 und Jigsaw ergeben sich Änderungen in vielen Bereichen, wie beispielsweise Entwicklung, Kompilierung, Testen, Packaging, Deployment und Laufzeitumgebung. Einige Tools werden ohne Anpassung nicht lauffähig sein, beispielsweise aus folgenden Gründen:
Infos zu Java 9, Java 10, Java 11 und Jigsaw finden Sie unter:
Bis Java 8 gab es nur den Classpath und keinen Modulepath. Alle Klassen und alle Jar-Libs befinden sich gleichermaßen im gemeinsamen Namespace im CLASSPATH und public ist global für alle öffentlich.
Ab Java 9 haben Sie die Wahl: Sie können weiterhin nur den konventionellen Classpath verwenden (mit -cp), Sie können Jigsaw-Module im Modulepath verwenden (mit -p), und Sie können beides kombinieren. Die Neuerungen und Vorteile durch Jigsaw gelten natürlich nur für Jigsaw-Module im Modulepath. Die Summe der Module von der Java-Runtime-Umgebung und der Module im Modulepath werden "Observable Modules" genannt.
Außer dem Modulepath (per --module-path oder -p) gibt es für den javac-Compiler noch den Modulesourcepath (per --module-source-path), über den angegebenen werden kann, wo sich Sourcen zu anderen Modulen befinden (siehe das Beispiel weiter unten).
Es gibt fünf verschiedene Arten von Modulen (außer dem "Unnamed Module" sind alle anderen "Named Modules"):
Platform Explicit Modules:
Die Java Runtime von Java SE selbst ist in Jigsaw-Module aufgeteilt, in ca. 80 so genannte
Platform Modules.
Siehe: "java --list-modules" und "jmod describe "%JAVA_HOME%\jmods\java.base.jmod"".
Application Explicit Modules:
Application Modules sind Jigsaw-Module von Bibliotheken und Anwendungen.
Dies sind gewöhnliche Jar-Libs, die um eine Jigsaw-konforme
"Module Descriptor"-Datei
module-info.class,
in dem der Modulname sowie Abhängigkeiten, Sichtbarkeiten und Dienste definiert werden, zu
"Modular Jars"
erweitert wurden
(dies wird weiter unten in den Demos gezeigt).
Open Modules:
Wie Explicit Modules, aber anders als bei Explicit Modules werden zusätzlich zur Laufzeit alle Packages für Deep Reflection exportiert.
Automatic Modules:
Wenn Jar-Libs, welche keinen Jigsaw-Module-Descriptor enthalten, dem Modulpath hinzugefügt werden, werden sie als
Automatic Modules
behandelt.
Jigsaw nimmt den Jar-Lib-Dateinamen (ohne Versionsnummer und Endung) als Modulnamen.
Automatic Modules exportieren alle enthaltenen Packages und können alle anderen Module verwenden.
Umgekehrt können Application Modules auf Automatic Modules zugreifen (mit requires).
Unnamed Module:
Falls ein Classpath definiert ist, und falls sich dort Klassen oder Jar-Libs befinden,
werden diese alle zusammen einem so genannten
Unnamed Module
zugeordnet.
Pro Classloader gibt es maximal ein Unnamed Module.
Normale Anwendungen verwenden meistens einen einzigen Application-Classloader.
Jar-Libs im Classpath werden als Nicht-Jigsaw-Module behandelt, auch dann, wenn sie einen Jigsaw-Module-Descriptor enthalten.
Alle Klassen und Jar-Libs in diesem Unnamed Module können wie in früheren Java-Versionen ungehindert aufeinander zugreifen.
Das Unnamed Module kann auf andere Jigsaw-Module zugreifen.
Aber beachten Sie Folgendes zum umgekehrten Weg:
Automatic Modules können auf das Unnamed Module zugreifen, aber Named Application Modules können dies nicht.
Der Name eines Jigsaw-Moduls kann frei gewählt werden, aber er muss eindeutig sein. Empfohlen wird dasselbe "reverse-domain-name Pattern" wie auch bei Java-Packages, also beginnend mit der umgekehrten Webdomainadresse, alles in Kleinschreibung und separiert durch Punkte, beispielsweise so: de.meinefirma.meinerstesmodul.
Das führt dazu, dass dieser Name üblicherweise an mehreren Stellen mit unterschiedlicher Bedeutung verwendet wird:
Dies wird weiter unten noch mal aufgegriffen.
Als einfache Sourcecode-Verzeichnisstruktur für ein Projekt bestehend aus mehreren Jigsaw-Modulen in einem gemeinsamen src-Verzeichnis wird eine Struktur ähnlich zu dieser empfohlen:
[\MeinWorkspace\MeinProjektverzeichnis] '- [src] |- [de.meinefirma.meinerstesmodul] | |- module-info.java | '- [de] | '- [meinefirma] | '- [meinerstesmodul] | '- MeineApp.java '- [de.meinefirma.meinzweitesmodul] |- module-info.java '- [de] '- [meinefirma] '- [meinzweitesmodul] '- [meinunterpackage] '- MeineKlasseX.java
Das Besondere dabei ist, dass pro Modul der Sourcecode in einem Unterverzeichnis mit dem Namen des Moduls angeordnet werden soll.
Falls Sie mit Maven ein Multimodulprojekt inklusive JUnit-Modultests erstellen, und wieder pro Modul den Sourcecode in einem Unterverzeichnis mit dem Namen des Moduls anordnen wollen, sieht die Verzeichnisstruktur folgendermaßen aus:
[\MeinWorkspace\MeinProjektverzeichnis] |- pom.xml |- [meinerstesmodul] | |- pom.xml | '- [src] | |- [main] | | '- [java] | | '- [de.meinefirma.meinerstesmodul] | | |- module-info.java | | '- [de] | | '- [meinefirma] | | '- [meinerstesmodul] | | '- MeineApp.java | '- [test] | '- [java] | '- [de.meinefirma.meinerstesmodul] | '- [de] | '- [meinefirma] | '- [meinerstesmodul] | '- MeineAppTest.java '- [meinzweitesmodul] |- pom.xml '- [src] |- [main] | '- [java] | '- [de.meinefirma.meinzweitesmodul] | |- module-info.java | '- [de] | '- [meinefirma] | '- [meinzweitesmodul] | '- [meinunterpackage] | '- MeineKlasseX.java '- [test] '- [java] '- [de.meinefirma.meinzweitesmodul] '- [de] '- [meinefirma] '- [meinzweitesmodul] '- [meinunterpackage] '- MeineKlasseXTest.java
Da diese Verzeichnisstruktur etwas von der bei Maven üblichen Verzeichnisstruktur abweicht, müssen Sie in den pom.xml der Modulprojekte die Source-Verzeichnisse deklarieren, beispielsweise so für meinerstesmodul:
<build> <sourceDirectory>src/main/java/de.meinefirma.meinerstesmodul</sourceDirectory> <testSourceDirectory>src/test/java/de.meinefirma.meinerstesmodul</testSourceDirectory> <resources> <resource> <directory>src/main/resources/de.meinefirma.meinerstesmodul</directory> </resource> </resources> <testResources> <testResource> <directory>src/test/resources/de.meinefirma.meinerstesmodul</directory> </testResource> </testResources> </build>
Komplette Demo-Beispiele finden Sie weiter unten unter Jigsaw-Dep-Demo mit Maven (und Jigsaw-Dep-Demo mit IntelliJ IDEA), sowie Jigsaw-Service-Demo mit Maven und IntelliJ IDEA.
Falls Sie mit Maven ein Multimodulprojekt inklusive JUnit-Modultests erstellen, aber der üblichen Maven-Konvention folgen wollen und nicht pro Modul den Sourcecode in einem Unterverzeichnis mit dem Namen des Moduls anordnen wollen, sieht die Verzeichnisstruktur folgendermaßen aus:
[\MeinWorkspace\MeinProjektverzeichnis] |- pom.xml |- [meinerstesmodul] | |- pom.xml | '- [src] | |- [main] | | '- [java] | | |- module-info.java | | '- [de] | | '- [meinefirma] | | '- [meinerstesmodul] | | '- MeineApp.java | '- [test] | '- [java] | '- [de] | '- [meinefirma] | '- [meinerstesmodul] | '- MeineAppTest.java '- [meinzweitesmodul] |- pom.xml '- [src] |- [main] | '- [java] | |- module-info.java | '- [de] | '- [meinefirma] | '- [meinzweitesmodul] | '- [meinunterpackage] | '- MeineKlasseX.java '- [test] '- [java] '- [de] '- [meinefirma] '- [meinzweitesmodul] '- [meinunterpackage] '- MeineKlasseXTest.java
Da diese Verzeichnisstruktur der üblichen Maven-Konvention entspricht, brauchen Sie in der pom.xml nichts bezüglich sourceDirectory etc. zu deklarieren.
Anders als Java 8 erlauben neuere Java-Versionen ab Java 9 nicht ohne Weiteres den Zugriff auf alle Java-Libs. Um alte Programme dennoch unter Java ab Version 9 betreiben zu können, kann mit dem "--add-modules"-Parameter der Zugriff für bestimmte Module freigeschaltet werden.
Für java und jlink kann das beispielweise so aussehen:
java --add-modules java.xml.bind ...
java --add-modules java.se.ee ...
java --add-modules ALL-SYSTEM ...
jlink --add-modules de.meinefirma.meineapp ...
Manchmal genügt es, eine Environmentvariable zu setzen, beispielsweise für Tomcat JAVA_OPTS, etwa so:
set "JAVA_OPTS=--add-modules=ALL-SYSTEM"
Falls Maven verwendet wird, ist kann es etwas komplizierter werden. Manchmal kommt man mit Kommandozeilenoptionen aus, beispielsweise so:
mvn -DargLine="--add-modules java.xml.bind" ...
Aber oft muss in der POM an mehreren Stellen der --add-modules-Parameter hinzugefügt werden, leider mit unterschiedlicher Syntax. Beispiele für möglicherweise erforderliche Fragmente:<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>9</source> <target>9</target> <jdkToolchain> <version>9</version> </jdkToolchain> <compilerArgs> <arg>--add-modules</arg> <arg>java.xml.bind</arg> </compilerArgs> </configuration> </plugin>
<plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.20.1</version> <configuration> <argLine>--add-modules java.xml.bind</argLine> </configuration> </plugin>
<plugin> <artifactId>maven-javadoc-plugin</artifactId> <version>3.0.0-M1</version> <configuration> <additionalparam>--add-modules ALL-SYSTEM</additionalparam> </configuration> </plugin>
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <jvmArguments>--add-modules java.xml.bind</jvmArguments> </configuration> </plugin>
<plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven2-plugin</artifactId> <version>1.6.5</version> <configuration> <configuration> <properties> <cargo.jvmargs>--add-modules java.xml.bind</cargo.jvmargs> </properties> ...
Siehe hierzu auch beispielsweise: jlink --add-modules ..., REST mit JAX-RS (mit dem Surefire-Plugin), REST mit Tomcat (mit dem Cargo-Plugin), REST mit Tomcat (mit JAVA_OPTS), REST mit Jetty (mit dem Jetty-Plugin), REST mit Dropwizard (mit -DargLine=...), SOAP-Webservice mit JAX-WS (mit java --add-modules ...), Site Project Reports mit Java 9 (mit compilerArgs), Migrating a Spring Boot application to Java 9, Nicolas Fränkel und NoClassDefFoundError: javax/xml/bind/JAXBException.
Falls Module per Reflection zugänglich sein müssen, kann --add-opens verwendet werden, beispielsweise so:
java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED ...
Siehe hierzu auch beispielsweise: Guice und AOP und Vert.x.
Beachten Sie, dass es in bestimmten Fällen zu Problemen führt, wenn Sie zu viele Modue per --add-modules hinzufügen, beispielsweise per "--add-modules java.se.ee" oder per "--add-modules ALL-SYSTEM". Dies führt zu Problemen, wenn verschiedene Module oder ein Modul und eine Lib identische Packages verwenden, was ab Java 9 nicht möglich ist.
Konkretes Beispiel:
Sowohl das Java-SE-Modul
java.xml.ws.annotation
als auch die
Java-EE-Lib
javax.annotation/javax.annotation-api
verwenden das identische Package javax.annotation, siehe
Java SE: javax.annotation
und
Java EE: javax.annotation.
Falls Ihre Anwendung beispielsweise die Java-EE-Lib "javax.annotation:javax.annotation-api"
verwenden will, so ist das nicht möglich, wenn Sie per "--add-modules java.se.ee" oder per "--add-modules ALL-SYSTEM"
auch das Java-SE-Modul java.xml.ws.annotation eingebunden haben. Sie erhalten dann Exceptions ähnlich zu:
java.lang.NoClassDefFoundError: javax/annotation/Priority at org.glassfish.jersey.model.internal.RankedProvider.computeRank(RankedProvider.java:111) ... Caused by: java.lang.ClassNotFoundException: javax.annotation.Priority at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) ...
Die Lösung des Problems besteht meistens darin, dass Sie nur die wirklich benötigten Module per --add-modules hinzufügen. Oft genügt es, wenn Sie lediglich "--add-modules java.xml.bind" verwenden. Sie können Komma-getrennt weitere hinzuzufügende Module angeben, beispielsweise so: "--add-modules java.xml.bind,java.weiteresmodul".
Das Demobeispiel BuecherRestClient zeigt einen konkreten Anwendungsfall für den Fall, dass "--add-modules java.se.ee" zu der genannten Exception führt, wohingegen "--add-modules java.xml.bind" funktioniert.
Diese Demo zeigt:
wie "Modular Jars" mit Jigsaw-Moduldefinitionsdateien ("Module Descriptor" module-info.java) erstellt werden,
wie Abhängigkeiten ("Readability") mit requires und exports definiert werden,
wie Jigsaw-Anwendungen kompiliert und gebaut werden,
wie Eigenschaften von "Modular Jars" untersucht werden können, und
wie mit jlink eine ausführbare Distribution inklusive abgespecktem JDK erstellt wird.
Sie können die folgenden Programmierbeispiele entweder als Zipdatei downloaden oder Schritt für Schritt aufbauen, wie im Folgenden beschrieben wird.
Die Kommandos sind für Windows dargestellt. Falls Sie Linux oder Mac OS X verwenden, genügt es in der Regel, wenn Sie in Pfadangaben "\" durch "/", in PATH-Angaben ";" durch ":" und bei Platzhaltern %MEINE_VARIABLE% durch $MEINE_VARIABLE ersetzen.
Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:
cd \MeinWorkspace
mkdir JigsawDepDemo
cd JigsawDepDemo
mkdir src\de.meinefirma.meineapp\de\meinefirma\meineapp
mkdir src\de.meinefirma.meinutil\de\meinefirma\meinutil
tree /F
Erstellen Sie im src\de.meinefirma.meineapp-Verzeichnis die Jigsaw-Moduldeskriptordatei für das Hauptmodul:
module-info.java
module de.meinefirma.meineapp { // Importiertes Modul: requires de.meinefirma.meinutil; }
Erstellen Sie im src\de.meinefirma.meinutil-Verzeichnis die Jigsaw-Moduldeskriptordatei für das Hilfsmodul:
module-info.java
module de.meinefirma.meinutil { // Exportiertes Package: exports de.meinefirma.meinutil; }
Erstellen Sie im src\de.meinefirma.meineapp\de\meinefirma\meineapp-Verzeichnis die Hauptapplikationsklasse (im "Initial Module"):
MeineApp.java
package de.meinefirma.meineapp; import java.lang.ModuleLayer; import de.meinefirma.meinutil.MeinUtil; public class MeineApp { public static void main( String[] args ) { System.out.println( "CLASSPATH: " + System.getProperty( "java.class.path" ) ); System.out.println( "Class / Modul: " + MeineApp.class.getSimpleName() + " aus " + MeineApp.class.getModule() + ", " + MeinUtil.class.getSimpleName() + " aus " + MeinUtil.class.getModule() ); ModuleLayer lr = MeinUtil.class.getModule().getLayer(); if( lr != null ) { System.out.println( "Layer.Configuration: " + lr.configuration() ); System.out.println( "Layer.Modules: " + lr.modules() ); } else { System.out.println( "Fehler: ModuleLayer ist null" ); } System.out.println( "ProcessHandle-Infos: " + MeinUtil.getProcessInfos() ); } }
Erstellen Sie im src\de.meinefirma.meinutil\de\meinefirma\meinutil-Verzeichnis die Hilfsklasse:
MeinUtil.java
package de.meinefirma.meinutil; public class MeinUtil { public static String getProcessInfos() { return "pid: " + ProcessHandle.current().pid() + ", user: " + ProcessHandle.current().info().user() + ", cmd: " + ProcessHandle.current().info().command(); } }
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es unter Windows mit tree /F und unter Linux mit tree):
[\MeinWorkspace\JigsawDepDemo] '- [src] |- [de.meinefirma.meineapp] | |- [de] | | '- [meinefirma] | | '- [meineapp] | | '- MeineApp.java | '- module-info.java '- [de.meinefirma.meinutil] |- [de] | '- [meinefirma] | '- [meinutil] | '- MeinUtil.java '- module-info.java
Installation des JDK 10:
Überprüfen Sie anschließend, ob bei JAVA_HOME und im PATH die gewünschte defaultmäßig zu nutzende Java-Version eingetragen ist (dies braucht nicht JDK 10 zu sein):
echo PATH=%PATH% bzw. echo PATH=$PATH
echo JAVA_HOME=%JAVA_HOME% bzw. echo JAVA_HOME=$JAVA_HOME
javac -version
java -version
Falls Sie defaultmäßig eine andere Java-Version nutzen (z.B. Java 8), dann müssen Sie für alle folgenden Schritte für das aktuelle Kommandozeilenfenster auf Java 10 umschalten (passen Sie den Pfad zu Ihrer Java-10-Installation an):
set JAVA_HOME=C:\Program Files\Java\jdk-10
PATH=%JAVA_HOME%\bin;%PATH%
echo JAVA_HOME=%JAVA_HOME%
javac -version
java -version
$env:Path = "C:\Program Files\Java\jdk-10\bin;$env:Path"
javac -version
java -version
export JAVA_HOME=/usr/lib/jvm/java-10-oracle
export PATH=$JAVA_HOME/bin:$PATH
echo JAVA_HOME=$JAVA_HOME
javac -version
java -version
Die jeweils letzten Kommandos müssen Java 10 melden.
Kompilieren Sie alle vier Java-Klassen:
cd \MeinWorkspace\JigsawDepDemo
javac -d classes --module-source-path src src\de.meinefirma.meineapp\*.java src\de.meinefirma.meineapp\de\meinefirma\meineapp\*.java
javac -d classes --module-source-path src src\de.meinefirma.meinutil\*.java src\de.meinefirma.meinutil\de\meinefirma\meinutil\*.java
cd \MeinWorkspace\JigsawDepDemo
javac -d classes --module-source-path src $(dir src -r -i "*.java")
cd ~/MeinWorkspace/JigsawDepDemo
javac -d classes --module-source-path src $(find src -name "*.java")
Diese vereinfachte Vorgehensweise mit --module-source-path funktioniert so nur, wenn beim Kompilieren von de.meinefirma.meineapp der Sourcecode von de.meinefirma.meinutil zur Verfügung steht. Weiter unten ist beschrieben, wie es ohne --module-source-path geht (nur dann könnten die Modulsourcen auch auf verschiedenen PCs sein).
Führen Sie die Anwendung aus:
java -p classes -m de.meinefirma.meineapp/de.meinefirma.meineapp.MeineApp
Sie erhalten unter anderem (die weiteren Ausgaben sollen vorerst noch nicht interessieren):
... Class / Modul: MeineApp aus module de.meinefirma.meineapp, MeinUtil aus module de.meinefirma.meinutil ...
Das JDK 10 hat die Modul-Struktur erkannt, und die Anwendungsklasse MeineApp im Modul de.meinefirma.meineapp kann auf die Hilfsmethode MeinUtil.getProcessInfos() aus dem Modul de.meinefirma.meinutil zugreifen.
Löschen Sie testweise
in src\de.meinefirma.meineapp\module-info.java die Zeile requires de.meinefirma.meinutil; oder
in src\de.meinefirma.meinutil\module-info.java die Zeile exports de.meinefirma.meinutil;.
Sie erhalten die Fehlermeldung:
error: package de.meinefirma.meinutil does not exist
Fügen Sie für die folgenden Versuche die beiden Zeilen wieder ein und kompilieren Sie erneut.
Beim Export werden Packages angegeben (im Beispiel: exports de.meinefirma.meinutil).
Beim Import werden Module angegeben (im Beispiel: requires de.meinefirma.meinutil).
Häufig sind die Package-Namen und Modul-Namen identisch, so auch in diesem Beispiel: beide lauten: de.meinefirma.meinutil.
Normale Jar-Libs sind per Zip zusammengefügte Archive von .class-Dateien plus weiteren Ressourcendateien. Mit Jigsaw gibt es eine Neuerung: Wenn eine solche Jar-Lib im Root-Verzeichnis einen "Jigsaw Module Descriptor" in Form einer module-info.class-Datei enthält, ist dies eine so genannte "Modular Jar".
Erzeugen Sie pro Modul eine "Modular Jar" und führen Sie wieder die Hauptklasse MeineApp aus, aber diesmal mit dem "Initial Modular Jar" (vergessen Sie nicht den Punkt am Ende der beiden jar-Kommandos):
mkdir modules
jar --create --file modules/de.meinefirma.meineapp-1.0.jar --module-version 1.0 --main-class de.meinefirma.meineapp.MeineApp -C classes/de.meinefirma.meineapp .
jar --create --file modules/de.meinefirma.meinutil-1.0.jar --module-version 1.0 -C classes/de.meinefirma.meinutil .
java -p modules -m de.meinefirma.meineapp/de.meinefirma.meineapp.MeineApp
Beachten Sie, dass der Modulpfad hinter -p anders als beim vorherigen Beispiel diesmal nicht classes lautet, sondern modules.
Da wir beim Bauen der "Initial Modular Jar" mit dem Parameter --main-class die Hauptanwendungsklasse MeineApp angegeben haben, können wir die Anwendung auch mit einem kürzeren Kommando starten, bei dem nur der Modulname ausreicht:
java -p modules -m de.meinefirma.meineapp
Beachten Sie die vier verschiedenen Bedeutungen von "de.meinefirma.meineapp":
Beachten Sie, dass diese Regeln nicht alle zwingend sind, aber üblichen Konventionen entsprechen.
Analysieren Sie die Dependencies mit jdeps:
jdeps -s --module-path modules modules/*.jar
Oder ausführlicher:
jdeps --module-path modules modules/*.jar
Lassen Sie sich die Moduldeskriptoren mit jar anzeigen:
jar --describe-module --file=modules/de.meinefirma.meineapp-1.0.jar
jar --describe-module --file=modules/de.meinefirma.meinutil-1.0.jar
Oder kürzer:
jar -d --file=modules/de.meinefirma.meineapp-1.0.jar
jar -d --file=modules/de.meinefirma.meinutil-1.0.jar
Führen Sie weitere Analysen mit javap durch:
javap -verbose classes\de.meinefirma.meineapp\module-info.class
javap -verbose classes\de.meinefirma.meinutil\module-info.class
Auch jmod-Dateien können untersucht werden, mit jmod:
jmod describe "%JAVA_HOME%/jmods/java.se.ee.jmod"
jmod describe "%JAVA_HOME%/jmods/java.xml.bind.jmod"
Angenommen, die Sourcen zu den Modulen liegen auf verschiedenen PCs (z.B. weil verschiedene Projektgruppen daran arbeiten): Dann funktioniert die oben beschriebene Vorgehensweise mit --module-source-path nicht. In diesem Fall werden die Modul-Jars weitergereicht (üblicherweise über ein zentrales Repository). Führen Sie hierzu folgende Schritte aus (die Kommandos sind so gestaltet, dass sie testweise auch auf einem einzigen PC ausgeführt werden können):
Erster PC:
cd \MeinWorkspace\JigsawDepDemo
mkdir modules
javac -d classes\de.meinefirma.meinutil src\de.meinefirma.meinutil\*.java src\de.meinefirma.meinutil\de\meinefirma\meinutil\*.java
jar --create --file modules/de.meinefirma.meinutil-1.0.jar --module-version 1.0 -C classes/de.meinefirma.meinutil .
Transfer der resultierenden de.meinefirma.meinutil-1.0.jar zum zweiten PC in das modules-Verzeichnis und dann auf dem zweiten PC:
javac -p modules -d classes\de.meinefirma.meineapp src\de.meinefirma.meineapp\*.java src\de.meinefirma.meineapp\de\meinefirma\meineapp\*.java
jar --create --file modules/de.meinefirma.meineapp-1.0.jar --module-version 1.0 --main-class de.meinefirma.meineapp.MeineApp -C classes/de.meinefirma.meineapp .
Ausführung der Anwendung:
java -p modules -m de.meinefirma.meineapp
Seit Java 9 gibt es mit jlink einen Linker, der alle benötigten Module sowohl aus der Anwendung als auch vom JDK ermittelt und damit eine Distribution erstellt, die ohne zusätzliches Java ausführbar ist, da sie ein eigenes abgespecktes Java enthält ("Custom Modular Run-time image"):
jlink --module-path "%JAVA_HOME%/jmods;modules" --add-modules de.meinefirma.meineapp --output distribution
jlink --module-path "$JAVA_HOME/jmods:modules" --add-modules de.meinefirma.meineapp --output distribution
Führen Sie das Ergebnis aus:
distribution\bin\java -m de.meinefirma.meineapp
distribution/bin/java -m de.meinefirma.meineapp
CLASSPATH: Class / Modul: MeineApp aus module de.meinefirma.meineapp, MeinUtil aus module de.meinefirma.meinutil Layer.Configuration: de.meinefirma.meineapp, de.meinefirma.meinutil, java.base Layer.Modules: [module java.base, module de.meinefirma.meinutil, module de.meinefirma.meineapp] ProcessHandle-Infos: pid: ..., user: Optional[...\...], cmd: Optional[\MeinWorkspace\JigsawDepDemo\distribution\bin\java.exe]
Anders als bei nicht modularisierten Anwendungen ist der CLASSPATH leer.
Wie Sie an der letzten Ausgabezeile erkennen können, wird anders als bei den vorherigen Ausführungen diesmal nicht das Java aus JAVA_HOME verwendet, sondern das abgespeckte Java aus der Distribution (unter Windows: \MeinWorkspace\JigsawDepDemo\distribution\bin\java.exe).
Es werden die korrekt benamten Named Application Modules aufgelistet.
Außer den beiden "Application Modules" der Anwendung de.meinefirma.meineapp und de.meinefirma.meinutil wird bei dieser simplen Anwendung vom JDK lediglich das "Platform Module" java.base benötigt. Überprüfen Sie das mit:
distribution\bin\java --list-modules
Das erzeugte distribution-Verzeichnis ist inklusive Java ca. 31 MByte groß. Mit folgendem Kommando wird es nur ca. 21 MByte groß:
jlink --module-path "%JAVA_HOME%/jmods;modules" --add-modules de.meinefirma.meineapp --output distribution --exclude-files *.diz --strip-debug --compress=2
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JigsawDepDemo] |- [classes] | |- [de.meinefirma.meineapp] | | '- ... | '- [de.meinefirma.meinutil] | '- ... |- [distribution] | |- [bin] | | '- ... | |- [conf] | | '- ... | |- [lib] | | '- ... | '- release |- [modules] | |- de.meinefirma.meineapp-1.0.jar | '- de.meinefirma.meinutil-1.0.jar '- [src] |- [de.meinefirma.meineapp] | |- [de] | | '- [meinefirma] | | '- [meineapp] | | '- MeineApp.java | '- module-info.java '- [de.meinefirma.meinutil] |- [de] | '- [meinefirma] | '- [meinutil] | '- MeinUtil.java '- module-info.java
Diese Demo zeigt:
wie in Modular-Jar-Moduldeskriptordateien Service-Provider (mit provides) und Service-Consumer (mit uses) definiert werden,
wie Service-Consumer-Module Service-Provider finden und aufrufen können, obwohl keine Abhängigkeit zwischen den Modulen besteht,
und wie diese "lose Kopplung" Konfigurationsoptionen zur Laufzeit bietet.
Folgende Abhängigkeitsbeziehungen zwischen den Jigsaw-Modulen wird erstellt (weiter unten wird gezeigt, wie Sie solche Grafiken mit Graphviz erzeugen):
Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:
cd \MeinWorkspace
mkdir JigsawServiceDemo
cd JigsawServiceDemo
mkdir src\de.meinefirma.meininterface\de\meinefirma\meininterface
mkdir src\de.meinefirma.meinprovider\de\meinefirma\meinprovider
mkdir src\de.meinefirma.meinconsumer\de\meinefirma\meinconsumer
tree /F
Erstellen Sie im src\de.meinefirma.meininterface-Verzeichnis den Jigsaw-Module-Descriptor für das Service-Interface: module-info.java
module de.meinefirma.meininterface { exports de.meinefirma.meininterface; }
Erstellen Sie im src\de.meinefirma.meinprovider-Verzeichnis den Jigsaw-Module-Descriptor für den Service-Provider: module-info.java
module de.meinefirma.meinprovider { requires transitive de.meinefirma.meininterface; provides de.meinefirma.meininterface.MeinServiceInterface with de.meinefirma.meinprovider.MeineServiceImplementierungA, de.meinefirma.meinprovider.MeineServiceImplementierungB; }
Erstellen Sie im src\de.meinefirma.meinconsumer-Verzeichnis den Jigsaw-Module-Descriptor für den Service-Konsumenten: module-info.java
module de.meinefirma.meinconsumer { requires de.meinefirma.meininterface; uses de.meinefirma.meininterface.MeinServiceInterface; }
Erstellen Sie im src\de.meinefirma.meininterface\de\meinefirma\meininterface-Verzeichnis die Service-Interface-Klasse: MeinServiceInterface.java
package de.meinefirma.meininterface; public interface MeinServiceInterface { String getInfos(); }
Erstellen Sie im src\de.meinefirma.meinprovider\de\meinefirma\meinprovider-Verzeichnis die erste Service-Provider-Klasse: MeineServiceImplementierungA.java
package de.meinefirma.meinprovider; import de.meinefirma.meininterface.MeinServiceInterface; public class MeineServiceImplementierungA implements MeinServiceInterface { @Override public String getInfos() { return " A: ProcessHandle-Infos: pid: " + ProcessHandle.current().pid() + ", user: " + ProcessHandle.current().info().user() + ", cmd: " + ProcessHandle.current().info().command(); } }
Erstellen Sie im src\de.meinefirma.meinprovider\de\meinefirma\meinprovider-Verzeichnis die zweite Service-Provider-Klasse: MeineServiceImplementierungB.java
package de.meinefirma.meinprovider; import de.meinefirma.meininterface.MeinServiceInterface; public class MeineServiceImplementierungB implements MeinServiceInterface { @Override public String getInfos() { return " B: Service-Provider: " + MeineServiceImplementierungB.class.getSimpleName() + " aus " + MeineServiceImplementierungB.class.getModule(); } }
Erstellen Sie im src\de.meinefirma.meinconsumer\de\meinefirma\meinconsumer-Verzeichnis die Service-Konsument-Klasse: MeinServiceConsumer.java
package de.meinefirma.meinconsumer; import java.util.Iterator; import java.util.ServiceLoader; import de.meinefirma.meininterface.MeinServiceInterface; public class MeinServiceConsumer { public static void main( String[] args ) { System.out.println( "" ); Iterator<MeinServiceInterface> iter = ServiceLoader.load( MeinServiceInterface.class ).iterator(); if( !iter.hasNext() ) { System.out.println( "Keine Implementierung zu 'MeinServiceInterface' vorhanden." ); } while( iter.hasNext() ) { System.out.println( iter.next().getInfos() ); } } }
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JigsawServiceDemo] '- [src] |- [de.meinefirma.meinconsumer] | |- [de] | | '- [meinefirma] | | '- [meinconsumer] | | '- MeinServiceConsumer.java | '- module-info.java |- [de.meinefirma.meininterface] | |- [de] | | '- [meinefirma] | | '- [meininterface] | | '- MeinServiceInterface.java | '- module-info.java '- [de.meinefirma.meinprovider] |- [de] | '- [meinefirma] | '- [meinprovider] | |- MeineServiceImplementierungA.java | '- MeineServiceImplementierungB.java '- module-info.java
JDK 10 muss installiert und aktiviert sein:
Falls Sie defaultmäßig eine andere Java-Version nutzen (z.B. Java 8), dann müssen Sie für alle folgenden Schritte für das aktuelle Kommandozeilenfenster auf Java 10 umschalten (passen Sie den Pfad zu Ihrer Java-10-Installation an) (falls Sie Linux verwenden: siehe oben):
set JAVA_HOME=C:\Program Files\Java\jdk-10
PATH=%JAVA_HOME%\bin;%PATH%
Überprüfen Sie, ob bei JAVA_HOME und im PATH das JDK 10 korrekt eingetragen ist:
echo JAVA_HOME=%JAVA_HOME%
javac -version
java -version
In allen drei Fällen muss Java 10 gemeldet werden.
Kompilieren und Bauen des Interface-Moduls:
cd \MeinWorkspace\JigsawServiceDemo
mkdir modules
javac -d classes\de.meinefirma.meininterface src\de.meinefirma.meininterface\*.java src\de.meinefirma.meininterface\de\meinefirma\meininterface\*.java
jar --create --file=modules/de.meinefirma.meininterface.jar -C classes/de.meinefirma.meininterface .
Kompilieren und Bauen des Provider-Moduls:
javac -p modules -d classes\de.meinefirma.meinprovider src\de.meinefirma.meinprovider\*.java src\de.meinefirma.meinprovider\de\meinefirma\meinprovider\*.java
jar --create --file=modules/de.meinefirma.meinprovider.jar -C classes/de.meinefirma.meinprovider .
Kompilieren und Bauen des Consumer-Moduls:
javac -p modules -d classes\de.meinefirma.meinconsumer src\de.meinefirma.meinconsumer\*.java src\de.meinefirma.meinconsumer\de\meinefirma\meinconsumer\*.java
jar --create --file=modules/de.meinefirma.meinconsumer.jar --main-class=de.meinefirma.meinconsumer.MeinServiceConsumer -C classes/de.meinefirma.meinconsumer .
Ausführen der Demo:
java -p modules -m de.meinefirma.meinconsumer
Sie erhalten zwei Ausgabezeilen, für jeden der beiden Service-Provider eine:
A: ProcessHandle-Infos: pid: ..., user: Optional[...\...], cmd: Optional[C:\Program Files\Java\jdk-10\bin\java.exe] B: Service-Provider: MeineServiceImplementierungB aus module de.meinefirma.meinprovider
Analysieren Sie die Dependencies mit jdeps:
jdeps -s modules/*.jar
jdeps modules/*.jar
Lassen Sie sich die Moduldeskriptoren anzeigen:
jar --describe-module --file=modules/de.meinefirma.meininterface.jar
jar --describe-module --file=modules/de.meinefirma.meinprovider.jar
jar --describe-module --file=modules/de.meinefirma.meinconsumer.jar
Beachten Sie:
Diese Demo zeigt:
wie in einem Jigsaw-Projekt eine Nicht-Jigsaw-Lib als "Automatic Module" eingebunden werden kann, und
wie eine Modul-Dependency per "Implied Readability" an ein anderes Modul weitergereicht werden kann.
Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:
cd \MeinWorkspace
mkdir JigsawAutoModImpliedReadDemo
cd JigsawAutoModImpliedReadDemo
mkdir modules
mkdir src\de.meinefirma.meineapp\de\meinefirma\meineapp
mkdir src\de.meinefirma.meinutil\de\meinefirma\meinutil
tree /F
Als "Nicht-Jigsaw-Lib" soll die Google-Guava-Lib verwendet werden. Downloaden Sie guava-21.0.jar von http://central.maven.org/maven2/com/google/guava/guava/21.0/ in das modules-Verzeichnis.
Erstellen Sie im src\de.meinefirma.meineapp-Verzeichnis die Jigsaw-Moduldeskriptordatei für das Hauptmodul:
module-info.java
module de.meinefirma.meineapp { requires de.meinefirma.meinutil; }
Erstellen Sie im src\de.meinefirma.meinutil-Verzeichnis die Jigsaw-Moduldeskriptordatei für das Hilfsmodul:
module-info.java
module de.meinefirma.meinutil { requires transitive guava; exports de.meinefirma.meinutil; }
Erstellen Sie im src\de.meinefirma.meineapp\de\meinefirma\meineapp-Verzeichnis die Hauptapplikationsklasse (im "Initial Module"):
MeineApp.java
package de.meinefirma.meineapp; import com.google.common.base.Joiner; import de.meinefirma.meinutil.MeinUtil; public class MeineApp { public static void main( String[] args ) { Joiner joiner = Joiner.on( ", " ).skipNulls(); System.out.println( "\n" + joiner.join( classAndModule( Joiner.class ), classAndModule( MeinUtil.class ), classAndModule( MeineApp.class ) ) ); } private static String classAndModule( Class<?> clss ) { return clss.getSimpleName() + " aus " + clss.getModule(); } }
Erstellen Sie im src\de.meinefirma.meinutil\de\meinefirma\meinutil-Verzeichnis die Hilfsklasse:
MeinUtil.java
package de.meinefirma.meinutil; public class MeinUtil { public static String dummy() { return "... wird nicht benutzt"; } }
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JigsawAutoModImpliedReadDemo] '- [modules] | '- guava-21.0.jar '- [src] |- [de.meinefirma.meineapp] | |- [de] | | '- [meinefirma] | | '- [meineapp] | | '- MeineApp.java | '- module-info.java '- [de.meinefirma.meinutil] |- [de] | '- [meinefirma] | '- [meinutil] | '- MeinUtil.java '- module-info.java
Falls Sie defaultmäßig eine andere Java-Version nutzen (z.B. Java 8), dann müssen Sie für alle folgenden Schritte für das aktuelle Kommandozeilenfenster auf Java 10 umschalten (passen Sie den Pfad zu Ihrer Java-10-Installation an) (falls Sie Linux verwenden: siehe oben):
set JAVA_HOME=C:\Program Files\Java\jdk-10
PATH=%JAVA_HOME%\bin;%PATH%
Kontrolle:
echo JAVA_HOME=%JAVA_HOME%
javac -version
java -version
In allen drei Fällen muss Java 10 gemeldet werden.
Löschen Sie Ergebnisse vorheriger Versuche:
cd \MeinWorkspace\JigsawAutoModImpliedReadDemo
rd /S /Q classes
del modules\de.meinefirma.*
Falls Sie guava-21.0.jar noch nicht downgeloaded haben, und curl installiert haben:
mkdir modules
cd modules
curl -O http://central.maven.org/maven2/com/google/guava/guava/21.0/guava-21.0.jar
cd ..
Kompilieren und Bauen Sie zuerst das Modul de.meinefirma.meinutil:
javac -p modules -d classes\de.meinefirma.meinutil src\de.meinefirma.meinutil\*.java src\de.meinefirma.meinutil\de\meinefirma\meinutil\*.java
jar --create --file modules/de.meinefirma.meinutil-1.0.jar --module-version 1.0 -C classes/de.meinefirma.meinutil .
Kompilieren und Bauen Sie das davon abhängige Modul de.meinefirma.meineapp:
javac -p modules -d classes\de.meinefirma.meineapp src\de.meinefirma.meineapp\*.java src\de.meinefirma.meineapp\de\meinefirma\meineapp\*.java
jar --create --file modules/de.meinefirma.meineapp-1.0.jar --module-version 1.0 --main-class de.meinefirma.meineapp.MeineApp -C classes/de.meinefirma.meineapp .
Führen Sie die Anwendung aus:
java -p modules -m de.meinefirma.meineapp
Sie erhalten:
Joiner aus module guava, MeinUtil aus module de.meinefirma.meinutil, MeineApp aus module de.meinefirma.meineapp
Beachten Sie:
Dies ist ein Jigsaw-Projekt, welches nur einen Modulepath und keinen Classpath verwendet.
Zusätzlich zu zwei Jigsaw-Modular-Jars wird die Nicht-Jigsaw-Lib guava-21.0.jar dem Modulepath hinzugefügt und von den Modular Jars als "Automatic Module" verwendet.
Das Modul de.meinefirma.meineapp verwendet die Joiner-Klasse aus der Guava-Lib, obwohl es keine Dependency dazu deklariert. Dies funktioniert nur, weil es eine Dependency zum Modul de.meinefirma.meinutil gibt, und dieses Modul seine Dependency zu Guava als transitive deklariert ("requires transitive guava"). Dieses "Weiterreichen" der Dependency wird "Implied Readability" genannt.
"Aggregator Modules": Das "Weiterreichen" mehrer Dependencies ermöglicht den Bau von Modulen, die nichts anderes machen, als eine Reihe von Dependencies in einem Modul zusammenzufassen und als eine einzige Dependency zur Verfügung zu stellen.
Lassen Sie sich die Moduldeskriptoren anzeigen:
jar --describe-module --file=modules/de.meinefirma.meineapp-1.0.jar
jar --describe-module --file=modules/de.meinefirma.meinutil-1.0.jar
Überprüfen Sie, dass Guava keinen Moduldeskriptor hat:
jar --describe-module --file=modules/guava-21.0.jar
Analysieren Sie die Dependencies von Guava mit jdeps:
jdeps modules/guava-21.0.jar
Diese Demo soll zum Experiemtieren einladen:
Es werden sechs Jar-Libs bzw. -Module erstellt. Fünf dieser Jar-Libs bzw. Module enthalten jeweils eine Klasse de.meinefirma.meinutil.MeinUtil mit demselben Namen und demselben Package, aber unterschiedlichem Inhalt. In einem Modul gibt es noch zwei weitere Klassen: ExtraUtil im selben Package und ExtraKlasse in einem anderen Package.
Es werden verschiedene Aufrufarten untersucht, so dass jede der fünf de.meinefirma.meinutil.MeinUtil mal verwendet wird.
Dabei werden die Jar-Libs und Module in verschiedenen Modularten betrieben: als "Application Module", als "Automatic Module" und im "Unnamed Module". Unter anderem wird die Aufrufreihenfolge "Application Module" --> "Automatic Module" --> "Unnamed Module" gezeigt.
In den Fällen, bei denen es keinen Modulepath gibt, sondern nur einen Classpath, in dem sich die Jar-Libs und Module befinden, hängt es von der möglicherweise zufälligen Reihenfolge im Classpath ab, welche de.meinefirma.meinutil.MeinUtil-Klasse ausgeführt wird. Außerdem können sich dann die Public-Klassen ungehindert gegenseitig aufrufen.
Wird statt oder zusätzlich zum Classpath der Modulepath verwendet, ist definiert, welche de.meinefirma.meinutil.MeinUtil-Klasse ausgeführt wird. Auf nicht explizit erlaubte Module ist kein Zugriff möglich. Bei dem Versuch, Klassen aus anderen Jar-Libs und Modulen mit demselben Package zu verwenden, gibt es keinen Compilierfehler, aber einen Laufzeitfehler (NoClassDefFoundError oder ClassNotFoundException). Das gilt auch für Reflection, wie das Beispiel demonstriert.
Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:
cd \MeinWorkspace
mkdir JigsawDuplicateVersions
cd JigsawDuplicateVersions
mkdir src\meinutilv1\de\meinefirma\meinutil
mkdir src\meinutilv2\de\meinefirma\meinutil
mkdir src\meinutilv2\de\meinefirma\extrapackage
mkdir src\meinv3util\de\meinefirma\meinutil
mkdir src\de.meinefirma.meinv4util\de\meinefirma\meinutil
mkdir src\de.meinefirma.meinv5util\de\meinefirma\meinutil
mkdir src\de.meinefirma.meineapp\de\meinefirma\meineapp
tree /F
Erstellen Sie folgende drei Jigsaw-Moduldeskriptordateien:
Im src\de.meinefirma.meinv4util-Verzeichnis: module-info.java
module de.meinefirma.meinv4util { exports de.meinefirma.meinutil; }
Im src\de.meinefirma.meinv5util-Verzeichnis: module-info.java
module de.meinefirma.meinv5util { exports de.meinefirma.meinutil; }
Im src\de.meinefirma.meineapp-Verzeichnis: module-info.java
module de.meinefirma.meineapp { requires meinv3util; }
Erstellen Sie folgende acht Java-Klassen:
Im src\meinutilv1\de\meinefirma\meinutil-Verzeichnis: MeinUtil.java
package de.meinefirma.meinutil; public class MeinUtil { public static void main( String[] args ) { System.out.println( "\n---- Version 1, MeinUtil.class aus:\n " + MeinUtil.class.getClassLoader().getResource( "de/meinefirma/meinutil/MeinUtil.class" ) ); } }
Im src\meinutilv2\de\meinefirma\meinutil-Verzeichnis: MeinUtil.java
package de.meinefirma.meinutil; public class MeinUtil { public static void main( String[] args ) { System.out.println( "\n---- Version 2, MeinUtil.class aus:\n " + MeinUtil.class.getClassLoader().getResource( "de/meinefirma/meinutil/MeinUtil.class" ) ); } }
Im src\meinutilv2\de\meinefirma\meinutil-Verzeichnis: ExtraUtil.java
package de.meinefirma.meinutil; public class ExtraUtil { public static String test() { return " de.meinefirma.meinutil.ExtraUtil aus meinutilv2 kann verwendet werden."; } }
Im src\meinutilv2\de\meinefirma\extrapackage-Verzeichnis: ExtraKlasse.java
package de.meinefirma.extrapackage; public class ExtraKlasse { public static String test() { return " de.meinefirma.extrapackage.ExtraKlasse aus meinutilv2 kann verwendet werden."; } }
Im src\meinv3util\de\meinefirma\meinutil-Verzeichnis: MeinUtil.java
package de.meinefirma.meinutil; public class MeinUtil { public static void main( String[] args ) { System.out.println( "\n---- Version 3, MeinUtil.class aus: " + MeinUtil.class.getModule() ); try { System.out.println( ExtraUtil.test() ); } catch( NoClassDefFoundError ex ) { System.out.println( " de.meinefirma.meinutil.ExtraUtil aus meinutilv2 kann nicht direkt verwendet werden (" + ex.getClass().getSimpleName() + ")." ); } try { Class<?> clss = MeinUtil.class.getClassLoader().loadClass( "de.meinefirma.meinutil.ExtraUtil" ); System.out.println( ((ExtraUtil) clss.getConstructor().newInstance()).test() ); } catch( ReflectiveOperationException ex ) { System.out.println( " de.meinefirma.meinutil.ExtraUtil aus meinutilv2 kann nicht per Reflection verwendet werden (" + ex.getClass().getSimpleName() + ")." ); } try { System.out.println( de.meinefirma.extrapackage.ExtraKlasse.test() ); } catch( NoClassDefFoundError ex ) { System.out.println( " de.meinefirma.extrapackage.ExtraKlasse aus meinutilv2 kann nicht verwendet werden (" + ex.getClass().getSimpleName() + ")." ); } } }
Im src\de.meinefirma.meinv4util\de\meinefirma\meinutil-Verzeichnis: MeinUtil.java
package de.meinefirma.meinutil; import java.io.IOException; import java.net.URL; import java.util.Enumeration; public class MeinUtil { public static void main( String[] args ) { System.out.println( "\n---- Version 4, MeinUtil.class aus: " + MeinUtil.class.getModule() ); try { System.out.println( "\n---- ClassLoader().getResources(MeinUtil): " ); Enumeration<URL> urls = MeinUtil.class.getClassLoader().getResources( "de/meinefirma/meinutil/MeinUtil.class" ); while( urls.hasMoreElements() ) { System.out.println( " " + urls.nextElement() ); } System.out.println( "\n---- CLASSPATH: " + System.getProperty( "java.class.path" ) ); } catch( IOException ex ) { System.out.println (ex ); } } }
Im src\de.meinefirma.meinv5util\de\meinefirma\meinutil-Verzeichnis: MeinUtil.java
package de.meinefirma.meinutil; public class MeinUtil { public static void main( String[] args ) { System.out.println( "\n---- Version 5, MeinUtil.class aus: " + MeinUtil.class.getModule() ); } }
Im src\de.meinefirma.meineapp\de\meinefirma\meineapp-Verzeichnis: MeineApp.java
package de.meinefirma.meineapp; import de.meinefirma.meinutil.MeinUtil; public class MeineApp { public static void main( String[] args ) { MeinUtil.main( null ); } }
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JigsawDuplicateVersions] '- [src] |- [de.meinefirma.meineapp] | |- [de] | | '- [meinefirma] | | '- [meineapp] | | '- MeineApp.java | '- module-info.java |- [de.meinefirma.meinv4util] | |- [de] | | '- [meinefirma] | | '- [meinutil] | | '- MeinUtil.java | '- module-info.java |- [de.meinefirma.meinv5util] | |- [de] | | '- [meinefirma] | | '- [meinutil] | | '- MeinUtil.java | '- module-info.java |- [meinutilv1] | '- [de] | '- [meinefirma] | '- [meinutil] | '- MeinUtil.java |- [meinutilv2] | '- [de] | '- [meinefirma] | |- [extrapackage] | | '- ExtraKlasse.java | '- [meinutil] | |- ExtraUtil.java | '- MeinUtil.java '- [meinv3util] '- [de] '- [meinefirma] '- [meinutil] '- MeinUtil.java
Aktivieren Sie Java 10.
Löschen Sie Ergebnisse vorheriger Versuche und erstellen Sie zwei Verzeichnisse:
rd /S /Q classes
rd /S /Q modules
rd /S /Q libs
mkdir modules
mkdir libs
Kompilieren und Bauen Sie zuerst im libs-Verzeichnis zwei Libs für das "Unnamed Module":
javac -d classes\meinutilv1 src\meinutilv1\de\meinefirma\meinutil\*.java
jar --create --file=libs/meinutilv1.jar --main-class=de.meinefirma.meinutil.MeinUtil -C classes/meinutilv1 .
javac -d classes\meinutilv2 src\meinutilv2\de\meinefirma\meinutil\*.java src\meinutilv2\de\meinefirma\extrapackage\*.java
jar --create --file=libs/meinutilv2.jar --main-class=de.meinefirma.meinutil.MeinUtil -C classes/meinutilv2 .
Kompilieren und Bauen Sie im modules-Verzeichnis ein "Automatic Module" und drei "Application Modules":
javac -cp libs/* -d classes\meinv3util src\meinv3util\de\meinefirma\meinutil\*.java
jar --create --file=modules/meinv3util.jar --main-class=de.meinefirma.meinutil.MeinUtil -C classes/meinv3util .
javac -d classes\de.meinefirma.meinv4util src\de.meinefirma.meinv4util\*.java src\de.meinefirma.meinv4util\de\meinefirma\meinutil\*.java
jar --create --file=modules/de.meinefirma.meinv4util.jar --main-class=de.meinefirma.meinutil.MeinUtil -C classes/de.meinefirma.meinv4util .
javac -d classes\de.meinefirma.meinv5util src\de.meinefirma.meinv5util\*.java src\de.meinefirma.meinv5util\de\meinefirma\meinutil\*.java
jar --create --file=modules/de.meinefirma.meinv5util.jar --main-class=de.meinefirma.meinutil.MeinUtil -C classes/de.meinefirma.meinv5util .
javac -p modules -d classes\de.meinefirma.meineapp src\de.meinefirma.meineapp\*.java src\de.meinefirma.meineapp\de\meinefirma\meineapp\*.java
jar --create --file=modules/de.meinefirma.meineapp.jar --main-class=de.meinefirma.meineapp.MeineApp -C classes/de.meinefirma.meineapp .
Listen Sie die Ergebnis-Libs und -Module auf:
dir libs /B
dir modules /B
[\MeinWorkspace\JigsawDuplicateVersions] |- [libs] | |- [meinutilv1.jar] | '- [meinutilv2.jar] '- [modules] |- [de.meinefirma.meineapp.jar] |- [de.meinefirma.meinv4util.jar] |- [de.meinefirma.meinv5util.jar] '- [meinv3util.jar]
Führen Sie die Module auf unterschiedliche Art aus, so dass jede der fünf MeinUtil-Klassen ausgeführt wird:
java -jar libs/meinutilv2.jar
---- Version 2, MeinUtil.class aus: jar:file:/D:/MeinWorkspace/JigsawDuplicateVersions/libs/meinutilv2.jar!/de/meinefirma/meinutil/MeinUtil.class
(Kein Modulepath, kein Classpath, nur jar-Lib.)
java -cp libs/*;modules/* de.meinefirma.meinutil.MeinUtil
---- Version 1, MeinUtil.class aus: jar:file:/D:/MeinWorkspace/JigsawDuplicateVersions/libs/meinutilv1.jar!/de/meinefirma/meinutil/MeinUtil.class
(Kein Modulepath, nur Classpath. Alle 5 MeinUtil-Klassen liegen im Classpath. Verwendet wird MeinUtil aus meinutilv1.jar, weil am Anfang vom Classpath.)
java -cp modules/*;libs/* de.meinefirma.meinutil.MeinUtil
---- Version 4, MeinUtil.class aus: unnamed module @7946e1f4 ---- ClassLoader().getResources(MeinUtil): jar:file:/D:/MeinWorkspace/JigsawDuplicateVersions/modules/de.meinefirma.meinv4util.jar!/de/meinefirma/meinutil/MeinUtil.class jar:file:/D:/MeinWorkspace/JigsawDuplicateVersions/modules/de.meinefirma.meinv5util.jar!/de/meinefirma/meinutil/MeinUtil.class jar:file:/D:/MeinWorkspace/JigsawDuplicateVersions/modules/meinv3util.jar!/de/meinefirma/meinutil/MeinUtil.class jar:file:/D:/MeinWorkspace/JigsawDuplicateVersions/libs/meinutilv1.jar!/de/meinefirma/meinutil/MeinUtil.class jar:file:/D:/MeinWorkspace/JigsawDuplicateVersions/libs/meinutilv2.jar!/de/meinefirma/meinutil/MeinUtil.class ---- CLASSPATH: modules/de.meinefirma.meineapp.jar;modules/de.meinefirma.meinv4util.jar;modules/de.meinefirma.meinv5util.jar; modules/meinv3util.jar;libs/meinutilv1.jar;libs/meinutilv2.jar
(Kein Modulepath, nur Classpath. Alle 5 MeinUtil-Klassen liegen im Classpath und werden mit ClassLoader.getResources() gefunden. Wegen geänderter Classpath-Reihenfolge wird diesmal MeinUtil aus de.meinefirma.meinv4util.jar verwendet. Obwohl de.meinefirma.meinv4util.jar einen Moduldeskriptor enthält, wird er dem "unnamed module" zugeordnet.)
java -p modules -m de.meinefirma.meinv5util
---- Version 5, MeinUtil.class aus: module de.meinefirma.meinv5util
(Kein Classpath, nur Modulepath.)
java -cp modules/meinv3util.jar;libs/* de.meinefirma.meinutil.MeinUtil
---- Version 3, MeinUtil.class aus: unnamed module @39fb3ab6 de.meinefirma.meinutil.ExtraUtil aus meinutilv2 kann verwendet werden. de.meinefirma.meinutil.ExtraUtil aus meinutilv2 kann verwendet werden. de.meinefirma.extrapackage.ExtraKlasse aus meinutilv2 kann verwendet werden.
(Kein Modulepath, nur Classpath: Alle Public-Klassen können sich gegenseitig aufrufen, sowohl direkt als auch per Reflection.)
java -cp libs/* -p modules -m meinv3util
---- Version 3, MeinUtil.class aus: module meinv3util de.meinefirma.meinutil.ExtraUtil aus meinutilv2 kann nicht direkt verwendet werden (NoClassDefFoundError). de.meinefirma.meinutil.ExtraUtil aus meinutilv2 kann nicht per Reflection verwendet werden (ClassNotFoundException). de.meinefirma.extrapackage.ExtraKlasse aus meinutilv2 kann verwendet werden.
(Sowohl Classpath als auch Modulepath. Es wird MeinUtil aus meinv3util.jar verwendet, weil mit -m ausgewählt. Die ExtraUtil-Klasse kann weder direkt noch per Reflection verwendet werden, weil sie das selbe bereits verwendete Package de.meinefirma.meinutil verwendet. Beachtenswert ist, dass es hierzu keinen Compilierfehler, sondern nur einen Laufzeitfehler gibt.)
java -cp libs/* -p modules -m de.meinefirma.meineapp
---- Version 3, MeinUtil.class aus: module meinv3util de.meinefirma.meinutil.ExtraUtil aus meinutilv2 kann nicht direkt verwendet werden (NoClassDefFoundError). de.meinefirma.meinutil.ExtraUtil aus meinutilv2 kann nicht per Reflection verwendet werden (ClassNotFoundException). de.meinefirma.extrapackage.ExtraKlasse aus meinutilv2 kann verwendet werden.
(Sowohl Classpath als auch Modulepath. Es wird MeinUtil aus meinv3util.jar verwendet, weil mit -m das Modul de.meinefirma.meineapp ausgewählt wurde, welches meinv3util per requires einbindet. Die ExtraUtil-Klasse kann aus demselben Grund wie eben wieder nicht verwendet werden. Die ExtraKlasse in dem anderen Package de.meinefirma.extrapackage kann dagegen verwendet werden. Beachten Sie hierzu die Aufrufreihenfolge: Das "Application Module" de.meinefirma.meineapp ruft das "Automatic Module" meinv3util auf, welches die Lib meinutilv2 vom "Unnamed Module" verwendet.)
Mit dem grafischen Visualisierungstool Graphviz können Sie einfach eine grafische Darstellung der Jigsaw-Modulabhängigkeiten erzeugen. Dazu werden die Modulabhängigkeiten mit jdeps im DOT-Format gespeichert, welches Graphviz verwerten kann.
Genauere Hinweise zur Installation von Graphviz sowohl für Windows als auch für Linux finden Sie unter:
Installation Notes.
Für dieses Beispiel genügt folgende einfache Installation:
md \Tools\Graphviz
cd \Tools\Graphviz
curl -O http://www.graphviz.org/pub/graphviz/stable/windows/graphviz-2.38.zip
jar xf graphviz-2.38.zip
del graphviz-2.38.zip
sudo apt-get install graphviz
Sinn macht die grafische Darstellung nur, wenn Sie ein Projekt mit vielen Abhängigkeiten haben. Die obigen Beispiele haben alle zu wenige Abhängigkeiten. Trotzdem soll am vorherigen JigsawDuplicateVersions-Beispiel die Anwendung von Graphviz gezeigt werden:
Windows:
cd \MeinWorkspace\JigsawDuplicateVersions
jdeps -dotoutput jdeps-dotoutput modules/*.jar
\Tools\Graphviz\release\bin\dot.exe -Tpng jdeps-dotoutput/summary.dot > graphviz-summary.png
start graphviz-summary.png
Linux:
cd ~/MeinWorkspace/JigsawDuplicateVersions
jdeps -dotoutput jdeps-dotoutput modules/*.jar
dot -Tpng jdeps-dotoutput/summary.dot > graphviz-summary.png
eog graphviz-summary.png
Sie erhalten:
Diese Demo zeigt:
wie das oben gezeigte Beispiel Jigsaw-Demo mit Modulabhängigkeiten in einer für Apache Maven geeigneten Struktur erstellt werden kann.
Sehen Sie sich hierzu die verschiedenen Konzepte zur Verzeichnisstruktur an.
Sie können die folgenden Programmierbeispiele entweder als Zipdatei downloaden oder Schritt für Schritt aufbauen, wie im Folgenden beschrieben wird.
Die Kommandos sind für Windows dargestellt. Falls Sie Linux oder Mac OS X verwenden, genügt es in der Regel, wenn Sie in Pfadangaben "\" durch "/", in PATH-Angaben ";" durch ":" und bei Platzhaltern %MEINE_VARIABLE% durch $MEINE_VARIABLE ersetzen.
Maven muss installiert sein (z.B. in Version 3.6.0).
Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace) und führen Sie folgende Kommandos aus:
cd \MeinWorkspace
mkdir JigsawDepDemoMitMaven
cd JigsawDepDemoMitMaven
mkdir meineapp\src\main\java\de.meinefirma.meineapp\de\meinefirma\meineapp
mkdir meinutil\src\main\java\de.meinefirma.meinutil\de\meinefirma\meinutil
tree /F
Erstellen Sie im JigsawDepDemoMitMaven-Wurzelverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma</groupId> <artifactId>JigsawDepDemoMitMaven</artifactId> <version>1.0</version> <packaging>pom</packaging> <modules> <module>meineapp</module> <module>meinutil</module> </modules> <properties> <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>9</source> <target>9</target> <showWarnings>true</showWarnings> <showDeprecation>true</showDeprecation> </configuration> </plugin> </plugins> </build> </project>
Verwechseln Sie nicht Maven-Module mit Jigsaw-Modulen: Beispielsweise enthält das Maven-Modul meineapp das Jigsaw-Modul de.meinefirma.meineapp.
Erstellen Sie im meineapp-Modulverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.meinefirma</groupId> <artifactId>JigsawDepDemoMitMaven</artifactId> <version>1.0</version> </parent> <artifactId>de.meinefirma.meineapp</artifactId> <build> <sourceDirectory>src/main/java/de.meinefirma.meineapp</sourceDirectory> <testSourceDirectory>src/test/java/de.meinefirma.meineapp</testSourceDirectory> </build> <dependencies> <dependency> <groupId>de.meinefirma</groupId> <artifactId>de.meinefirma.meinutil</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
Erstellen Sie im meinutil-Modulverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.meinefirma</groupId> <artifactId>JigsawDepDemoMitMaven</artifactId> <version>1.0</version> </parent> <artifactId>de.meinefirma.meinutil</artifactId> <build> <sourceDirectory>src/main/java/de.meinefirma.meinutil</sourceDirectory> <testSourceDirectory>src/test/java/de.meinefirma.meinutil</testSourceDirectory> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
Erstellen Sie im meineapp\src\main\java\de.meinefirma.meineapp-Verzeichnis die Jigsaw-Moduldeskriptordatei für das Hauptmodul: module-info.java
module de.meinefirma.meineapp { requires de.meinefirma.meinutil; }
Erstellen Sie im meinutil\src\main\java\de.meinefirma.meinutil-Verzeichnis die Jigsaw-Moduldeskriptordatei für das Hilfsmodul: module-info.java
module de.meinefirma.meinutil { exports de.meinefirma.meinutil; }
Erstellen Sie im meineapp\src\main\java\de.meinefirma.meineapp\de\meinefirma\meineapp-Verzeichnis die Hauptapplikationsklasse (im "Initial Module"): MeineApp.java
package de.meinefirma.meineapp; import java.lang.ModuleLayer; import de.meinefirma.meinutil.MeinUtil; public class MeineApp { public static void main( String[] args ) { System.out.println( "CLASSPATH: " + System.getProperty( "java.class.path" ) ); System.out.println( "Class / Modul: " + MeineApp.class.getSimpleName() + " aus " + MeineApp.class.getModule() + ", " + MeinUtil.class.getSimpleName() + " aus " + MeinUtil.class.getModule() ); ModuleLayer lr = MeinUtil.class.getModule().getLayer(); if( lr != null ) { System.out.println( "Layer.Configuration: " + lr.configuration() ); System.out.println( "Layer.Modules: " + lr.modules() ); } else { System.out.println( "Fehler: ModuleLayer ist null" ); } System.out.println( "ProcessHandle-Infos: " + MeinUtil.getProcessInfos() ); } }
Erstellen Sie im meinutil\src\main\java\de.meinefirma.meinutil\de\meinefirma\meinutil-Verzeichnis die Hilfsklasse: MeinUtil.java
package de.meinefirma.meinutil; public class MeinUtil { public static String getProcessInfos() { return "pid: " + ProcessHandle.current().pid() + ", user: " + ProcessHandle.current().info().user() + ", cmd: " + ProcessHandle.current().info().command(); } }
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es unter Windows mit tree /F und unter Linux mit tree):
[\MeinWorkspace\JigsawDepDemoMitMaven] |- pom.xml |- [meineapp] | |- pom.xml | '- [src] | '- [main] | '- [java] | '- [de.meinefirma.meineapp] | |- module-info.java | '- [de] | '- [meinefirma] | '- [meineapp] | '- MeineApp.java '- [meinutil] |- pom.xml '- [src] '- [main] '- [java] '- [de.meinefirma.meinutil] |- module-info.java '- [de] '- [meinefirma] '- [meinutil] '- MeinUtil.java
Aktivieren Sie Java 10 wie oben beschrieben.
Bauen Sie die Anwendung und führen Sie sie aus (das lange java-Kommando in einer Zeile):
cd \MeinWorkspace\JigsawDepDemoMitMaven
mvn clean package
java -p meineapp\target\de.meinefirma.meineapp-1.0.jar;meinutil\target\de.meinefirma.meinutil-1.0.jar -m de.meinefirma.meineapp/de.meinefirma.meineapp.MeineApp
Sie erhalten ein ähnliches Ergebnis, wie bereits oben gezeigt (der CLASSPATH ist leer, und bei Class / Modul werden die korrekt benamten Named Application Modules aufgelistet):
CLASSPATH: Class / Modul: MeineApp aus module de.meinefirma.meineapp, MeinUtil aus module de.meinefirma.meinutil Layer.Configuration: de.meinefirma.meineapp, de.meinefirma.meinutil, ... Layer.Modules: [module de.meinefirma.meineapp, module de.meinefirma.meinutil, ...] ProcessHandle-Infos: pid: ..., user: Optional[...\...], cmd: Optional[C:\Program Files\Java\jdk-10\bin\java.exe]
Um zu beweisen, dass Sie eine echte modularisierte Java-9-Jigsaw-Anwendung erstellt haben, können Sie testweise temporär in einer der beiden module-info.java-Dateien entweder die requires- oder die exports-Zeile auskommentieren. Dann erhalten Sie bereits beim "mvn clean package"-Kommando eine Fehlermeldung wegen fehlender Sichtbarkeit.
Um JUnit-Modultests hinzuzufügen, erzeugen Sie folgendermaßen zwei neue Verzeichnisse, in welchen Sie geeignete JUnit-Modultest-Klassen erstellen können:
cd \MeinWorkspace\JigsawDepDemoMitMaven
mkdir meineapp\src\test\java\de.meinefirma.meineapp\de\meinefirma\meineapp
mkdir meinutil\src\test\java\de.meinefirma.meinutil\de\meinefirma\meinutil
tree /F
Die Projektstruktur sieht dann so aus:
[\MeinWorkspace\JigsawDepDemoMitMaven] |- pom.xml |- [meineapp] | |- pom.xml | '- [src] | |- [main] | | '- [java] | | '- [de.meinefirma.meineapp] | | |- module-info.java | | '- [de] | | '- [meinefirma] | | '- [meineapp] | | '- MeineApp.java | '- [test] | '- [java] | '- [de.meinefirma.meineapp] | '- [de] | '- [meinefirma] | '- [meineapp] | '- MeineAppTest.java '- [meinutil] |- pom.xml '- [src] |- [main] | '- [java] | '- [de.meinefirma.meinutil] | |- module-info.java | '- [de] | '- [meinefirma] | '- [meinutil] | '- MeinUtil.java '- [test] '- [java] '- [de.meinefirma.meinutil] '- [de] '- [meinefirma] '- [meinutil] '- MeinUtilTest.java
Um ein konkretes ausführbares Beispiel zu zeigen: Ein ziemlich primitiver Demo-JUnit-Modultest könnte beispielsweise so aussehen: MeinUtilTest.java
package de.meinefirma.meinutil; import org.junit.*; public class MeinUtilTest { @Test public void testProcessInfos() { String s = MeinUtil.getProcessInfos(); Assert.assertTrue( "pid:", s.contains( "pid:" ) ); Assert.assertTrue( "user: Optional", s.contains( "user: Optional" ) ); Assert.assertTrue( "cmd: Optional", s.contains( "cmd: Optional" ) ); Assert.assertTrue( "java", s.contains( "java" ) ); } }
Jetzt erhalten Sie beim "mvn clean package"-Kommando:
... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running de.meinefirma.meinutil.MeinUtilTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.1 sec ...
Falls Sie folgende Fehlermeldung erhalten:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.6.1:testCompile (default-testCompile) on project ...: Fatal error compiling: invalid flag: -Xmodule:...
Dann verwenden Sie das maven-compiler-plugin in einer zu alten Version. Für JUnit-Tests mit Jigsaw-Modulen unter Java 10 benötigen Sie mindestens Version 3.6.2. Siehe hierzu: Maven Compiler Plugin: MCOMPILER-294: test-compile broken due to -Xmodule removal in jdk-ea167.
Im Folgenden soll das obige "Jigsaw-Dep-Demo mit Maven"-Beispiel in die Entwicklungsumgebung JetBrains IntelliJ IDEA geladen werden.
Für Java 9 und Jigsaw benötigen Sie IntelliJ IDEA in mindestens der Version 2017.2. Installieren Sie IntelliJ IDEA wie beschrieben unter: Install and set up IntelliJ IDEA, Installation unter Ubuntu und Erste Schritte.
Führen Sie das obige "Jigsaw-Dep-Demo mit Maven"-Beispiel vollständig aus. Die Kommandos
cd \MeinWorkspace\JigsawDepDemoMitMaven
mvn clean package
dürfen keinen Fehler melden.
Laden Sie das Maven-Projekt in IntelliJ IDEA:
File | Open... | \MeinWorkspace\JigsawDepDemoMitMaven.
Achten Sie darauf, dass es als Maven-Projekt erkannt wird: View | Tool Windows | Maven Projects.
Achten Sie darauf, dass Java 10 konfiguriert ist: File | Project Structure | Project | Project SDK.
Öffnen Sie per Strg+N den JUnit-Modultest MeinUtilTest, und führen Sie ihn aus, z.B. per Strg+Shift+F10.
Öffnen Sie per Strg+N die Hauptapplikationsklasse MeineApp, und führen Sie sie aus, z.B. per Strg+Shift+F10.
Bei der Ausführung von MeineApp erhalten Sie ein ähnliches Ergebnis, wie bereits oben gezeigt (der CLASSPATH ist leer, und bei Class / Modul werden die korrekt benamten Named Application Modules aufgelistet):
CLASSPATH: Class / Modul: MeineApp aus module de.meinefirma.meineapp, MeinUtil aus module de.meinefirma.meinutil Layer.Configuration: de.meinefirma.meineapp, de.meinefirma.meinutil, ... Layer.Modules: [module de.meinefirma.meineapp, module de.meinefirma.meinutil, ...] ProcessHandle-Infos: pid: ..., user: Optional[...\...], cmd: Optional[C:\Program Files\Java\jdk-10\bin\java.exe]
Diese Demo zeigt:
wie das oben gezeigte Beispiel Jigsaw-Demo mit Service-Provider und -Consumer in einer für Apache Maven geeigneten Struktur erstellt werden kann,
und wie es in JetBrains IntelliJ IDEA geöffnet und ausgeführt werden kann.
Folgende Abhängigkeitsbeziehungen zwischen den Jigsaw-Modulen wird erstellt:
Diese Demo funktioniert mit JetBrains IntelliJ IDEA ab Version 2017.2.
Voraussetzung ist die obige Jigsaw-Demo mit Service-Provider und -Consumer im JigsawServiceDemo-Verzeichnis.
Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. \MeinWorkspace), legen Sie das neue Projektverzeichnis JigsawServiceDemoMitMaven an, und kopieren Sie die Sourcedateien vom JigsawServiceDemo-Projekt:
cd \MeinWorkspace
mkdir JigsawServiceDemoMitMaven
cd JigsawServiceDemoMitMaven
xcopy ..\JigsawServiceDemo\src\de.meinefirma.meininterface\*.* meininterface\src\main\java\de.meinefirma.meininterface\ /S
xcopy ..\JigsawServiceDemo\src\de.meinefirma.meinconsumer\*.* meinconsumer\src\main\java\de.meinefirma.meinconsumer\ /S
xcopy ..\JigsawServiceDemo\src\de.meinefirma.meinprovider\*.* meinprovider\src\main\java\de.meinefirma.meinprovider\ /S
tree /F
Erstellen Sie im JigsawServiceDemoMitMaven-Wurzelverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.meinefirma</groupId> <artifactId>JigsawServiceDemoMitMaven</artifactId> <version>1.0</version> <packaging>pom</packaging> <modules> <module>meinconsumer</module> <module>meininterface</module> <module>meinprovider</module> </modules> <properties> <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>9</source> <target>9</target> <showWarnings>true</showWarnings> <showDeprecation>true</showDeprecation> </configuration> </plugin> </plugins> </build> </project>
Erstellen Sie im meininterface-Modulverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.meinefirma</groupId> <artifactId>JigsawServiceDemoMitMaven</artifactId> <version>1.0</version> </parent> <artifactId>de.meinefirma.meininterface</artifactId> <build> <sourceDirectory>src/main/java/de.meinefirma.meininterface</sourceDirectory> </build> </project>
Erstellen Sie im meinconsumer-Modulverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.meinefirma</groupId> <artifactId>JigsawServiceDemoMitMaven</artifactId> <version>1.0</version> </parent> <artifactId>de.meinefirma.meinconsumer</artifactId> <build> <sourceDirectory>src/main/java/de.meinefirma.meinconsumer</sourceDirectory> </build> <dependencies> <dependency> <groupId>de.meinefirma</groupId> <artifactId>de.meinefirma.meininterface</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>de.meinefirma</groupId> <artifactId>de.meinefirma.meinprovider</artifactId> <version>1.0</version> <scope>runtime</scope> </dependency> </dependencies> </project>
Erstellen Sie im meinprovider-Modulverzeichnis die Maven-Projektkonfigurationsdatei: pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.meinefirma</groupId> <artifactId>JigsawServiceDemoMitMaven</artifactId> <version>1.0</version> </parent> <artifactId>de.meinefirma.meinprovider</artifactId> <build> <sourceDirectory>src/main/java/de.meinefirma.meinprovider</sourceDirectory> </build> <dependencies> <dependency> <groupId>de.meinefirma</groupId> <artifactId>de.meinefirma.meininterface</artifactId> <version>1.0</version> </dependency> </dependencies> </project>
Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):
[\MeinWorkspace\JigsawServiceDemoMitMaven] |- pom.xml |- [meinconsumer] | |- pom.xml | '- [src] | '- [main] | '- [java] | '- [de.meinefirma.meinconsumer] | |- module-info.java | '- [de] | '- [meinefirma] | '- [meinconsumer] | '- MeinServiceConsumer.java |- [meininterface] | |- pom.xml | '- [src] | '- [main] | '- [java] | '- [de.meinefirma.meininterface] | |- module-info.java | '- [de] | '- [meinefirma] | '- [meininterface] | '- MeinServiceInterface.java '- [meinprovider] |- pom.xml '- [src] '- [main] '- [java] '- [de.meinefirma.meinprovider] |- module-info.java '- [de] '- [meinefirma] '- [meinprovider] |- MeineServiceImplementierungA.java '- MeineServiceImplementierungB.java
Aktivieren Sie Java 10 wie oben beschrieben.
Bauen Sie die Anwendung und führen Sie sie aus (das lange java-Kommando in einer Zeile):
cd \MeinWorkspace\JigsawServiceDemoMitMaven
mvn clean package
java -p meinconsumer\target\de.meinefirma.meinconsumer-1.0.jar;meininterface\target\de.meinefirma.meininterface-1.0.jar;meinprovider\target\de.meinefirma.meinprovider-1.0.jar -m de.meinefirma.meinconsumer/de.meinefirma.meinconsumer.MeinServiceConsumer
Sie erhalten ein ähnliches Ergebnis, wie bereits oben gezeigt:
A: ProcessHandle-Infos: pid: ..., user: Optional[...\...], cmd: Optional[C:\Program Files\Java\jdk-10\bin\java.exe] B: Service-Provider: MeineServiceImplementierungB aus module de.meinefirma.meinprovider
Laden Sie das Maven-Projekt in IntelliJ IDEA:
File | Open... | \MeinWorkspace\JigsawServiceDemoMitMaven.
Achten Sie darauf, dass es als Maven-Projekt erkannt wird: View | Tool Windows | Maven Projects.
Achten Sie darauf, dass Java 10 konfiguriert ist: File | Project Structure | Project | Project SDK.
Öffnen Sie per Strg+N die Anwendungsklasse MeinServiceConsumer, und führen Sie sie aus, z.B. per Strg+Shift+F10.
Bei der Ausführung von MeinServiceConsumer erhalten Sie wieder das gleiche Ergebnis, wie eben gezeigt.
Leider ist
Eclipse Oxygen 3 (4.7.3)
noch nicht so weit, wie IntelliJ IDEA,
insbesondere beim Import von Java-9-Maven-Projekten mit Jigsaw-Modulabhängigkeiten und inklusive JUnit-Modultests.
Siehe hierzu beispielsweise:
Eclipse Oxygen.1a Release (4.7.1a): Error occurred during initialization of boot layer,
Maven + Eclipse Oxygen + Java 9: Error occurred during initialization of boot layer,
Eclipse Bug 517777: Running a Java 9 application in Eclipse Oxygen 4.7 does not set the module path,
Eclipse Bug 514760: Run configuration should support notion of modules und
Java 9 Support for Oxygen.
Falls Sie trotzdem eigene Versuche durchführen wollen, könnten vielleicht folgende Hinweise hilfreich sein:
Eclipse-Release-Versionen erhalten Sie über:
http://www.eclipse.org/downloads/packages/.
Eclipse-Experimentiervorabversionen können Sie downloaden über:
https://eclipse.org/downloads/index-developer.php,
http://www.eclipse.org/downloads/packages/release/Oxygen/ und
http://download.eclipse.org/eclipse/downloads/index.html.
Zur Planung zukünftiger Update Releases siehe:
Simultaneous Release.
Je nach Ausgangsinstallationsdatei enthält Ihre Eclipse-Installation mehr oder weniger viele Plug-ins. Falls in Ihrer Installation unter dem Menüpunkt "Help" der Menüpunkt "Eclipse Marketplace..." fehlt, installieren Sie folgendermaßen das Marketplace-Plugin:
Help | Install New Software... | Work with: Oxygen - http://download.eclipse.org/releases/oxygen | Name: > General Purpose Tools | [x] Marketplace Client | Next > | Next > | I accept ... | Finish.
Warten Sie, bis Sie aufgefordert werden, Eclipse neu zu starten (das kann mehrere Minuten dauern). Betätigen Sie: Restart Now. Nach dem Neustart finden Sie unter "Help" den Menüpunkt "Eclipse Marketplace...".
In aktuellen Eclipse-Versionen brauchen Sie das Java-9-Plugin nicht zusätzlich zu installieren. Falls Sie eine ältere Eclipse-Version verwenden, müssen Sie es installieren:
Help | Eclipse Marketplace... | Find: Java 9 Support for Oxygen 4.7 | Install | Confirm > | I accept ... | Finish.
Warten Sie, bis Sie aufgefordert werden, Eclipse neu zu starten (das kann mehrere Minuten dauern), und restarten Sie.
Prüfen Sie, ob Ihre Eclipse-Installation das Maven-m2e-Plugin beinhaltet:
Wählen Sie: File | Import... Wenn in der Import-Wizard-Liste Maven fehlt,
installieren Sie folgendermaßen das Maven-m2e-Plugin:
Sie können das Maven-m2e-Plugin wie üblich über den Eclipse Marketplace installieren.
Alternativ können Sie stattdessen folgendermaßen installieren, um vielleicht eine aktuellere Version zu erhalten:
Help | Install New Software... | Work with: Oxygen - http://download.eclipse.org/releases/oxygen | Name: > General Purpose Tools | [x] m2e - Maven Integration for Eclipse (includes Incubating components) | Next > | Next > | I accept ... | Finish.
Warten Sie, bis Sie aufgefordert werden, Eclipse neu zu starten (das kann mehrere Minuten dauern), und restarten Sie.
Lassen Sie sich die installierten Plug-ins anzeigen und kontrollieren Sie die installierten Versionen:
Help | About Eclipse | Installation Details | Installed Software:
...
Eclipse Java Development Tools (JDT), Version 3.13.3.v20180301-0715
...
m2e - Maven Integration for Eclipse (includes Incubating components), Version 1.8.3.20180227-2137
...
Falls Sie außer Java 9 noch weitere Java-Versionen installiert haben,
und Eclipse nicht mit Java 9 gestartet wurde,
können Sie folgendermaßen Java 9 vorgeben:
Beenden Sie Eclipse.
Fügen Sie in der eclipse.ini vor der Zeile -vmargs zusätzlich folgende zwei Zeilen hinzu
(passen Sie den Pfad zum JDK 9 an):
-vm
C:\Program Files\Java\jdk-9\bin
Starten Sie Eclipse und überprüfen Sie die geänderte Eclipse-Konfiguration:
Help | About Eclipse | Installation Details | Configuration.
Konfigurieren Sie Java 9 global:
Window | Preferences | > Java | > Installed JREs | --> ist Java 9 als Standard VM gewählt?
Window | Preferences | > Java | > Build Path | Classpath Variables | --> ist Java 9 konfiguriert?
Window | Preferences | > Java | > Compiler | --> ist Java 9 konfiguriert und Use default compliance settings aktiviert?
Importieren Sie das obige "Jigsaw-Dep-Demo mit Maven"-Beispiel:
File | Import... | > Maven | Existing Maven Projects | Next > | Root Directory: \MeinWorkspace\JigsawDepDemoMitMaven | Finish.
Konfigurieren Sie Java 9 für die Unterprojekte:
Window | Preferences | > Java | > Compiler | Configure Project Specific Settings... | --> ist für alle Projektmodule Java 9 konfiguriert und Use default compliance settings aktiviert?
Falls Sie folgende Fehlermeldung erhalten:
Build path specifies execution environment J2SE-1.4. There are no JREs installed in the workspace that are strictly compatible with this environment.
Dann klicken Sie im linken Package-Explorer-Fenster mit der rechten Maustaste nacheinander auf die beiden Projektmodulnamen de.meinefirma.meineapp und de.meinefirma.meinutil und konfigurieren Sie jeweils:
Build Path | Configure Build Path... | Java Build Path | Libraries | Entfernen Sie "JRE System Library [J2SE-1.4]" per Remove | Fügen Sie "JRE System Library [jdk-9]" hinzu per Add Library... | JRE System Library | Next > | Workspace default JRE (jdk-9) | Finish | Apply and Close.
Falls Sie in Eclipse im Modul de.meinefirma.meineapp die Fehlermeldung "de.meinefirma.meinutil cannot be resolved to a module" erhalten, müssen Sie eventuell Folgendes konfigurieren:
Klicken Sie im linken Package-Explorer-Fenster mit der rechten Maustaste auf den Projektmodulnamen
de.meinefirma.meineapp, und wählen Sie nacheinander:
a) Properties | Java Build Path | Projects | Modulepath | Add... | de.meinefirma.meinutil aktivieren | OK | Apply and Close;
b) Properties | Maven | "Resolve dependencies from Workspace projects" deaktivieren | Apply and Close.
Passen Sie die Startkonfiguration an unter:
Run | Run Configurations...
Öffnen Sie per Strg+Shift+T die Hauptapplikationsklasse MeineApp, und führen Sie sie aus, z.B. per Strg+F11.
Leider kann das Ergebnis nicht überzeugen:
Je nach Konfiguration erhalten Sie:
"Error occurred during initialization of boot layer, java.lang.module.FindException: Module ... not found",
oder "clientBuilder.sslSocketFactory(SSLSocketFactory) not supported on JDK 9+",
oder die Module werden fälschlicherweise als "Unnamed Module" über den CLASSPATH geladen,
oder der JUnit-Modultest lässt sich nicht ausführen.