Skip to content

embarced/jupdate-training

Repository files navigation

Java Modulsystem

Hier sind die Übungsbeschreibungen zu den Modulübungen.

Die Beschreibungen zu den anderen Übungen sind in den weiligen Unterordnern.

Automatic Modules - Teil 1

Hinweis:

Im Verlauf der nachfolgenden Übungen wird Schritt für Schritt eine modularisierte Beispielanwendung entwickelt. In den Beschreibungen werden verschiedene Packages genannt, welche mit „jupdate“ beginnen. DEr Teil „jupdate“ darf gerne durch andere Basis-Package-Namen ersetzen (z.B. „jupdate.main“ zu „com.example.main“). Damit die Übungen im späteren Verlauf korrekt funktionieren, müssen die Packages aber anhand ihres Suffix klar unterscheidbar sein. Die einzelnen Zwischenschritte können für späteres Selbststudium gesichert werden.

Aufgabenstellung:

  1. Erstellt in Eurer IDE ein neues Java 9 Projekt

  2. In Eclipse nennen wir dieses Projekt „main“

  3. In IntelliJ IDEA erstellen wir ein Modul „main“

  4. Wir erstellen eine Klasse „StringUtil“ im package „jupdate.util“, welche eine statische Methode „flip“ zum Umkehren von Strings anbietet

  5. Wir erstellen Sie eine neue Klasse „Main“ im Package „jupdate.main“ mit einer „main“-Methode

  6. Wir rufen in der „main“-Methode die Methode „flip“ in StringUtil auf und geben sie mit Hilfe von dieser auf der Konsole aus

Klasse StringUtil
package jupdate.util;

public final class StringUtil {

	private StringUtil() {
	}

	public static String flip(String s) {
		final StringBuffer flippedString = new StringBuffer();

		for (int i = (s.length() - 1); i >= 0; i--) {
			flippedString.append(s.charAt(i));
		}

		return flippedString.toString();
	}
}
Klasse StringUtil
package jupdate.main;

import jupdate.util.StringUtil;

public class Main {

	public static void main(String[] args) {
		System.out.println(StringUtil.flip("!dlroW olleH"));
	}
}

Automatic Modules - Teil 2

Aufgabenstellung:

  1. Kompiliere die Quelltexte zu *.class Dateien. Nutzen Sie hierfür entweder Ihre IDE oder die Kommandozeile

  2. Erzeuge aus Ihren *.class Dateien eine *.jar Datei

  3. Starte die Anwendung und lade die *.jar Datei über den Classpath

  4. Ermittle mit Hilfe des Kommandos „jar“ den generierten Namen der *.jar Datei

  5. Starte die Anwendung und lade die *.jar Datei über den Module Path

Shell
mkdir out
mkdir lib
javac -d out src/jupdate/main/Main.java src/jupdate/util/StringUtil.java
jar --create --file lib/main.jar -C out .
java -cp lib/main.jar jupdate.main.Main
jar -d --file lib/main.jar
java -p lib/main.jar -m main/jupdate.main.Main

Ergebnis:

Einfache *.jar-Dateien können ohne Erweiterungen für das Modulsystem auch als Module eingesetzt werden. Dies ermöglicht eine einfache Nutzung von Anwendungsbestandteilen sowohl in einer modularisierten, als auch in einer nicht modularisierten Umgebung.

Automatic Modules - Teil 3

Aufgabenstellung:

  1. Erstelle im Projekt eine Datei „MANIFEST.txt“, in welcher das Attribut „Automatic-Module-Name” auf den Werte “jupdate.main” gesetzt ist

  2. Erzeuge wie zuvor eine *.jar Datei, wobei allerdings der Parameter “-m MANIFEST.txt” an das “jar” Kommando mitgegeben wird

