Das Java Platform Module System (JPMS, "Jigsaw")

+ andere TechDocs
+ OSGi
+ Docker
+


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:

Wichtige Ziele von Jigsaw sind:

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.



Inhalt

  1. Vergleich mit anderen Modularisierungskonzepten
    Java Access Modifier / Visibility Modifier, Jar-Libs, Jigsaw, OSGi, Docker
  2. Wichtige Änderungen ab Java 9 und durch Jigsaw
    Änderungen und Inkompatibilitäten, Infos zu Java 9, Java 10 und Jigsaw
  3. Modulepath, Jigsaw-Modularten sowie Konventionen zur Benamung und zur Verzeichnisstruktur
    Modulepath versus Classpath, Arten von Jigsaw-Modulen, Modul-Name, Verzeichnisstruktur
  4. "add-modules" mit Maven
  5. Problem mit "add-modules"
  6. Jigsaw-Demo mit Modulabhängigkeiten
    Verzeichnisstruktur und Java-Dateien, module-info.java, MeineApp.java, MeinUtil.java, Installation des JDK 9 oder JDK 10, Aktivieren von Java 10, Kompilieren mit "module-source-path" und direktes Ausführen, Depenency: Package versus Module, Modular Jars erstellen und ausführen, Benamung, Modular-Jar-Analyse, Kompilieren ohne "module-source-path", Mit jlink eine ausführbare Distribution inklusive abgespecktem JDK erstellen, Bemerkenswertes
  7. Jigsaw-Demo mit Service-Provider und -Consumer
    Verzeichnisstruktur und Java-Dateien, module-info.java, MeinServiceInterface.java, MeineServiceImplementierungA.java, MeineServiceImplementierungB.java, MeinServiceConsumer.java, Modular Jars erstellen und ausführen, Bemerkenswertes
  8. Jigsaw-Demo mit einem "Automatic Module" und mit "Implied Readability"
    Verzeichnisstruktur und Java-Dateien, module-info.java, MeineApp.java, MeinUtil.java, Kompilieren, Bauen und Ausführen, Bemerkenswertes
  9. Jigsaw-Demo mit doppelten Klassen und verschiedenen Modularten
    Anlage der Verzeichnisstruktur und Erzeugen der Java-Dateien, Jigsaw-Moduldeskriptordateien, Java-Klassen, Kompilieren und Bauen, Ausführung auf verschiedene Arten
  10. Abhängigkeitsgraph mit Graphviz
  11. Jigsaw-Dep-Demo mit Maven
  12. Jigsaw-Dep-Demo mit IntelliJ IDEA
  13. Jigsaw-Service-Demo mit Maven und IntelliJ IDEA
  14. Jigsaw-Dep-Demo mit Eclipse



Vergleich mit anderen Modularisierungskonzepten

