v.3.12 Ein Puzzlespiel Einführung in das Modulsystem Jigsaw in Java 9 Java User Group Darmstadt 09. November 2017 MARTIN LEHMANN, DR. KRISTINE SCHAAL Java User Group Darmstadt, 9.11.2017
9 kommt das lange angekündigte Modulsystem Jigsaw. Jigsaw ist eine grundlegende Strukturänderung von Java-Plattform und -Sprache, mit deren Auswirkungen man sich möglichst früh beschäftigen sollte. In diesem Vortrag erläutern wir Grundlagen von Jigsaw. Wir zeigen Motive und Ziele für die Einführung eines Modulsystems. Wir geben eine Rundum-Einführung in Jigsaw zu folgenden Fragen: Warum braucht Java überhaupt ein Modulsystem? Wie profitieren Entwickler davon? Was ist ein Modul und wie definiere ich Module und Abhängigkeiten? Welche Sichtbarkeiten gibt es zwischen Modulen? Module sind sowohl Compiler-Erweiterung als auch Teil des Laufzeitsystems - wie wirkt sich das aus? Wie vertragen sich Module mit generischen Ansätzen wie Reflection oder Callbacks, auf denen eine Reihe bekannter Frameworks basieren? Welche anderen Konstrukte gibt es (Beispiel: Services, Layer)? Wie weit sind Entwicklertools wie IDEs und für Build-/ Dependency-Management auf Jigsaw eingestellt? Wie gestaltet sich die Migration alter Java-Anwendungen? Falls noch Zeit bleibt: Kurzüberblick über andere (Non-Jigsaw-)Themen von Java9. Abstract Java User Group Darmstadt, November 2017
Grammes, Accso - Accelerated Solutions GmbH Softwarearchitekt Dr. Rüdiger Grammes ist seit November 2011 als Principal bei der Accso – Accelerated Solutions GmbH und dort als Softwarearchitekt in verschiedenen Projekten unterwegs. [email protected] www.xing.com/profile/Ruediger_Grammes Martin Lehmann, Accso - Accelerated Solutions GmbH Cheftechnologe Martin Lehmann ist Diplom-Informatiker und arbeitet als Cheftechnologe und Softwarearchitekt bei der Accso - Accelerated Solutions GmbH. Seit Ende der 90er-Jahre wirkt er als Softwareentwickler und -architekt in der Softwareentwicklung in diversen Projekten der Individualentwicklung für Kunden verschiedener Branchen. Seit den Zeiten von Java 1.0 beschäftigt er sich mit Java als Programmiersprache und als Ökosystem. [email protected] @lemmi111171 www.xing.com/profile/Martin_Lehmann3 Dr. Kristine Schaal, Accso - Accelerated Solutions GmbH Softwarearchitektin Dr. Kristine Schaal ist als Softwarearchitektin bei der Accso - Accelerated Solutions GmbH tätig. Sie arbeitet seit fast 20 Jahren in der Softwareentwicklung und ist in Projekten der Individualentwicklung für Kunden verschiedener Branchen unterwegs, technisch überwiegend im Java-Umfeld. [email protected] @krschaal www.xing.com/profile/Kristine_Schaal
JSR 376 und seine Ziele Strong Encapsulation Eine Komponente kann ihr öffentliches API definieren und den Zugriff auf Implementierungsgeheimnisse verhindern. Reliable Configuration Ablösung des Classpath (fehleranfällig, wenn Klassen mehrfach enthalten sind Reihenfolge). Scalable Java SE Platform Die Java-Plattform selber ist nun modularisiert. Man kann individuell angepasste, „schlanke“ Plattformen bauen. JSR 376 (2014-2017) „Java Platform Module System“ (JPMS) Project Jigsaw in Java 9-Release vom 21. September 2017 enthalten. Aktuell: 9.0.1 vom 18. Oktober 2017 Enthalten: JEP 261, 200, 201, 220, 260, 282 mit Modulsystem, modularisiertem JDK und der Kapselung interner APIs
Menge von Java-Packages & Resources Wird in ein „Modular JAR“ kompiliert. Dieses liegt im neuen Module-Path MP. Modul-Namen müssen eindeutig sein (erlaubt sind . und _ , aber nicht -) • „read“-Abhängigkeitsbeziehungen zu einem oder mehreren anderen Modulen • „exports“: Welche Packages des Moduls werden exportiert? (Default: nichts!) modmain Ein Modul ist ab Java 9 ein „First-Class-Citizen“ in Java. moda read exports pkg main pkg a pkga inter nal
Java 9: Readability und Accessibility read modmain pkg main Checks zu Compile-Zeit und zur Laufzeit von 1. neu: Readability „ich benötige dieses Modul, will darauf zugreifen“ 2. neu: Accessibility „worauf dürfen andere zugreifen?“ (unabhg. von Classloadern!) 3. Check der „alten“ Sichtbarkeitsmodifier public,protected,<package>,private de.my. package. ximpl de.my. package. internal de.my. package exports moda
pkga1/ A1.java pkga2/ A2.java pkga3/ A3.java pkgainternal/ InternalA.java modb/ module-info.java pkgb/ B.java modc/ module-info.java pkgc/ C.java modmain/ module-info.java pkgmain/ Main.java Modul-Definition eines Moduls liegt in Datei module-info.java im obersten Modul-Sourcenverzeichnis. • mit allen die Abhängigkeiten zu anderen Modulen • mit der Liste der zugreifbaren Packages Wird kompiliert zu .class-File! Wird mit in das JAR-File paketiert. Beispiel: Vier Module moda, modb, modc, modmain und ihr Sourcenbaum module-info.java
java.sql.Driver Implementierung in Modul java.sql im MySQL-Driver Dynamische Bindung beim Start über ServiceLoader, also keine statische Auflösung über Modul-Namen wie bei Requires! Dynamische Bindung von Interface und Implementierung module java.sql { … // Definiert Schnittstelle uses java.sql.Driver; // ... und exportiert sie auch exports java.sql; } module com.mysql.jdbc { requires java.sql; … // Implementiert Schnittstelle provides java.sql.Driver with com.mysql.jdbc.Driver; }
um Reflection? (und um Serialisierung, Dependency-Injection, Bytecode-Enhancement etc.) Zur Erinnerung 1. Check der Readability 2. Check der Accessibility 3. Check von public,protected,<package>,private Alle Checks auch bei Reflection! Convenience bei Reflection: read-Beziehung ist immer vorhanden. Aber Accessibility-Checks greifen auch „scharf“ bei Reflection: Klassen in nicht exportierten Packages sind also nicht zugreifbar. • Gilt für newInstance, getField, getMethod, … • Gilt auch für setAccessible(true)
für „Deep Reflection“ mit setAccessible(true) Shortcut „open module“ öffnet alle Packages des Moduls mit „opens“ opens öffnet ein Package nur zur Laufzeit Compile-Zeit Reflection (Shallow) Reflection (Deep) exports pkg erlaubt erlaubt nicht erlaubt opens pkg nicht erlaubt erlaubt erlaubt exports pkg und opens pkg erlaubt erlaubt erlaubt
immer um Typen-Sichtbarkeit: Interfaces exports pkga inter nal pkga Inte rnal Data Nur Export von IData Instanzen von InternalData behalten ihren Typ auch über Modulgrenze hinaus. public class Factory { public IData createData() { return new InternalData(); } … public class Caller { … IData myData = new Factory().createData(); … I Data Fact ory Call er mod main moda pkgmain
immer um Typen-Sichtbarkeit: Ableitung exports pkga inter nal pkga Inte rnal Data Nur Export von Data Instanzen von InternalData behalten ihren Typ auch über Modulgrenze hinaus. public class Factory { public Data createData() { return new InternalData(); } … public class Caller { … Data myData = new Factory().createData(); … Data Fact ory Call er moda mod main pkgmain
nal pkga Letztlich geht‘s immer um Typen-Sichtbarkeit: Exceptions Interne Exceptions können nach außen geworfen werden. Caller kann nur Oberklassen fangen. public class A { public void doIt() throws InternalException { ... } public class Caller { … try { new A().doIt(); } catch(Exception ex) {...} catch(RuntimeException rex) {…} … Call er moda Inter nal Excep tion Inter nalRun timeExc eption exports A mod main pkgmain
eine Resource in einem anderen Modul moda: Kein Zugriff erlaubt? Stream ist null , keine IllegalAccessException Der Zugriff ist immer auch ohne opens möglich auf: • Resources im "unnamed package" • Resources, deren Verzeichnis sich nicht auf einen gültigen Packagenamen abbilden lässt (z.B. META-INF/MANIFEST.MF) Resource wird über ihr Verzeichnis geschützt, wenn sich dieses auf einen Package-Namen abbilden lässt. Das Package muss mit opens (nicht exports!) geöffnet sein. Der Zugriff auf Resources anderer Module ist geschützt! A.class.getModule().getResourceAsStream("/foo/bar.properties")
& Reads der module-info nicht reichen…? Neue Read-Beziehung vonnach Export/Open weiterer Packages Kommandozeile javac, java oder @file programmatisch --add-reads java.lang. Module.addReads() --add-exports --add-opens Auch in JAR-Manifest möglich! java.lang. Module.addExports() Module.addOpens() Vorsicht - Caller-sensitiv: Aufruf nur im eigenen Modul möglich!
nur öffentliche Schnittstelle von moda. Neues Modul braucht also keinen Zugriff auf interne Klassen. Blackbox-Test eines Moduls moda moda read exports pkga internal pkga $ java --module-path mlib\;amlib --add-modules modtest.blackbox -m junit/org.junit.runner.JUnitCore pkgblacktest.BlackBoxTest junit read modtest. blackbox pkg black test
für Whitebox-Tests eines Moduls moda Ein Whitebox-Test benötigt zum Test interne Klassen von moda aus dem Package pkgainternal. moda pkga internal pkga Unschön Ein Patch des JAR-Files moda.jar mit passender module-info.class ist i.d.R. keine Alternative. Funktioniert z.B. nicht bei Checksummen! exports
Test-Modul modtest.whitebox Wie erhält es Zugriff auf die internen Packages von moda? Whitebox-Test eines Moduls moda : Varianten 1a, b Statische Abhängigkeit zu Testcode Pflege der Skripte?! module moda { exports pkga; exports pkgainternal to modtest.whitebox; } a) Mit exports to der zu testenden Packages an modtest.whitebox $ javac --add-exports moda/pkgainternal=modtest.whitebox $ java --add-exports moda/pkgainternal=modtest.whitebox b) Dynamischer Export der zu testenden Packages mit --add-exports
Modul moda enthält auch die Testklassen. Aber wo verwaltet man diese? Whitebox-Test eines Moduls moda : Varianten 2a, b Testcode wird mit paketiert und deployed a) Testklassen sind Teil von moda. Testklassen können damit alle zu testenden Klassen aus moda sehen. b) Testklassen werden getrennt abgelegt. Zu Compile-Zeit und zur Test-Laufzeit werden sie dem Modul moda hinzugefügt („Patchen“).
System-Module-Path Observable Modules: „Was da ist“ java. base Module-Path modmain Alle Module auf dem Module-Path zusammen mit den System-Modulen bilden das „Universum“ der Observable Modules.
ist die transitive Hülle aller Abhängigkeiten aller Root-Module der Observable Modules. Root-Module ist hier modmain Observable Modules System-Module-Path Configuration: „Was wirklich geladen wird“ Module-Path modmain java. base $ java --module-path mlib -m modmain/pkgmain.Main modmain java. base
System-Module-Path Configuration: Alle Module aus Module-Path dazunehmen Module-Path modmain java. base modmain java. base modx Shortcut für „alle Module aus MP“ dazunehmen über ALL-MODULE-PATH $ java --module-path mlib --add-modules ALL-MODULE-PATH -m modmain/pkgmain.Main
Module darf keine Klassen im Unnamed Package enthalten. Resources sind dagegen im Unnamed Package weiterhin erlaubt. Klassen im Unnamed Package nicht mehr erlaubt src\moda\MyClassInTheUnnamedPackage.java:2: error: unnamed package is not allowed in named modules public class MyClassInTheUnnamedPackage { ^ 1 error Compile- Fehler
Laufzeit- Fehler beim Start Ein „Split“ eines Package auf mehrere Module ist nicht erlaubt! Gilt auch, wenn das Package nicht exportiert ist!! Selbst dann, wenn es keine Klassen-Duplikate in den beiden Modulen gibt!!! Split Package: Module müssen disjunkte Packages haben. Ausnahme: Gilt nicht für The Unnamed Module. src\modmainfoo\module-info.java:1: error: module modmainfoo reads package pkgfoo from both modsplitfoo1 and modsplitfoo2 module modmainfoo { ^ 1 error Error occurred during initialization of boot layer java.lang.LayerInstantiationException: Package pkgbar in both module modsplitbar1 and module modsplitbar2 Gilt nur für Module in einer gemeinsamen Configuration (nur auf Module-Path OK).
die Verwendung von JDK-internen Klassen: Seit Java 8: Neue offizielle APIs ersetzen unsupportete APIs. Beispiel: sun.misc.BASE64Encoder java.util.Base64.Encoder Andere APIs werden schrittweise ersetzt, später nicht mehr zugänglich sein. Details siehe JEP 260: http://openjdk.java.net/jeps/260 JDK-interne APIs sind geschützt, nicht mehr zugreifbar! $ jdeps --jdk-internals ./classes pkgmain.Main -> jdk.internal.misc.SharedSecrets JDK internal API (java.base) Warning: JDK internal APIs are unsupported and private to JDK implementation that are subject to be removed or changed incompatibly and could break your application. Please modify your code to eliminate dependence on any JDK internal APIs. For the most recent update on JDK internal API replacements, please check: https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool
einen illegalen Zugriff: permit entspricht einem opens auf alle JDK-internen Packages, aber nur auf die Packages, die es bereits in Java 8 gab! Nur in Java 9 ist permit der Default – nur bis Release 18.3! Der große Kill-Switch --illegal-access $ java --add-opens=java.base/sun.io=ALL-UNNAMED ... $ java --illegal-access=permit|warn|deny ... Einige prominente Frameworks sind noch nicht auf Java 9 umgestellt. Hauptproblem: Verwendung JDK-interner Klassen per Reflection. Lösung: --add-opens öffnet benötigte Packages für Classpath. • Nachteil: --add-opens muss in allen run-Skripten gesetzt werden! • Bricht zahlreichen existierenden Code!
alle 6 Monate. Wechsel zu „time-based release train“ Nächstes Release bereits im März 2018 als „18.3“ http://openjdk.java.net/projects/jdk/18.3/ Releases im März & September mit fertigen neuen Features Dazu vier weitere Releases im Jahr mit Security- und Bugfixes Alle 3 Jahre Releases mit LTS (Long-Term-Support), evtl. ab „18.9“ https://mreinhold.org/blog/forward-faster #javatrain http://mail.openjdk.java.net/pipermail/discuss/2017- September/thread.html#4281 Warum umsteigen? Neue Release-Policy nach Java 9!
„echtes“ Modul: Explicit Module enthält module-info in einem Modular JAR liegt auf dem Module-Path oder auf dem System- Module-Path Open Module ist Spezialfall eines Explicit Modules moda java. base modmain
in ein Modul umwandeln: Automatic Module Module-Path moda java. base JAR auf dem Module-Path modmain junit hat „reads“ auf alle anderen Module „exports“ + „opens“ aller Packages Ein Explicit Module muss ein Automatic Module requiren. Automatischer Name: - . junit-4.12.jar junit Named Modules := Explicit Modules + Automatic Modules
auf dem Classpath im „Unnamed Module“ Module-Path modmain moda The Unnamed Module java. base „exports“ aller Packages readable von Automatic Modules nicht ohne weiteres von Explicit Modules hat „reads“ auf alle Module junit a.jar hibernate .jar
Module-Path überdeckt den Classpath! Module-Path moda modmain java. base junit 1 The Unnamed Module a.jar Suchreihenfolge Module-Path The Unnamed Module Vorsicht bei gleichzeitiger Verwendung von Module-Path und Classpath! 2 hibernate .jar 1 2 pkga. A pkga. A Klasse pkga.A wird in moda gefunden! Keine Fehler- meldung, kein Split Package!
Anwendung (fast) unverändert auf Java 9 benutzen Java-System-Module sind auf dem System-Module-Path Module-Path The Unnamed Module java. base java. sql main.jar a.jar b.jar hibernate.jar Die Anwendung liegt auf dem Classpath (im Unnamed Module).
Anwendung (fast) unverändert auf Java 9 benutzen Module-Path The Unnamed Module java. base java. sql main.jar a.jar b.jar hibernate.jar Keine oder nur minimale Anpassungen notwendig: • Compile- und Runtime-Option --add-modules java.sql • Vorsicht: Wenig inkompatible JDK-Änderungen! Ggf. Updates nötig!
„Normales“ JAR in ein Explicit Module migrieren b.jar hängt von nichts ab. b.jar kann daher einfach in ein Explicit Module migriert werden. Compile- und Runtime-Option --add-modules modb notwendig The Unnamed Module main.jar a.jar hibernate.jar Module-Path java. base java. sql b.jar modb
JAR in ein Automatic Module migrieren a.jar hängt von hibernate.jar ab. Wird daher erst in ein Automatic Module migriert, damit hibernate.jar readable bleibt. Evtl. Package-Überschneidungen auflösen (Split-Package)! Module-Path java. base java. sql modb The Unnamed Module main.jar hibernate.jar a.jar moda
Third-Party-Lib in Automatic Module migrieren hibernate wird in ein Automatic Module migriert. Um hibernate in ein Explicit Module zu migrieren, müsste module-info erstellt werden (… das überlassen wir besser dem Maintainer) Module-Path java. base java. sql modb moda hiber nate The Unnamed Module main.jar hibernate.jar
Automatic Module in Explicit Module migrieren moda kann nun in ein Explicit Module migriert werden, weil hibernate nun ebenfalls im Module-Path liegt. Module-Path java. base java. sql modb hiber nate The Unnamed Module main.jar moda moda
Den Rest migrieren Module-Path java. base java. sql c junit b main Module-Path java. base java. sql modb hiber nate moda main mod main The Unnamed Module main.jar
dem MP Modul in einem JAR Modul in einer JMOD-Datei Aus einer module- info.class Wie kann man Informationen zu einem Modul ausgeben? $ jar --file mlib/modmain.jar –-describe-module $ jdeps --module-path mlib mlib/modmain.jar $ jmod describe $JAVA_HOME/jmods/java.base.jmod $ javap -verbose module-info.class $ java --module-path mlib --describe-module modmain
Configuration Debugging Debugger gibt Modul aus Stacktrace gibt Modul mit aus Wie erhalte ich Informationen zur Laufzeit? $ java –Xdiag:resolver ... $ java –Xlog:module=debug|trace ... $ java --module-path ./mlib --list-modules ... über Class.getModule() pkgb.MyException: MyException's message at modb/pkgb.B.doItThrowException(B.java:13) at modmain/pkgmain.Main.main(Main.java:18) $ java --module-path ./mlib --show-module-resolution
Oxygen.1 von Oktober 2017 unterstützt Java 9 (alte Eclipse-Versionen mit Beta-Plugin besser meiden!) • Eclipse startet mit Java 9 • Java 9 als JRE nutzbar • Code-Completion für module-info für requires, exports, opens … • Launch und Debug kann Module-Path Noch buggy oder fehlt: Fehler bei Automatic Modules, JUnit-Dialoge ohne MP, Unnamed-Package-Compilefehler, keine Optionen für J9-Compiler einstellbar (z.B. --add-exports) Entwicklungstools: Eclipse
unverändert test-compile unterstützt Blackbox- und Whitebox-Tests. test noch mit Classpath. Abhängigkeiten müssen redundant in der pom und in der module-info modelliert werden. Build-Tools: Maven mit Java-9-Support … <dependency> <groupId>de.accso.jigsaw</groupId> <artifactId>moda</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> module modmain { requires moda; }
ModuleDescriptor.Exports ModuleDescriptor.Opens ModuleDescriptor.Provides ModuleReference ModuleFinder Configuration Zugriff auf das Modulsystem über neue API Modulbeschreibung mit Name, Version, … ... mit allen Requires ... mit allen Exports … mit allen Opens … mit allen Provides aber Uses sind leider nur Strings Laufzeit-Referenz auf ein Modul Ein/alle Module finden („was da ist”) „Was geladen wurde“ java.lang.module.…
„DepVis“ zur Visualisierung von Modul-Graphen Ideen: requires-n-transitive, Filter auf einzelne Beziehungen, Packages, Hashes, nur Module der Configuration, mehr Konfiguration mit Farben & Co … https://github.com/accso/java9-jigsaw-depvis Basiert auf GraphViz http://www.graphviz.org/ mit Java-API https://github.com/kohsuke/graphviz-api Visualisierung und Textausgabe von Modulen auf Module-Path und im System mit Wildcard – Black-&Whitelisting Explicit, Automatic, Open requires (auch 1-transitive, static, mandated), exports-to, opens-to Concealed Packages uses und provides
werden Module in sogenannte Layer gruppiert: Jedes Modul liegt in einem Layer. Layer • haben leider keinen Namen • haben jeweils eine eigene Configuration. • benötigen mindestens je einen Classloader (pro Layer / pro Module). Es gibt mindestens einen Layer zur Laufzeit („Boot Layer“). Layer bilden eine Hierarchie (aber kein Baum, da mehrere Parents möglich!) Eine Jigsaw-Anwendung besteht zur Laufzeit aus Layern
2 – Module aufgeteilt auf Layer-Hierarchie Pseudo- Parent empty-Layer Layerübergreifende Aufrufe von „oben nach unten“ und umgekehrt via Reflection Split-Package mit mehreren (Geschwister-) Layern? Kein Problem, solange nur getrennte CLs verwendet werden
public != accessible Erster Accessibility-Level ist nun das „Package“. (Warum eigentlich?) Zugriffsschutz ist sehr restriktiv. Opt-in oder Opt-out? Leider keine Wildcards bei Exports-Package! Ein Modul hat keine (echten) Versionen - nur informell module-info wird zu .class-File kompiliert. Ist aber doch gar keine Klasse. Warum nicht MANIFEST.MF? Oder wenigstens Human-Readable-Format? Abhängigkeitsmodell ist sehr statisch über Namen. Kein Alias für ein Modul möglich (außer --patch-module) Module sind nicht gruppierbar. Keine Hierarchie möglich (vgl. parent.pom) Warum keine Scopes wie „Test“ und „Runtime“ (vgl. Maven)? Kritik …
Schritt zur echter Komponentenorientierung! Jigsaw und Java 9 sind sehr stabil. Allerdings sehr viel Dynamik: Implementierungen (und auch Konzepte, gerade zu Reflection) wurden bis zuletzt geändert. Kill-Switch als Antwort auf JCP-Ablehnung im April 2017. Aggregator-Module über requires transitive Migration ist gut machbar, aber wird mühsam! • Modul-Kategorien: „wo kommt was her?“ ist nicht trivial! • MP & CP: Bekommen wir das jemals richtig aussortiert? … und Lob!
mit Java Release-Build b181 und 9.0.1 Bash-Skripte für Compile, Run, Test (in Windows z.B. mit Babun nutzbar) Projekte für Eclipse 4.7.1a Unsere 32 Beispiele zeigen alles Wissenswerte zu Jigsaw zu Requires, Exports, Exports-To, Requires-Static, Requires-Transitive, Blackbox-Test, Whitebox-Test, Uses, Provides, Maven-Integration, Opens, Open Module, Manifest-Optionen, versteckte Main-Klasse, Automatic Modules, Reflection, Resolved Modules (Configuration), Naming von Modulen, Unnamed Module (Classpath), Layer, Javadoc, Annotations, jlink etc. pp. usw. usf. Unsere Jigsaw-Beispiele stehen auf Github