Shell
jar --create --file lib/main.jar -m MANIFEST.txt -C out .
  1. Prüfe, dass der in der MANIFEST.txt eingestellte Modulname verwendet wird

  2. Starte die *.jar Datei und verwende den definierten Modulnamen „jupdate.main“

Ergebnis:

Durch die Spezifikation des Modulnamens in der MANIFEST.MF einer *.jar Datei können wir erreichen, dass ein Automatic Module einen definierten Namen erhält, welcher nicht vom Namen der Datei abgeleitet wird.

Moduldeskriptor

Aufgabenstellung:

  1. Lege im Quellverzeichnis eine Datei „module-info.java“ an und deklariere dort ein Modul namens „jupdate.main“.

Moduldeskriptor module-info.java
module jupdate.main {
}
  1. Kompiliere die Quelldateien inklusive „module-info.java“ und erstelle daraus eine *.jar Datei.

  2. Starte die Anwendung mit der *.jar Datei auf dem Module Path.

  3. Vergewissere Dich, dass der Modulname unabhängig vom Dateinamen wie im Moduldeskriptor deklariert erkannt wird.

Ergebnis:

Mit Hilfe des Moduldeskriptors „module-info.java“ kann ein Modulname deklariert werden, welcher unabhängig vom Namensschema der *.jar Dateien ein stabiles Verhalten zur Laufzeit garantiert.

Abhängigkeiten zu System-Modulen

Aufgabenstellung:

  1. Versuche in der „main“ Methode auf die Java Logging API (Package java.util.logging) zuzugreifen

  2. Deklariere im Modul „jupdate.main“ eine Abhängigkeit auf das System-Modul „java.logging“

Moduldeskriptor module-info.java
module jupdate.main {
    requires java.logging;
}
  1. Nutze die Java Logging API für die Ausgabe in der Anwendung (statt System.out.println)

  2. Kompiliere und starte die Anwendung und vergewissere Dich, dass die Ausgabe über die Java Logging API funktioniert

Ergebnis:

Ohne Anpassung des Moduldeskriptors „module.info.java“ verwehrt Die IDE sowie der Java Compiler den Zugriff auf Packages, welche sich nicht im implizit referenzierten Modul „java.base“ befinden. Nach Deklaration der notwendigen Abhängigkeit, gelingt der Zugriff, wie wir es von nicht-modularen Anwendungen gewohnt sind.

Versionierung von Modulen

Aufgabenstellung:

  1. Werfe testweise eine RuntimeException in der „main“ Methode

  2. Erstelle die *.jar Datei des Moduls neu und spezifiziere hierbei eine Version für das Modul

  3. Inspiziere das erzeugte Modul und stellen sicher, dass die vergebene Version wieder auftaucht

  4. Starte die Anwendung und betrachte den StackTrace

Ergebnis:

Wird eine Version bei der Erstellung einer *.jar Datei spezifiziert, so findet sich dieses Metadatum in verschiedenen Ausgaben wieder und kann zum Beispiel zur Fehlersuche verwendet werden.

Modulare Anwendung

Aufgabenstellung:

  1. Erweitere die Projektstruktur für ein neues Modul

    1. In Eclipse: lege ein neues Java-Projekt „util“ an

    2. In IntelliJ IDEA: Erstelle ein neues Modul namens „util“ im Projekt und richte ein Quellverzeichnis „src“ ein

  2. Erstelle die Datei „module-info.java“ im neuen Quellverzeichnis und deklarieren dort das Modul „jupdate.util“

module-info.java in main
module jupdate.main {
    requires java.logging;
    requires jupdate.util;
}
  1. Lege außerdem das Package „jupdate.util“ an und verschiebe die Klasse „StringUtil“ dorthin

  2. Exportiere das Package „jupdate.util“ im neuen Modul und definiere eine Abhängigkeit vom bestehenden Modul „jupdate.main“ auf „jupdate.util“