Java Access Modifier / Visibility Modifier (Zugriffs-/Sichtbarkeitsmodifizierer)
Über die Access Modifier private, protected, public, sowie default (ohne Modifier), kann die Sichtbarkeit von Feldern, Methoden und Klassen definiert werden. Damit ist allerdings keine wirkliche Modularisierung und gezielte Abhängigkeitskontrolle möglich, da die Attribute anwendungsweit gelten. public bedeutet global für alle öffentlich. Und die anderen einschränkenden Modifier erlauben keine gezielte Veröffentlichung für bestimmte andere Packages.
Jar-Libs
Die Bündelung von Klassen in Jar-Libs erhöht die Übersichtlichkeit, verdeutlicht die gewollte Struktur und erleichtert die Weitergabe von Bibliotheken. Aber die Aufteilung in Jars bedeutet keine wirkliche Modularisierung, weil auch hierbei keine Abhängigkeitskontrolle möglich ist. Die Namen der Jar-Libs und die Grenzen zwischen den Jar-Libs haben für die JVM und den ClassLoader keine Bedeutung: Alle Klassen aller Jar-Libs befinden sich gleichermaßen im gemeinsamen Namespace im CLASSPATH und es gilt weiterhin: public ist global für alle öffentlich.
Von der "JAR Hell" spricht man, wenn mehrere zu einer Anwendung zusammengefügte Jar-Libs die gleichen Klassen in unterschiedlichen Versionen enthalten. Dann kann es von der zufälligen Reihenfolge im CLASSPATH abhängen, welche Version geladen wird.
Java Platform Module System (JPMS, "Jigsaw")
Mit Java 9 wird das Modularisierungssystem Jigsaw eingeführt. Damit wird erstmalig ein Modulsystem Bestandteil von Java, welches explizite Abhängigkeitskontrolle verwendet. Jigsaw-Module ("Modular Jars") werden von der JVM und vom ClassLoader erkannt. Sie müssen alle benötigten Fremd-Module explizit deklarieren (mit requires) und eigene öffentlich zur Verfügung gestellte Packages explizit benennen (mit exports). Lose gekoppelte "Services" werden unterstützt, indem "Service Consumer" (mit uses) und "Service Provider" (mit provides) definiert werden. Jigsaw ermöglicht echte Kapselung und Modularisierung, und zwar in allen Phasen, insbesondere bereits beim Kompilieren. Anders als bei OSGi werden bei Jigsaw zur Trennung der Module nicht getrennte ClassLoader verwendet.
OSGi
OSGi gibt es bereits seit dem Jahr 2000. Es bietet ein sehr ausgereiftes gut funktionierendes Modularisierungssystem mit expliziter Abhängigkeitskontrolle, welches in vielen Produkten Verwendung findet. OSGi ist etwas aufwändiger als Jigsaw, aber anders als Jigsaw bietet OSGi viele über die reine Modularisierung hinausgehende Optionen, beispielsweise:
- Versionierung wird unterstützt. Zu einer Komponente können gleichzeitig mehrere Versionen in Betrieb sein.
- Life-Cycle-Management. Komponenten können während des laufenden Betriebs hinzugefügt und ausgetauscht werden ("Hot Deployment", "Hot Plugging"), auch übers Internet.
- Eine Service Registry bietet die Suche nach passenden Komponenten zur Laufzeit.
- Service-Orientierung und lose Kopplung werden optimal unterstützt.
Docker
Docker bietet eine viel weiter gehende Kapselung und Modularisierung. Die Module laufen in getrennten Containern und damit auch in getrennten Java-VMs, oder sogar in einer gänzlich anderen Programmiersprache. Eine Kommunikation zwischen den Modulen in getrennten Docker-Containern erfolgt über Netzwerkschnittstellen (z.B. TCP/IP, REST etc.) und explizit freigegebenen Portnummern.
In Docker-Containern realisierte Dienste sind häufig einzeln deploybar sowie durch mehrfache Instanziierung leicht skalierbar.
Während mit Jigsaw und OSGi realisierte Dienste manchmal als Nanoservice bezeichnet werden, fallen in Docker-Containern realisierte Dienste eher in die Rubrik Microservices und Self-contained Systems (SCS).


Wichtige Änderungen ab Java 9 und durch Jigsaw

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:



Modulepath, Jigsaw-Modularten sowie Konventionen zur Benamung und zur Verzeichnisstruktur

Modulepath versus Classpath

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

Arten von Jigsaw-Modulen

Es gibt fünf verschiedene Arten von Modulen (außer dem "Unnamed Module" sind alle anderen "Named Modules"):

Konventionen zur Benamung

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.

Einfache Verzeichnisstruktur mehrerer Jigsaw-Module in einem gemeinsamen src-Verzeichnis

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.

Verzeichnisstruktur für Maven mit Modulunterverzeichnissen

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.

Verzeichnisstruktur für Maven ohne Modulunterverzeichnissen

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.



"add-modules" mit Maven

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.



Problem mit "add-modules"

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.



Jigsaw-Demo mit Modulabhängigkeiten

JigsawDepDemo-Graphviz Diese Demo zeigt:

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.

Anlage der Verzeichnisstruktur und Erzeugen der Java-Dateien

  1. 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

  2. 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;
    }
    
  3. 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;
    }
    
  4. 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() );
       }
    }
    
  5. 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();
       }
    }
    
  6. 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 9 oder JDK 10

  1. Installation des JDK 10:

  2. Ü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

Aktivieren von Java 10

  1. 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):

    Die jeweils letzten Kommandos müssen Java 10 melden.

