Hier sind die Übungsbeschreibungen zu den Modulübungen.
Die Beschreibungen zu den anderen Übungen sind in den weiligen Unterordnern.
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.
-
Erstellt in Eurer IDE ein neues Java 9 Projekt
-
In Eclipse nennen wir dieses Projekt „main“
-
In IntelliJ IDEA erstellen wir ein Modul „main“
-
Wir erstellen eine Klasse „StringUtil“ im package „jupdate.util“, welche eine statische Methode „flip“ zum Umkehren von Strings anbietet
-
Wir erstellen Sie eine neue Klasse „Main“ im Package „jupdate.main“ mit einer „main“-Methode
-
Wir rufen in der „main“-Methode die Methode „flip“ in StringUtil auf und geben sie mit Hilfe von dieser auf der Konsole aus
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();
}
}
package jupdate.main;
import jupdate.util.StringUtil;
public class Main {
public static void main(String[] args) {
System.out.println(StringUtil.flip("!dlroW olleH"));
}
}
-
Kompiliere die Quelltexte zu *.class Dateien. Nutzen Sie hierfür entweder Ihre IDE oder die Kommandozeile
-
Erzeuge aus Ihren *.class Dateien eine *.jar Datei
-
Starte die Anwendung und lade die *.jar Datei über den Classpath
-
Ermittle mit Hilfe des Kommandos „jar“ den generierten Namen der *.jar Datei
-
Starte die Anwendung und lade die *.jar Datei über den Module Path
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
-
Erstelle im Projekt eine Datei „MANIFEST.txt“, in welcher das Attribut „Automatic-Module-Name” auf den Werte “jupdate.main” gesetzt ist
-
Erzeuge wie zuvor eine *.jar Datei, wobei allerdings der Parameter “-m MANIFEST.txt” an das “jar” Kommando mitgegeben wird
jar --create --file lib/main.jar -m MANIFEST.txt -C out .
-
Prüfe, dass der in der MANIFEST.txt eingestellte Modulname verwendet wird
-
Starte die *.jar Datei und verwende den definierten Modulnamen „jupdate.main“
-
Lege im Quellverzeichnis eine Datei „module-info.java“ an und deklariere dort ein Modul namens „jupdate.main“.
module jupdate.main {
}
-
Kompiliere die Quelldateien inklusive „module-info.java“ und erstelle daraus eine *.jar Datei.
-
Starte die Anwendung mit der *.jar Datei auf dem Module Path.
-
Vergewissere Dich, dass der Modulname unabhängig vom Dateinamen wie im Moduldeskriptor deklariert erkannt wird.
-
Versuche in der „main“ Methode auf die Java Logging API (Package java.util.logging) zuzugreifen
-
Deklariere im Modul „jupdate.main“ eine Abhängigkeit auf das System-Modul „java.logging“
module jupdate.main {
requires java.logging;
}
-
Nutze die Java Logging API für die Ausgabe in der Anwendung (statt System.out.println)
-
Kompiliere und starte die Anwendung und vergewissere Dich, dass die Ausgabe über die Java Logging API funktioniert
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.
-
Werfe testweise eine RuntimeException in der „main“ Methode
-
Erstelle die *.jar Datei des Moduls neu und spezifiziere hierbei eine Version für das Modul
-
Inspiziere das erzeugte Modul und stellen sicher, dass die vergebene Version wieder auftaucht
-
Starte die Anwendung und betrachte den StackTrace
-
Erweitere die Projektstruktur für ein neues Modul
-
In Eclipse: lege ein neues Java-Projekt „util“ an
-
In IntelliJ IDEA: Erstelle ein neues Modul namens „util“ im Projekt und richte ein Quellverzeichnis „src“ ein
-
-
Erstelle die Datei „module-info.java“ im neuen Quellverzeichnis und deklarieren dort das Modul „jupdate.util“
module jupdate.main {
requires java.logging;
requires jupdate.util;
}
-
Lege außerdem das Package „jupdate.util“ an und verschiebe die Klasse „StringUtil“ dorthin
-
Exportiere das Package „jupdate.util“ im neuen Modul und definiere eine Abhängigkeit vom bestehenden Modul „jupdate.main“ auf „jupdate.util“
module jupdate.util {
exports jupdate.util;
}
-
Zur Korrektur von Fehlern konfigurieren Sie Ihre IDE:
-
In Eclipse: Lege im Java Build Path eine Projekt-Abhängigkeit vom Projekt „main“ auf das Projekt „util“ für den Module Path an
-
In IntelliJ IDEA: Lege in den Moduleinstellungen des Moduls „main“ eine Abhängigkeit auf das Modul „util“ an
-
-
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.
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.
-
In Eclipse:
-
lege ein neues Java-Projekt „test“ an
-
Lege im Java Build Path eine Projekt-Abhängigkeit vom Projekt „test“ auf das Projekt „util“ für den Classpath an
-
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
-
-
In IntelliJ IDEA:
-
Erstelle ein neues Modul namens „test“ im Projekt und richte ein Quellverzeichnis „src“ ein
-
Lege in den Moduleinstellungen des Moduls „test“ eine Abhängigkeit auf das Modul „util“ an
-
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
-
-
Implementiere im neuen Projekt/Modul eine Testklasse „jupdate.util.test.StringUtilTest“, welche die Methode „StringUtil.flip“ testet
public class StringUtilTest {
@Test
public void testHelloWorld() {
Assertions.assertEquals("dlroW olleH", StringUtil.flip("Hello World"));
}
}
-
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.
-
Übergeben hierbei außerdem den Parameter “--add-modules ALL-MODULE-PATH“, um das “StringUtil” für den Test sichtbar zu machen.
-
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.
$ 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
-
Übergebe hierbei wieder den Parameter “--add-modules ALL-MODULE-PATH“, um das “StringUtil” für den Test sichtbar zu machen.
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.
-
In Eclipse
-
Lege ein neues Java-Projekt „api“ an
-
Füge das Projekt „api“ zum Module Path der Projekte „main“ und „util“ hinzu und entfernen Sie „util“ vom Module Path des Projekts „main“
-
-
In IntelliJ IDEA
-
Erstellen Sie ein neues Modul namens „api“ in ihrem Projekt und richten Sie ein Quellverzeichnis „src“ ein
-
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 jupdate.api {
exports jupdate.api;
}
module jupdate.util {
requires jupdate.api;
provides jupdate.api.StringUtil with jupdate.util.StringUtilImpl;
}
module jupdate.main {
requires jupdate.api;
uses jupdate.api.StringUtil;
}
-
Erstellen Sie die Datei „module-info.java“ in Ihrem neuen Quellverzeichnis und deklarieren Sie dort das Modul „jupdate.api“
-
Erstellen Sie im Modul „jupdate.api“ das Interface „jupdate.api.StringUtil“, welches die Methode „flip“ aus den vorigen Übungen definiert
-
Exportieren Sie das Package „jupdate.api“
-
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“
-
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)
public final class StringUtilImpl implements StringUtil {
public String flip(String s) {
[...]
}
}
-
Exportiere die Klasse „jupdate.util.StringUtilImpl“ als Service Implementierung von „jupdate.api.StringUtil“
module jupdate.util {
requires jupdate.api;
provides jupdate.api.StringUtil with jupdate.util.StringUtilImpl;
}
-
Ä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“
-
Was fällt beim Starten der Anwendung auf?
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"));
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.