Moduldeskriptor module-info.java
module jupdate.util {
	exports jupdate.util;
}
  1. Zur Korrektur von Fehlern konfigurieren Sie Ihre IDE:

    1. In Eclipse: Lege im Java Build Path eine Projekt-Abhängigkeit vom Projekt „main“ auf das Projekt „util“ für den Module Path an

    2. In IntelliJ IDEA: Lege in den Moduleinstellungen des Moduls „main“ eine Abhängigkeit auf das Modul „util“ an

  2. Starte die Anwendung

Ergebnis: Im Verlauf der Übung teilen wir unser Modul auf und extrahieren die Klasse StringUtil in ein eigenes Modul. Leider müssen die Abhängigkeiten zusätzlich zur Deklaration im Moduldeskriptor redundant in der IDE konfiguriert werden, um einen lauffähigen Zustand zu erhalten.

Nicht-modulare Tests und eine modulare Anwendung - Teil 1

Hinweis:

Weder Eclipse noch IntelliJ IDEA unterstützen auf einfache Art den Mischbetrieb von modularen und nicht modularen Anwendungsbestandteilen. Im einfachsten Fall führt dies dazu, dass alle Module zum Classpath hinzugefügt werden. Dies funktioniert für unseren einfachen Fall, kann in komplexeren Szenarien allerdings zu Problemen führen.

Aufgabenstellung:

  1. In Eclipse:

    1. lege ein neues Java-Projekt „test“ an

    2. Lege im Java Build Path eine Projekt-Abhängigkeit vom Projekt „test“ auf das Projekt „util“ für den Classpath an

    3. Kopiere all Testabhängigkeiten (Junit 5) in ein Verzeichnis „test-lib“ des Projektes „test“ und binde alle *.jar Dateien im Java Build Path per Classpath ein

  2. In IntelliJ IDEA:

    1. Erstelle ein neues Modul namens „test“ im Projekt und richte ein Quellverzeichnis „src“ ein

    2. Lege in den Moduleinstellungen des Moduls „test“ eine Abhängigkeit auf das Modul „util“ an

    3. Kopiere alle Testabhängigkeiten (Junit 5) in ein Verzeichnis „test-lib“ des Moduls „test“und binden Sie alle *.jar Dateien in den Moduleinstellungen als Abhängigkeiten ein

Nicht-modulare Tests und eine modulare Anwendung – Teil 2

Aufgabenstellung:

  1. Implementiere im neuen Projekt/Modul eine Testklasse „jupdate.util.test.StringUtilTest“, welche die Methode „StringUtil.flip“ testet

Testklasse StringUtilTest
public class StringUtilTest {
    @Test
    public void testHelloWorld() {
        Assertions.assertEquals("dlroW olleH", StringUtil.flip("Hello World"));
    }
}
  1. Kompiliere den Test, wobei sich das Modul „jupdate.util“ auf dem Module Path befindet und die Abhängigkeit „junit-jupiter-api” per Classpath eingebunden ist.

  2. Übergeben hierbei außerdem den Parameter “--add-modules ALL-MODULE-PATH“, um das “StringUtil” für den Test sichtbar zu machen.

  3. Führe den Test aus, wobei sich das Modul „jupdate.util“ auf dem Module Path befindet und die Junit *.jars sowie die Testklasse auf dem Klassenpfad eingebunden sind.

Shell
$ javac --add-modules ALL-MODULE-PATH -p ../util/lib/util.jar -d out-test -cp lib-test/junit-jupiter-api-5.1.0.jar src/jupdate/util/test/StringUtilTest.java

$ java --add-modules ALL-MODULE-PATH -p ../util/lib/util.jar -cp "out-test;lib-test/*" org.junit.platform.console.ConsoleLauncher -e junit-jupiter -p jupdate.util.test
  1. Übergebe hierbei wieder den Parameter “--add-modules ALL-MODULE-PATH“, um das “StringUtil” für den Test sichtbar zu machen.

Ergebnis:

