In diesem Tutorial erforschen wir die Entwicklung von modernen Java-Programmen. Um die Grundlagen zu verstehen, konzentrieren wir uns ausschließlich auf Tools, die von der JDK mitgeliefert werden.
Selbstredend ist uns bewußt, daß professionelle Java-Entwickler in der Praxis eine Reihe von komplexen und höherleveligen Tools nutzen (Build Tools, IDEs und Frameworks). Allerdings fußen all diese Tools auf einem Fundament: der JDK.
Deshalb lohnt es sich ein solides Verständnis der JDK zu erlangen.
Es gibt keine Regeln bezüglich Hilfsmittel und/oder Kooperation. Gerne kann eine KI oder Kolleg:innen zu Rate gezogen werden!
Offensichtlich wird dieses Projekt in GIT getrackt. Damit du deinen Code sichern und zeigen kannst, erstelle einen eigenen Branch und nutze diesen während des Kurses, um deinen Fortschritt zu synchronisieren.
# branch erstellen
git branch NAME
# zum branch wechseln
git switch NAME
# unversionierte Änderungen zu git hinzufügen
git add .
# lokalen commit erzeugen mit allen lokalen Änderungen
git commit --all
# branch hochladen
git push origin NAME
# branch herunterladen
git pull origin NAMEDer normale GIT-Workflow ist: PULL -> COMMIT,COMMIT,COMMIT,... -> PUSH. Nutze ihn während des Kurses, von der Konsole aus!
Quellen:
Wir entwickeln eine Webserver-Applikation, die eine CSV-Datei entgegen nimmt und diese als HTML-Tabelle rendert und serviert.
- JDK 24
- Ein simpler Editor
- Terminal
Für diese Übung verzichten wir auf die Bequemlichkeiten einer IDE oder eines Build Tools, um ein besseres Verständnis dafür zu entwickeln, was diese Tools eigentlich für uns leisten.
Generell kann unterschieden werden, zwischen der Entwicklung von Applikationen und Bibliotheken. Bibliotheken werden von anderen Bibliotheken und Applikationen konsumiert. Applikationen werden an Endkunden vertrieben.
Da die Anwendungsfälle dieser 2 Programmtypen derart anders ist, unterscheiden sich auch deren Vertriebsformen radikal. Bibliotheken werden in Form einer JAR gebündelt, damit diese von anderen Programmen konsumiert werden können.
Applikationen hingegen werden typischerweise als ausführbares Programm (aka executable) oder Installer ausgeliefert.
Für Java-Programmierer heisst dies, es gibt mindestens folgende Arbeitsphasen:
- Code & Debug: Code wird geschrieben, kompiliert und dann test-weise ausgeführt
- Library Release: Code wird in einer JAR gebündelt und dann verteilt
- App Release: Code wird kompiliert und dann einem Image (Exe oder Installer) gebündelt
In der Praxis gibt es noch eine sehr wichtige Komponente, die alle obigen Phasen betrifft: Testing. Aber da es in der JDK kein Tool dafür gibt, ignorieren wir das Thema erst mal.
In jeder Phase kommen unterschiedliche Tools des JDKs zum Einsatz.
- Code & Debug
- Library Release
- App Release
Es wird von dir erwartet, daß du die obigen Spezifikationen zu rate ziehst, um die folgenden Aufgaben zu lösen. Allerdings sind in diesem Kurs alle Hilfsmittel erlaubt. Egal, ob AI, Pair Programming oder StackOverflow, nutze alle Mittle, die dich beim Lernen unterstützen.
Um die Features der JDK zu erforschen, wird diese kleine Demo-Applikation auf eine etwas übertrieben komplizierte Art und Weise gebaut. Allerdings wird die Komplexität kein Level erreichen, die uns ablenken wird, vom eigentlichen Inhalt.
Die Applikation soll aus 2 Modulen bestehen:
server: Der Webserver. Konsumiert lib als JAR.
cli: Das Kommandozeilenprogramm. Konsumiert lib als JAR.
lib: Die Bibliothek. Enthält die Funktionalität, CSV-Dateien zu HTML-Tabellen zu konvertieren.
Die Aufteilung der Applikation in diese Module wird einige Java Features demonstrieren und Gelegenheit bieten, gängige Muster zu nutzen.
Es gibt viele Aspekte eines Software-Projekts, die in diesem Kurs absichtlich undefiniert gelassen wurden: zum Beispiel Projektlayout. In solchen Fällen ist die eigene Kreativität und Gusto gefragt!
Als statische Code Analyse sollen die folgenden Werkzeuge der JDK eingesetzt werden:
Das JavaDoc soll im Markdown-Format verfaßt werden.
Bisher hat dieses Dokument eine sehr allgemeine Einleitung zu diesem Lernprojekt geliefert. Nun folgt eine Serie von kleinschrittigen Aufgaben, die wir im laufe mehrerer Sitzung behandeln können.
Bedenke die Restriktionen, die du in diesem Kurs einhalten sollst:
- ein simpler Editor
- Konsole
- JDK-Tools only
- Überfliege den Java Shell User’s Guide und halte diesen als Referenz bereit.
- Nutze JShell, um eine Funktion zu entwickeln, die einen CSV-String zu einer tabellarischen Datenstruktur parst:
String[][] parseCsv( String input ) throws IllegalArgumentException;. - Sobald du mit der Implementierung zufrieden bist, speichere die Funktion als JShell-Script.
- Erzeuge aus dem Inhalt des vorherigen JShell-Scripts ein vollwertiges Java-Modul.
- Dieses Modul soll die 'lib'-Bibliothek in der Architektur werden.
- Nutze
/open TOOLINGin JShell, um das Modul mitjavaczu bauen - Sobald du mit den Parametern zum Kompilieren der
libzufrieden bist, speichere diese in einer Text-Datei, und teste ob diese als Command-Line Argument File genutzt werden kann.
- Erzeuge in JShell leere Typen aller Art:
interface I {} class C {} enum E {} record R() {} - Inspiziere das (visualisierte) Bytecode-Format dieser Typen mit
javap.Nutze The Java® Virtual Machine Specification als Nachschlagewerk./open TOOLING javap(I.class) javap(C.class) javap(E.class) javap(R.class) - Was ist der Unterschied zwischen
<init>und<clinit>? Warum existieren diese in manchen Typen und anderen nicht? - Vergleiche die folgenden Klassen in
javap:Warum ist deren Bytecode derart dramatisch anders, obwohl diese semantisch "das gleiche" tun?class A { A() { final var c = call_me(); System.out.println(c); } private String call_me() { return "maybe" + 7;} } class B { private final Supplier<String> call_me = () -> "maybe" + 7; B() { final var c = call_me.get(); System.out.println(c); } }
Es ist an der Zeit die Bibliothek in lib zu vervollständigen und in ein Format
zu packen, das andere konsumieren können.
Das Format ist eine signierte JAR-Datei und andere in diesem Kontext werden
server und cli sein.
In einem professionellen Kontext würde die JAR auf unterschiedlichste Weisen an
Dritte verteilt werden:
- Download auf einer Webseite
- Teil eines Repositories (zum Beispiel Maven Central)
- Endpunkt eines Web-Services
- Erweitere das
lib-Modul um Funktion, von denen du glaubst sie seien notwendig fürcliundserverDas Ziel ist: Eine CSV-Datei als HTML Tabelle darzustellen
- Packe das Modul in eine JAR mit
jar. - Signiere die JAR mit
jarsignerund einem persönlichen Schlüssel
Besonders im Falle von Bibliotheken, ist eine extensive Dokumentation der öffentlichen API notwendig. Zu diesem Zwecke kommt in Java JavaDoc zum Einsatz.
JavaDoc wird als doc comments im Quellcode verfaßt, und dann mit javadoc zu
einem HTML-Dokument generiert.
Klassisches JavaDoc nutzt eine Untermenge von HTML5 als Markup-Sprache mit Java-spezifischen Zusätzen, namens tags.
/**
* Diese Methode tut <em>es</em>!
* <p>
* <b>Das Ding</b> wird ohne Umschweife erledigt. <i>Fehlerfrei!</i>
* @param input Ein Input-Parameter!
* @returns Ein Rückgabewert!
*/
public int doTheThing( String input ) { ... }Modernes JavaDoc wird in Markdown verfaßt:
/// Diese Methode tut __es__!
///
/// **Das Ding** wird ohne Umschweife erledigt. _Fehlerfrei!_
/// @param input Ein Input-Parameter!
/// @returns Ein Rückgabewert!
public int doTheThing( String input ) { ... }- Verfasse ausführliche Dokumentation im Markdown-Format für alle öffentlichen
Methoden in
lib. - Generiere aus dem Modul
libJavaDoc-HTML mitjavadoc.
Als Beispiel einer Applikation bauen wir nun ein Modul cli, das ein Kommandozeilentool
sein soll, das die Funktionalität der lib-Bibliothek hinter einem Command
Line Interface (CLI) anbietet.
Es folgt eine fiktive Manpage des Kommandozeilentools, das du nutzen kannst, um das Interface zu implementieren.
cli - convert CSV files to HTML tables
cli [options] input_fileThe cli command reads a CSV (Comma-Separated Values) file and outputs an HTML table representation of its contents.
-
-h, --helpDisplay this help message and exit. -
-o, --outputSpecify the output HTML file. If not provided, output is written to standard output. -
-t, --titleSet the title of the HTML document. Default is "CSV to HTML Table". -
-s, --stylesheetLink to an external CSS stylesheet for styling the table.
Convert data.csv to an HTML table and write to standard output:
cli data.csvConvert data.csv to table.html with a custom title:
cli -o table.html -t "My Data Table" data.csvConvert data.csv to table.html with an external stylesheet:
cli -o table.html -s styles.css data.csv- Nutze
javac, um das Modulcliund all seine Abhängigkeiten zu bauen - Validiere die Signatur der
lib-JAR mitjarsigner - Nutze
java, um das Modul zu starten und zu testen
Nun, da wir 2 fertige Module haben
-
lib, für Entwickler mit JavaDoc -
clifür Power-User ist es an der Zeit, das Modulcliso zu bauen und zu verpacken, daß wir es an unsere Kunden liefern können. Da für Kommandozeilentools kein Installer nötig ist, da Power-User fähig sind diese eigenständig in ihr System zu integrieren, liefern wir das Modul als Image (EXE) aus. -
Kompiliere das Modul
cliund seine Abhängigkeiten mitjavac -
Verlinke das Modul
climit seinen JDK-Modul-Abhängigkeiten und baue eine eigene Java Runtime, die das Modulcliintegriert mitjlink -
Nutze
jpackageund die eigene Runtime als Image (EXE) zu Bündeln
Mit lib haben wir Entwickler bedient.
Mit cli haben wir unsere Power-User glücklich gemacht.
Nun ist es an der Zeit die unglaublichen Fähigkeiten von lib auf den freien Markt zu bringen und normalen Menschen zu präsentieren!
Erschaffe das Modul server.
Es soll eine Abhängigkeit auf lib haben, und ähnlich wie cli, dessen Funktionen nach außen präsentieren.
server soll einen einfachen HTTP1.1-Webserver starten und eine HTML-Seite liefern,
die die CSV-Daten in Form einer schönen HTML-Tabelle anzeigt.
Entscheide selbst auf welche Art und Weise Menschen in der Lage sein sollen CSV-Daten an den Server zu senden. Dateidialog? Kommandozeilenparameter? Ordner mit CSV-Dateien?
- Baue das Modul
servermitjavac - Teste das Modul mit
java
Das Modul server hat nun eine Komplexität erreicht, die repräsentativ für echte
Applikationen ist. Wir haben Abhängigkeiten, einen Spin-Loop und ein komplexes Protokoll.
Deswegen ist es an der Zeit sich mit dem Thema "Debugging" zu beschäftigen. Im speziellen geht es hier, um das Tool-gestützte Debugging, denn daß sogenannte "printf"-Debugging ist eine Kunstform für sich, die immer eingesetzt werden kann.
Tool-basiertes Debugging, also vor allem mit einem Debugger, lohnt sich immer dann, wenn die Applikation eine gewisse Komplexität erreicht.
Allerdings gibt es neben dem Debugger noch andere Tools, die helfen eine laufende Applikation zu verstehen.
- Starte das Modul
servermitjava - Finde die PID des Prozesses mit
jps - Inspiziere die Ergebnisse von
jinfoundjstack
- Starte das Modul
servermitjava - Starte
jconsoleund selektiere den Prozess - Inspiziere die Informationen, die
jconsoleanzeigt
- Starte das Modul
servermitjava - Finde die PID des Prozesses mit
jps - Starte den Debugger
jdbmit der PID - Setze einen Breakpoint beim Rendern der Tabelle und lade die Seite neu
- Inspiziere den Call Stack in
jdb
Alle Tools, die du oben erforscht hast, haben eine zusätzliche Superkraft: sie funktionieren über Netzwerkgrenzen hinweg (remote), wenn richtig konfiguriert. Welche Use-Cases kannst du dir vorstellen, die diese Kräfte ermöglichen? In diesem Beispiel, aber auch zum Beispiel in einer Client-Server-Architektur?
Wir kommen zum Finale dieses Projekts. Wir haben fast alle Phasen eines typischen Produkt-Lebenszykluses (stark vereinfacht) in Java durchgespielt, und eine tolle Webapplikation entwickelt.
Nun fehlt nur noch ein Installer, den unserer Enterprise-Kunden nutzen können, um die Webseite in ihrem internen Netzwerk zu veröffentlichen.
- Baue das Modul
servermitjavac - Linke das Modul mit
jlink - Baue einen Installer mit
jpackage - Setze alle nötigen Metadaten des Installers, damit das Projekt professionell aussieht
- Verschicke den Installer an einen Menschen, für den ultimativen Qualitätstest!
okr