Kompilieren mit "module-source-path" und direktes Ausführen des Beispiels

  1. Kompilieren Sie alle vier Java-Klassen:

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

  2. 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.

  3. 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

  4. Fügen Sie für die folgenden Versuche die beiden Zeilen wieder ein und kompilieren Sie erneut.

  5. Beachten Sie:

    Häufig sind die Package-Namen und Modul-Namen identisch, so auch in diesem Beispiel: beide lauten: de.meinefirma.meinutil.

Pro Modul ein Modular Jar erstellen und die Anwendung damit ausführen

  1. 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".

  2. 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.

  3. 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

  4. Beachten Sie die vier verschiedenen Bedeutungen von "de.meinefirma.meineapp":

    Beachten Sie, dass diese Regeln nicht alle zwingend sind, aber üblichen Konventionen entsprechen.

Analyse der Modular Jars mit jdeps, mit describe-module und mit javap

  1. Analysieren Sie die Dependencies mit jdeps:

    jdeps -s --module-path modules modules/*.jar

    Oder ausführlicher:

    jdeps --module-path modules modules/*.jar

  2. 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

  3. 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

  4. 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"

Kompilieren ohne "module-source-path" (z.B. falls getrennter Soucecode)

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):

  1. 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 .

  2. 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 .

  3. Ausführung der Anwendung:

    java -p modules -m de.meinefirma.meineapp

Mit jlink eine ausführbare Distribution inklusive abgespecktem JDK erstellen

  1. 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"):

    Führen Sie das Ergebnis aus:

  2. Sie erhalten:

    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]
    
  3. Beachten Sie:

Resultierende Projektstruktur

  1. 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
    


Jigsaw-Demo mit Service-Provider und -Consumer

Diese Demo zeigt:

Anlage der Verzeichnisstruktur und Erzeugen der Java-Dateien

  1. 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

  2. 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;
    }
    
  3. 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;
    }
    
  4. 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;
    }
    
  5. 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();
    }
    
  6. 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();
       }
    }
    
  7. 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();
       }
    }
    
  8. 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() );
          }
       }
    }
    
  9. 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
    

Aktivieren von Java 10

  1. 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%

  2. Ü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, bauen der "Modular Jars" und Ausführen der Demo

  1. 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 .

  2. 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 .

  3. 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 .

  4. Ausführen der Demo:

    java -p modules -m de.meinefirma.meinconsumer

  5. 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
    

Analyse der Modular Jars mit jdeps und mit describe-module

  1. Analysieren Sie die Dependencies mit jdeps:

    jdeps -s modules/*.jar

    jdeps modules/*.jar

  2. 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

  3. Beachten Sie:



Jigsaw-Demo mit einem "Automatic Module" und mit "Implied Readability"

Diese Demo zeigt:

Anlage der Verzeichnisstruktur und Erzeugen der Java-Dateien

  1. 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

  2. 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.

  3. 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;
    }
    
  4. 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;
    }
    
  5. 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();
       }
    }
    
  6. 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";
       }
    }
    
  7. 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
    

Aktivieren von Java 10

  1. 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%

  2. Kontrolle:

    echo JAVA_HOME=%JAVA_HOME%

    javac -version

    java -version

    In allen drei Fällen muss Java 10 gemeldet werden.

Kompilieren, Bauen und Ausführen

  1. Löschen Sie Ergebnisse vorheriger Versuche:

    cd \MeinWorkspace\JigsawAutoModImpliedReadDemo

    rd /S /Q classes

    del modules\de.meinefirma.*

  2. 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 ..

  3. 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 .

  4. 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 .

  5. 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
    
  6. Beachten Sie:

Analyse der Jar-Libs mit describe-module und mit jdeps

  1. 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

  2. Überprüfen Sie, dass Guava keinen Moduldeskriptor hat:

    jar --describe-module --file=modules/guava-21.0.jar

  3. Analysieren Sie die Dependencies von Guava mit jdeps:

    jdeps modules/guava-21.0.jar



Jigsaw-Demo mit doppelten Klassen und verschiedenen Modularten

Diese Demo soll zum Experiemtieren einladen:

Anlage der Verzeichnisstruktur und Erzeugen der Java-Dateien

  1. 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

  2. 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;
    }
    
  3. 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 );
       }
    }
    
  4. 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
    

Kompilieren und Bauen der sechs Module

  1. Aktivieren Sie Java 10.

  2. 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

  3. 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 .

  4. 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 .

  5. 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]
    

Ausführung auf verschiedene Arten

  1. Führen Sie die Module auf unterschiedliche Art aus, so dass jede der fünf MeinUtil-Klassen ausgeführt wird:

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

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

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

  5. java -p modules -m de.meinefirma.meinv5util

    ---- Version 5, MeinUtil.class aus: module de.meinefirma.meinv5util
    

    (Kein Classpath, nur Modulepath.)

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

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

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



Abhängigkeitsgraph mit Graphviz

  1. 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.

  2. 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:

  3. 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:

  4. Sie erhalten:

    JigsawDuplicateVersions-Graphviz



Jigsaw-Dep-Demo mit Maven

JigsawDepDemo-Graphviz Diese Demo zeigt:

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.

Anlage der Verzeichnisstruktur und Erzeugen der Java-Dateien und Maven-Konfigurationen

  1. Maven Maven muss installiert sein (z.B. in Version 3.6.0).

  2. 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

  3. 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.

  4. 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>
    
  5. 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>
    
  6. 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;
    }
    
  7. 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;
    }
    
  8. 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() );
       }
    }
    
  9. 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();
       }
    }
    
  10. 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
    

Kompilieren, Bauen und Ausführen

  1. Aktivieren Sie Java 10 wie oben beschrieben.

  2. 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

  3. 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]
    
  4. 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.

JUnit-Modultests

  1. 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

  2. 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
    
  3. 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
    ...
    
  4. 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.



Jigsaw-Dep-Demo mit IntelliJ IDEA

IntelliJ-IDEA Im Folgenden soll das obige "Jigsaw-Dep-Demo mit Maven"-Beispiel in die Entwicklungsumgebung JetBrains IntelliJ IDEA geladen werden.

  1. 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.

  2. 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.

  3. Laden Sie das Maven-Projekt in IntelliJ IDEA:

    File | Open... | \MeinWorkspace\JigsawDepDemoMitMaven.

  4. 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.

  5. Ö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.

  6. 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]
    

IntelliJ-IDEA


Jigsaw-Service-Demo mit Maven und IntelliJ IDEA

Diese Demo zeigt:

Diese Demo funktioniert mit JetBrains IntelliJ IDEA ab Version 2017.2.

  1. Voraussetzung ist die obige Jigsaw-Demo mit Service-Provider und -Consumer im JigsawServiceDemo-Verzeichnis.

  2. 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

  3. 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>
    
  4. 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>
    
  5. 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>
    
  6. 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>
    
  7. 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
    

Kompilieren, Bauen und Ausführen

  1. Aktivieren Sie Java 10 wie oben beschrieben.

  2. 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

  3. 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
    

Öffnen des Projekts in IntelliJ IDEA

  1. Laden Sie das Maven-Projekt in IntelliJ IDEA:

    File | Open... | \MeinWorkspace\JigsawServiceDemoMitMaven.

  2. 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.

  3. Öffnen Sie per Strg+N die Anwendungsklasse MeinServiceConsumer, und führen Sie sie aus, z.B. per Strg+Shift+F10.

  4. Bei der Ausführung von MeinServiceConsumer erhalten Sie wieder das gleiche Ergebnis, wie eben gezeigt.


Jigsaw-Dep-Demo mit Eclipse

Eclipse 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:

  1. 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.

  2. 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...".

  3. 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.

  4. 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.

  5. 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
    ...

  6. 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

  7. Starten Sie Eclipse und überprüfen Sie die geänderte Eclipse-Konfiguration:

    Help | About Eclipse | Installation Details | Configuration.

  8. 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?

  9. Importieren Sie das obige "Jigsaw-Dep-Demo mit Maven"-Beispiel:

    File | Import... | > Maven | Existing Maven Projects | Next > | Root Directory: \MeinWorkspace\JigsawDepDemoMitMaven | Finish.

  10. 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?

  11. 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.

  12. 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.

  13. Passen Sie die Startkonfiguration an unter:
    Run | Run Configurations...

  14. Öffnen Sie per Strg+Shift+T die Hauptapplikationsklasse MeineApp, und führen Sie sie aus, z.B. per Strg+F11.

  15. 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.

Eclipse




Weitere Themen: andere TechDocs | OSGi | Docker
© 2017 Torsten Horn, Aachen