Wir haben gesehen, dass sich JUnit Tests für modularisierte Awendungen umsetzen lassen, hierbei allerdings das Modulsystem umgangen wird. Dieser Trick ist für alle Junit Versionen bis einschließlich 5.0.x zwingend notwendig. Dieses Vorgehen ist außerdem mit anderen Testwerkzeugen notwendig, welche noch nicht für die Benutzung im Kontext von Java Modulen angepasst wurden.

ServiceLoader in der modularen Welt - Teil 1

Aufgabenstellung:

  1. In Eclipse

    1. Lege ein neues Java-Projekt „api“ an

    2. Füge das Projekt „api“ zum Module Path der Projekte „main“ und „util“ hinzu und entfernen Sie „util“ vom Module Path des Projekts „main“

  2. In IntelliJ IDEA

    1. Erstellen Sie ein neues Modul namens „api“ in ihrem Projekt und richten Sie ein Quellverzeichnis „src“ ein

    2. Fügen Sie eine Abhängigkeit auf das Module „api“ zu den Modulen „main“ und „util“ hinzu und entfernen sie die Abhängigkeit von „main“ auf „util“

module-info.java
module jupdate.api {
	exports jupdate.api;
}
module-info.java
module jupdate.util {
	requires jupdate.api;
	provides jupdate.api.StringUtil with jupdate.util.StringUtilImpl;
}
module-info.java Dateien
module jupdate.main {
	requires jupdate.api;
	uses jupdate.api.StringUtil;
}
  1. Erstellen Sie die Datei „module-info.java“ in Ihrem neuen Quellverzeichnis und deklarieren Sie dort das Modul „jupdate.api“

  2. Erstellen Sie im Modul „jupdate.api“ das Interface „jupdate.api.StringUtil“, welches die Methode „flip“ aus den vorigen Übungen definiert

  3. Exportieren Sie das Package „jupdate.api“

  4. Definieren Sie Abhängigkeiten von den Modulen „jupdate.main“ und „jupdate.util“ auf „jupdate.api“ und entfernen Sie die Anhängigkeit von „jupdate.main“ auf „jupdate.util“

ServiceLoader in der modularen Welt - Teil 2

Aufgabenstellung:

  1. Benenne die Klasse „jupdate.util.StringUtil“ in „jupdate.util.StringUtilImpl“ um und lassen Sie diese Das interface „jupdate.api.StringUtil“ implementieren (hierfür darf die Methode nicht mehr als „abstract“ definiert sein)

Klasse StringUtilImpl
public final class StringUtilImpl implements StringUtil {
    public String flip(String s) {
        [...]
    }
}
  1. Exportiere die Klasse „jupdate.util.StringUtilImpl“ als Service Implementierung von „jupdate.api.StringUtil“

module-info.java
module jupdate.util {
   requires jupdate.api;
   provides jupdate.api.StringUtil with jupdate.util.StringUtilImpl;
}
  1. Ändere in der „main“ Methode den statischen Zugriff auf die Methode „flip“ in einen Aufruf auf einen per „ServiceLoader“ importierten Service des Typs „jupdate.api.StringUtil“

  2. Was fällt beim Starten der Anwendung auf?

Aufruf Serviceloader
ServiceLoader<StringUtil> loader = ServiceLoader.load(StringUtil.class);
Optional<StringUtil> probablyStringService = loader.findFirst();
probablyStringService.ifPresentOrElse(stringUtil -> logger.info(stringUtil.flip("!dlroW olleH")),
        () -> System.err.println("Service fehlt"));

Ergebnis:

Durch die lose Kopplung unserer Module gehört die Service-Implementierung nicht zur Menge der Module, welche vom „main“ Modul referenziert werden. Damit fügt unsere IDE das Module beim Start auch nicht zum Module Path hinzu. Es kann somit zur Laufzeit keine Instanz für das Service Interface ermittelt werden.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages