{"id":3980,"date":"2017-10-23T17:05:01","date_gmt":"2017-10-23T15:05:01","guid":{"rendered":"http:\/\/www.tutego.de\/blog\/javainsel\/?p=3980"},"modified":"2017-10-23T17:05:01","modified_gmt":"2017-10-23T15:05:01","slug":"module-entwickeln-und-einbinden","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2017\/10\/module-entwickeln-und-einbinden\/","title":{"rendered":"Module entwickeln und einbinden"},"content":{"rendered":"<p>Das JPMS (Java Platform Module System), auch unter dem Projektnamen Jigsaw bekannt, ist eine der gr\u00f6\u00dften Neuerungen in Java 9. Im Mittelpunkt steht die starke Kapselung: Implementierungsdetails kann ein Modul geheim gehalten. Selbst Hilfscode innerhalb des Moduls, auch wenn er \u00f6ffentlich ist, darf nicht nach au\u00dfen dringen. Zweitens kommt eine Abstraktion von Verhalten \u00fcber Schnittstellen hinzu, die interne Klassen aus dem Modul implementieren k\u00f6nnen, wobei dem Nutzer die konkreten Klassen nicht bekannt sind. Als dritten Punkt machen explizite Abh\u00e4ngigkeiten die Interaktion mit anderen Modulen klar. Eine grafische Darstellung hilft auch bei gro\u00dfen Architekturen, die \u00dcbersicht \u00fcber Nutzungsbeziehungen zu behalten.<\/p>\n<h3>Wer sieht wen<\/h3>\n<p>Klassen, Pakete und Module lassen sich als Container mit unterschiedlichen Sichtbarkeiten sehen:<\/p>\n<p>\u00b7 Ein Typ, sei es Klasse oder Schnittstelle, enth\u00e4lt Attribute und Methoden<\/p>\n<p>\u00b7 Ein Paket enth\u00e4lt Typen.<\/p>\n<p>\u00b7 Ein Modul enth\u00e4lt Pakete.<\/p>\n<p>\u00b7 Private Eigenschaften in einem Typ sind nicht in anderen Typen sichtbar.<\/p>\n<p>\u00b7 Nicht \u00f6ffentliche Typen sind in anderen Paketen nicht sichtbar.<\/p>\n<p>\u00b7 Nicht exportierte Pakete sind au\u00dferhalb eines Moduls nicht sichtbar.<\/p>\n<p>Ein Modul ist definiert<\/p>\n<p>1. durch einem Namen,<\/p>\n<p>2. durch die Angabe, was es exportiert m\u00f6chte und<\/p>\n<p>3. welches Modul es zur Arbeit selbst ben\u00f6tigt.<\/p>\n<p>Interessant ist der zweite Aspekt, also dass ein Modul etwas exportiert. Wenn nichts exportiert wird, ist auch nichts sichtbar nach au\u00dfen. Alles, was Au\u00dfenstehende sehen sollen, muss in der Modulbeschreibung aufgef\u00fchrt sein \u2013 nicht alle \u00f6ffentlichen Typen des Moduls sind standardm\u00e4\u00dfig \u00f6ffentlich, dann w\u00e4re das kein Fortschritt zu JAR-Dateien. Mit dem neuen Modulsystem haben wir also eine ganz andere Sichtbarkeit. Aus der Viererbande public, private, paketsichtbar, protected bekommt public eine viel feinere Abstufung. Denn was public ist bestimmt das Modul, und das sind<\/p>\n<p>&#8211; Typen, die das Modul f\u00fcr alle exportiert,<\/p>\n<p>&#8211; Typen f\u00fcr explizit aufgez\u00e4hlte Module,<\/p>\n<p>&#8211; alle Typen im gleichen Modul.<\/p>\n<p>Der Compiler und die JVM achten auf die Einhaltung der Sichtbarkeit, und auch Tricks mit Reflection sind nicht mehr m\u00f6glich, wenn ein Modul keine Freigabe erteilt hat.<\/p>\n<h3>Modultypen<\/h3>\n<p>Wir wollen uns in dem Abschnitt intensiver mit drei Modultypen besch\u00e4ftigen. Wenn wir neue Module schreiben, dann sind das benannte Module. Daneben gibt es aus Kompatibilit\u00e4tsgr\u00fcnden automatische Module und unbenannte Module, mit denen wir vorhandene JAR-Dateien einbringen k\u00f6nnen. Die Bibliothek der Java SE ist selbst in Module unterteilt, wir nennen sie Plattform-Module.<\/p>\n<p>Die Laufzeitumgebung zeigt mit einem Schalter &#8211;list-modules alle Plattform-Module an.<\/p>\n<p>Beispiel: Liste die ca. 70 Module auf:<\/p>\n<p>$ <b>java &#8211;list-modules<\/b><\/p>\n<p>java.activation@9<\/p>\n<p>java.base@9<\/p>\n<p>java.compiler@9<\/p>\n<p>\u2026<\/p>\n<p>oracle.desktop@9<\/p>\n<p>oracle.net@9<\/p>\n<p>Im Ordner C:\\Program Files\\Java\\jdk-9\\jmods liegen JMOD-Dateien.<\/p>\n<h3>Plattform-Module und JMOD-Beispiel<\/h3>\n<p>Das Kommandozeilenwerkzeug jmod zeigt an, was ein Modul exportiert und ben\u00f6tigt. Nehmen wir die JDBC-API f\u00fcr Datenbankverbindungen als Beispiel; die Typen sind in einem eigenen Modul mit den Namen java.sql.<\/p>\n<p>C:\\Program Files\\Java\\jdk-9\\bin&gt;<b>jmod describe ..\\jmods\\java.sql.jmod<\/b><\/p>\n<p>java.sql@9<\/p>\n<p>exports java.sql<\/p>\n<p>exports javax.sql<\/p>\n<p>exports javax.transaction.xa<\/p>\n<p>requires java.base mandated<\/p>\n<p>requires java.logging transitive<\/p>\n<p>requires java.xml transitive<\/p>\n<p>uses java.sql.Driver<\/p>\n<p>platform windows-amd64<\/p>\n<p>Wir k\u00f6nnen ablesen:<\/p>\n<p>\u00b7 den Namen<\/p>\n<p>\u00b7 die Pakete, die das Modul exportiert: java.sql, javax.sql und javax.transation.xa<\/p>\n<p>\u00b7 die Module, die java.sql ben\u00f6tigt: java.base ist hier immer drin, dazu kommen java.logging und java.xml<\/p>\n<p>Die Meldung mit \u201euses\u201c steht im Zusammenhang mit dem Service-Locator \u2013 wir k\u00f6nnen das vorerst ignorieren. Die Kennung \u00fcber die Plattform (windows-amd64) schreibt jmod mit hinein, es ist die Belegung der System-Property os.arch auf dem Build-Server.<\/p>\n<h3>Verbotene Plattformeigenschaften nutzen, &#8211;add-exports<\/h3>\n<p>Als Sun von vielen Jahren mit der Entwicklung der Java-Bibliotheken begann, kamen viele interne Hilfsklassen mit in die Bibliothek. Viele beginnen mit den Paketpr\u00e4fixen com.sun und sun. Die Typen wurden immer als interne Typen kommuniziert, doch bei einigen Entwicklern war die Neugierde und das Interesse so gro\u00df, dass die Warnungen von Sun\/Oracle ignoriert wurden. In Java 9 kommt der gro\u00dfe Knall, da public nicht mehr automatisch public f\u00fcr alle Klassen au\u00dferhalb des Moduls ist; die internen Klassen werden nicht mehr exportiert, sind also nicht mehr benutzbar. Es kommt zu einem Compilerfehler, wie in folgendem Beispiel:<\/p>\n<p>public class ShowRuntimeArguments {<\/p>\n<p>public static void main( String[] args ) throws Exception {<\/p>\n<p>System.out.println( java.util.Arrays.toString( jdk.internal.misc.VM.getRuntimeArguments() ) );<\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>Unser Programm greift auf die VM-Klasse zur\u00fcck, um die eigentliche Belegung der Kommandozeile zu erfragen. In der main(String[] args)-Methode sind in args keine VM-Argumente enthalten.<\/p>\n<p>\u00dcbersetzen wir das Programm, gibt es einen Compilerfehler (nicht bei einem Java 8-Compiler):<\/p>\n<p>$ <b>javac ShowRuntimeArguments.java<\/b><\/p>\n<p>ShowRuntimeArguments.java:3: error: package jdk.internal.misc is not visible<\/p>\n<p>System.out.println( java.util.Arrays.toString( jdk.internal.misc.VM.getRuntimeArguments() ) );<\/p>\n<p>^<\/p>\n<p>(package jdk.internal.misc is declared in module java.base, which does not export it to the unnamed module)<\/p>\n<p>1 error<\/p>\n<p>The module java.base does not export the package jdk.internal.misc., so the type jdk.internal.misc.Unsafe is not accessible &#8211; as a consequence compilation fails.<\/p>\n<p>Das Problem dokumentiert der Compiler. Es ist dadurch zu l\u00f6sen, indem mit dem Schalter &#8211;add-exports aus dem Modul java.base das Paket jdk.internal.misc unserer Klasse bereitgestellt wird. Die Angabe ist f\u00fcr den Compiler und f\u00fcr die Laufzeitumgebung zu setzen:<\/p>\n<p>$ <b>javac <u>&#8211;add-exports java.base\/jdk.internal.misc=ALL-UNNAMED<\/u> ShowRuntimeArguments.java<\/b><\/p>\n<p>$ <b>java ShowRuntimeArguments<\/b><\/p>\n<p>Exception in thread &quot;main&quot; java.lang.IllegalAccessError: class ShowRuntimeArguments (in unnamed module @0x77afea7d) cannot access class jdk.internal.misc.VM (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @0x77afea7d<\/p>\n<p>at ShowRuntimeArguments.main(ShowRuntimeArguments.java:3) <\/p>\n<p>$ <b>java <u>&#8211;add-exports java.base\/jdk.internal.misc=ALL-UNNAMED<\/u> ShowRuntimeArguments<\/b><\/p>\n<p>[&#8211;add-exports=java.base\/jdk.internal.misc=ALL-UNNAMED]<\/p>\n<p>Wir sehen die Ausgabe, das Programm funktioniert.<\/p>\n<p>Eine Angabe wie java.base\/jdk.internal.misc, bei der vorne das Modul steht und hinter dem \/ der Paketname, ist oft ab Java 9 anzutreffen. Hinter dem Gleichheitszeichen steht entweder unser Paket, welches die Typen in jdk.internal.misc sehen kann, oder \u2013 wie in unserem Fall \u2013 ALL-UNNAMED.<\/p>\n<h3>jdeps<\/h3>\n<p>H\u00e4tten wir das Programm schon erfolgreich unter Java 8 \u00fcbersetzt, w\u00fcrde es zur Laufzeit ebenfalls knallen. Da es nun sehr viel Programmcode gibt, haben die Java-Entwickler bei Oracle das Kommandozeilenprogramm jdeps entwickelt. Es meldet, wenn interne Typen im Programm vorkommen:<\/p>\n<p>$ jdeps ShowRuntimeArguments.class<\/p>\n<p>ShowRuntimeArguments.class -&gt; java.base<\/p>\n<p>&lt;unnamed&gt; -&gt; java.io java.base<\/p>\n<p>&lt;unnamed&gt; -&gt; java.lang java.base<\/p>\n<p>&lt;unnamed&gt; -&gt; java.util java.base<\/p>\n<p>&lt;unnamed&gt; -&gt; jdk.internal.misc JDK internal API (java.base)<\/p>\n<p>Die Meldung \u201eJDK internal API\u201c bereitet uns darauf vor, dass es gleich \u00c4rger geben wird. <\/p>\n<p>So kann relativ leicht eine gro\u00dfe Codebasis untersucht werden und Entwicker k\u00f6nnen proaktiv den Stellen auf den Grund gehen, die problematische Abh\u00e4ngigkeiten haben.<\/p>\n<h3>Plattformmodule einbinden, &#8211;add-modules und &#8211;add-opens<\/h3>\n<p>Jedes Java SE-Projekt basiert auf dem Modul java.se, was diverse Modulabh\u00e4ngigkeiten nach sich zieht. <\/p>\n<p>&lt;pic: java.se-graph.png, \u201eModulabh\u00e4ngigkeiten von java.se\u201c&gt;<\/p>\n<p>Diverse Module sind nicht Teil vom Modul Java SE, unter anderem sind das das Java Activation Framework, CORBA, Transaction-API, JAXB, Web-Services und interne Module, die mit jdk beginnen.<\/p>\n<p>Starten wir ein Programm mit Bezug zu einem dieser Bibliotheken gibt es einen Fehler. Zun\u00e4chst zum Programm, das ein Objekt automatisch in XML konvertieren soll:<\/p>\n<p>public class Person {<\/p>\n<p>public String name = &quot;Chris&quot;;<\/p>\n<p>public static void main( String[] args ) {<\/p>\n<p>javax.xml.bind.JAXB.marshal( new Person(), System.out );<\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>Ausgef\u00fchrt auf der Kommandozeile folgt ein Fehler:<\/p>\n<p>$ <b>java Person<\/b><\/p>\n<p>Exception in thread &quot;main&quot; java.lang.NoClassDefFoundError: javax\/xml\/bind\/JAXB<\/p>\n<p>at Person.main(Person.java:6)<\/p>\n<p>Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXB<\/p>\n<p>at java.base\/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)<\/p>\n<p>at java.base\/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)<\/p>\n<p>at java.base\/java.lang.ClassLoader.loadClass(Unknown Source)<\/p>\n<p>&#8230; 1 more<\/p>\n<p>Wir m\u00fcssen das Modul java.xml.bind (oder auch das \u201e\u00dcber\u201c-Modul java.se.ee) mit angeben; daf\u00fcr dient der Schalter &#8211;add-modules.<\/p>\n<p>$ <b>java <u>&#8211;add-modules java.xml.bind<\/u> Person<\/b><\/p>\n<p>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;yes&quot;?&gt;<\/p>\n<p>&lt;person&gt;<\/p>\n<p>&lt;name&gt;Chris&lt;\/name&gt;<\/p>\n<p>&lt;\/person&gt;<\/p>\n<h3>\u00d6ffnen f\u00fcr Reflection<\/h3>\n<p>Jetzt gibt es allerdings ein anderes Problem, was auff\u00e4llt, wenn wir einen anderen Typ in XML umwandeln wollen:<\/p>\n<p>public class Today {<\/p>\n<p>public static void main( String[] args ) {<\/p>\n<p>javax.xml.bind.JAXB.marshal( new java.util.Date(), System.out );<\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>Auf der Kommandozeile zeigt sich:<\/p>\n<p>$ <b>java &#8211;add-modules java.xml.bind Today<\/b><\/p>\n<p>Exception in thread &quot;main&quot; javax.xml.bind.DataBindingException: javax.xml.bind.JAXBException: Package java.util with JAXB class java.util.Date defined in a module java.base must be open to at least java.xml.bind module.<\/p>\n<p>at java.xml.bind@9\/javax.xml.bind.JAXB._marshal(Unknown Source)<\/p>\n<p>at java.xml.bind@9\/javax.xml.bind.JAXB.marshal(Unknown Source)<\/p>\n<p>at Today.main(Today.java:4)<\/p>\n<p>Caused by: javax.xml.bind.JAXBException: Package java.util with JAXB class java.util.Date defined in a module java.base must be open to at least java.xml.bind module.<\/p>\n<p>at java.xml.bind@9\/javax.xml.bind.ModuleUtil.delegateAddOpensToImplModule(Unknown Source)<\/p>\n<p>at java.xml.bind@9\/javax.xml.bind.ContextFinder.newInstance(Unknown Source)<\/p>\n<p>at java.xml.bind@9\/javax.xml.bind.ContextFinder.newInstance(Unknown Source)<\/p>\n<p>\u2026<\/p>\n<p>Die zentrale Information ist \u201ePackage java.util with JAXB class java.util.Date defined in a module java.base must be open to at least java.xml.bind module\u201c. Wir m\u00fcssen etwas \u00f6ffnen; dazu verwenden wir den Schalter &#8211;add-opens:<\/p>\n<p>$ <b>java &#8211;add-modules java.xml.bind <u>&#8211;add-opens java.base\/java.util=java.xml.bind<\/u> Box<\/b><\/p>\n<p>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;yes&quot;?&gt;<\/p>\n<p>&lt;date&gt;2017-09-02T23:20:52.170+02:00&lt;\/date&gt;<\/p>\n<p>Die Option \u00f6ffnet f\u00fcr Reflection das Paket java.util aus dem Modul java.base f\u00fcr java.xml.bind. Neben &#8211;add-opens gibt es das \u00e4hnliche &#8211;add-exports, was alle \u00f6ffentlichen Typen und Eigenschaften zur \u00dcbersetzungs-\/Laufzeit \u00f6ffnet; &#8211;add-opens geht f\u00fcr Reflection einen Schritt weiter.<\/p>\n<h3>Projektabh\u00e4ngigkeiten in Eclipse<\/h3>\n<p>Um Module praktisch umzusetzen wollen wir in Eclipse zwei neue Java-Projekte aufbauen: \u201ecom.tutego.greeter\u201c und \u201ecom.tutego.main\u201c. Wir legen in das Projekt \u201ecom.tutego.greeter\u201c eine Klasse com.tutego.insel.greeter.Greeter an und in \u201ecom.tutego.main\u201c die Klasse com.tutego.insel.main.Main.<\/p>\n<p>Jetzt ist eine wichtige Vorbereitung in Eclipse n\u00f6tig: Wir m\u00fcssen einstellen, dass \u201ecom.tutego.main\u201c das Java-Projekt \u201ecom.tutego.greeter\u201c ben\u00f6tigt. Dazu gehen wir auf das Projekt \u201ecom.tutego.main\u201c und rufen im Kontextmen\u00fc Project auf; alternativ im Men\u00fcpunkt Project &gt; Properties oder \u00fcber die Tastenkombination Alt + Return. Im Dialog navigiere links auf Java Build Path und aktiviere den Reiter Projects. W\u00e4hle Add\u2026 und im Dialog w\u00e4hle aus der Liste com.tutego.greeter. Ok schlie\u00dft den kleinen Dialog und unter Required projects in build path taucht eine Abh\u00e4ngigkeit auf.<\/p>\n<p>Wir k\u00f6nnen jetzt zwei einfache Klassen implementieren. Zun\u00e4chst f\u00fcr das Projekt \u201ecom.tutego.greeter\u201c:<\/p>\n<p>com\/tutego\/insel\/greeter\/Greeter.java<\/p>\n<p>package com.tutego.insel.greeter;<\/p>\n<p>public class Greeter {<\/p>\n<p>private Greeter() { }<\/p>\n<p>public static Greeter instance() {<\/p>\n<p>return new Greeter();<\/p>\n<p>}<\/p>\n<p>public void greet( String name ) {<\/p>\n<p>System.out.println( &quot;Hey &quot;+ name );<\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>Und die Hauptklasse im Projekt \u201ecom.tutego.main\u201c:<\/p>\n<p>com\/tutego\/insel\/main\/Main<\/p>\n<p>package com.tutego.insel.main;<\/p>\n<p>import com.tutego.insel.greeter.Greeter;<\/p>\n<p>public class Main {<\/p>\n<p>public static void main( String[] args ) {<\/p>\n<p>Greeter.instance().greet( &quot;Chris&quot; );<\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>Da wir in Eclipse vorher die Abh\u00e4ngigkeit gesetzt haben, gibt es keinen Compilerfehler.<\/p>\n<h3>Benannte Module und module-info.java<\/h3>\n<p>Die Modulinformationen werden \u00fcber eine Datei module-info.java (kurz Modulinfodatei) deklariert, Annotationen kommen nicht zum Einsatz. Diese zentrale Datei ist der Hauptunterschied zwischen einem Modul und einer einfachen JAR-Datei. In dem Moment, in dem die spezielle Klassendatei module-info.class im Modulpfad ist, beginnt die Laufzeitumgung das Projekt als Modul zu interpretieren.<\/p>\n<p>Testen wir das, indem wir in unsere Projekte \u201ecom.tutego.greeter\u201c und \u201ecom.tutego.main\u201c eine Modulinfodatei anlegen. Das kann Eclipse \u00fcber das Kontextmen\u00fc Configue &gt; Create module-info.java f\u00fcr uns machen.<\/p>\n<p>F\u00fcr das erste Modul com.tutego.greeter entsteht:<\/p>\n<p>module-info.java<\/p>\n<p>\/**<\/p>\n<p>* <\/p>\n<p>*\/<\/p>\n<p>\/**<\/p>\n<p>* @author Christian<\/p>\n<p>*<\/p>\n<p>*\/<\/p>\n<p>module com.tutego.greeter {<\/p>\n<p>exports com.tutego.insel.greeter;<\/p>\n<p>requires java.base;<\/p>\n<p>}<\/p>\n<p>Und f\u00fcr die zweite Modulinfodatei \u2013 Kommentare ausgeblendet:<\/p>\n<p>module-info.java<\/p>\n<p>module com.tutego.main {<\/p>\n<p>exports com.tutego.insel.main;<\/p>\n<p>requires com.tutego.greeter;<\/p>\n<p>requires java.base;<\/p>\n<p>}<\/p>\n<p>Hinter dem Schl\u00fcsselwort module steht der Name des Moduls, den Eclipse automatisch so w\u00e4hlt wie das Eclipse-Projekt hei\u00dft.<a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Eigene Dokumente\/Insel\/Band 1\/#_ftn1_9164\" name=\"_ftnref1_9164\">[1]<\/a> Es folgt ein Block in geschweiften Klammern.<\/p>\n<p>Zwei Schl\u00fcsselworte fallen ins Auge, die wir schon vorher bemerkt haben: exports und requires.<\/p>\n<p>\u00b7 Das Projekt\/Modul com.tutego.greeter exportiert das Paket com.tutego.insel.greeter. Andere Pakete nicht. Es ben\u00f6tigt (requires) java.base, wobei das Modul Standard ist, und die Zeile gel\u00f6scht werden kann.<\/p>\n<p>\u00b7 Das Projekt\/Modul com.tutego.main exportiert das Paket com.tutego.insel.main und es ben\u00f6tigt com.tutego.greeter \u2013 diese Information nimmt sich Eclipse selbstst\u00e4ndig aus den Projektabh\u00e4ngigkeiten.<\/p>\n<p><strong>Info: Ein Modul required ein anderes Modul aber exports ein Paket.<\/strong><\/p>\n<p>Beginnen wir mit den Experimenten in den beiden module-info.java-Dateien:   <\/p>\n<table cellspacing=\"0\" cellpadding=\"0\" border=\"1\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"189\">\n<p>Modul<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>Aktion<\/p>\n<\/td>\n<td valign=\"top\" width=\"313\">\n<p>Ergebnis<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"189\">\n<p>com.tutego.greeter<\/p>\n<p>com.tutego.main<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>\/\/ requires java.base;<\/p>\n<\/td>\n<td valign=\"top\" width=\"313\">\n<p>Auskommentieren f\u00fchrt zu keiner \u00c4nderung, da java.base immer required wird<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"189\">\n<p>com.tutego.greeter<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>\/\/ exports com.tutego.insel.greeter;<\/p>\n<\/td>\n<td valign=\"top\" width=\"313\">\n<p>Compilerfehler im main-Modul \u201cThe type com.tutego.insel.greeter.Greeter is not accessible\u201d<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"189\">\n<p>com.tutego.greeter<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>exports com.tutego.insel.greeter to god;<\/p>\n<\/td>\n<td valign=\"top\" width=\"313\">\n<p>Nur das Modul god bekommt Zugriff auf com.tutego.insel.greeter. Das main-Modul meldet \u201cThe type com.tutego.insel.greeter.Greeter is not accessible\u201d<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"189\">\n<p>com.tutego.greeter<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>exports com.tutego.insel.closer;<\/p>\n<\/td>\n<td valign=\"top\" width=\"313\">\n<p>Hinzuf\u00fcgen f\u00fchrt zum Compilerfehler \u201cThe package com.tutego.insel.closer does not exist or is empty\u201d<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"189\">\n<p>com.tutego.main<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>\/\/ requires com.tutego.greeter;<\/p>\n<\/td>\n<td valign=\"top\" width=\"313\">\n<p>Compilerfehler \u201cThe import com.tutego.insel.greeter cannot be resolved\u201d<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"189\">\n<p>com.tutego.main<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>\/\/ exports com.tutego.insel.main;<\/p>\n<\/td>\n<td valign=\"top\" width=\"313\">\n<p>Keins, denn c.t.i.m wird von keinem Modul required<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Die Zeile mit exports com.tutego.insel.greeter to god zeigt einen qualifizierten Export.<\/p>\n<h3>\u00dcbersetzen und Packen von der Kommandozeile<\/h3>\n<p>Setzen wir in der Wurzelverzeichnis vom Modul com.tutego.greeter ein Batch-Skript compile.bat:<\/p>\n<p>compile.bat<\/p>\n<p>set PATH=%PATH%;C:\\Program Files\\Java\\jdk-9\\bin<\/p>\n<p>rmdir \/s \/q lib<\/p>\n<p>mkdir lib<\/p>\n<p>javac -d bin src\\module-info.java src\\com\\tutego\\insel\\greeter\\Greeter.java<\/p>\n<p>jar &#8211;create &#8211;file=lib\/com.tutego.greeter@1.0.jar &#8211;module-version=1.0 -C bin .<\/p>\n<p>jar &#8211;describe-module &#8211;file=lib\/com.tutego.greeter@1.0.jar<\/p>\n<p>Folgende Schritte f\u00fchrt das Skript aus:<\/p>\n<p>1. Setzen der PATH-Variable f\u00fcr die JDK-Tools<\/p>\n<p>2. L\u00f6schen eines vielleicht schon angelegten lib-Ordners<\/p>\n<p>3. Anlegen eines neuen lib-Orders f\u00fcr die JAR-Datei<\/p>\n<p>4. \u00dcbersetzen der zwei Java-Dateien in den Zielordner bin<\/p>\n<p>5. Anlegen einer JAR-Datei. &#8211;create (abk\u00fcrzbar zu \u2013c) instruiert das Werkzeug eine neue JAR-Datei anzulegen. \u2013file (oder kurz \u2013f) bestimmt den Zielnamen. \u2013module-version unsere Versionsnummer und \u2013C wechselt das Verzeichnis und beginnt ab dort die Dateien einzusammeln. Die Kommandozeilensyntax beschreibt Oracle auf der Webseite https:\/\/docs.oracle.com\/javase\/9\/tools\/jar.htm.<\/p>\n<p>6. Die Option &#8211;describe-module (oder kurz \u2013d) zeigt die Modulinformation und f\u00fchrt zu folgender (vereinfachten) Ausgabe: com.tutego.greeter@1.0 jar:file:\/\/\/C:\/\u2026\/com.tutego.greeter\/lib\/com.tutego.greeter@1.0.jar\/!module-info.class exports com.tutego.insel.greeter requires java.base.<\/p>\n<p>F\u00fcr das zweite Projekt ist die compile.bat sehr \u00e4hnlich, dazu kommt ein Aufruf der JVM, um das Programm zu starten.<\/p>\n<p>compile.bat<\/p>\n<p>set PATH=%PATH%;C:\\Program Files\\Java\\jdk-9\\bin<\/p>\n<p>rmdir \/s \/q lib<\/p>\n<p>mkdir lib<\/p>\n<p>javac -d bin &#8211;module-path ..\\com.tutego.greeter\\lib src\\module-info.java src\\com\\tutego\\insel\\main\\Main.java<\/p>\n<p>jar -c -f=lib\/com.tutego.main@1.0.jar &#8211;main-class=com.tutego.insel.main.Main &#8211;module-version=1.0 -C bin .<\/p>\n<p>java -p lib;..\\com.tutego.greeter\\lib -m com.tutego.main<\/p>\n<p>\u00c4nderungen gegen\u00fcber dem ersten Skript sind:<\/p>\n<p>1. Beim Compilieren m\u00fcssen wir den Modulpfad mit &#8211;module-path (oder k\u00fcrzer mit -p) angeben, weil ja das Modul com.tutego.greeter required ist.<\/p>\n<p>2. Beim Anlegen der JAR-Datei geben wir \u00fcber \u2013main-class die Klasse mit der main(\u2026)-Methode an.<\/p>\n<p>3. Startet die JVM das Programm, l\u00e4dt sie das Hauptmodul und alle abh\u00e4ngigen Module. Wir geben beide lib-Order mit den JAR-Dateien an und mit \u2013m das sogenannte initiale Modul f\u00fcr die Hauptklasse.<\/p>\n<h3>Automatische Module<\/h3>\n<p>JAR-Dateien spielen seit 20 Jahren eine zentrale Rolle im Java-System; sie vom einen zum anderen Tag abzuschaffen w\u00fcrde gro\u00dfe Probleme bereiten. Ein Blick auf https:\/\/mvnrepository.com\/repos offenbart \u00fcber 7,7 Millionen Artefakte; es gehen auch Dokumentationen und andere Dateien in die Statistik ein, doch es gibt eine Gr\u00f6\u00dfenordnung, wie viele JAR-Dateien im Umlauf sind.<\/p>\n<p>Damit JAR-Dateien unter Java 9 eingebracht werden k\u00f6nnen gibt es zwei L\u00f6sungen: das JAR in den Klassenpfad oder in den neuen Modulpfad zu setzen. Kommt ein JAR in den Modulpfad und hat es keine Modulinfodatei entsteht ein automatisches Modul. Bis auf eine kleine Einschr\u00e4nkung funktioniert das f\u00fcr die meisten existierenden Java-Bibliotheken.<\/p>\n<p>Ein automatisches Modul hat gewisse Eigenschaften f\u00fcr den Modulnamen und Konsequenzen in den Abh\u00e4ngigkeiten:<\/p>\n<p>\u00b7 Ohne Modulinfo haben die automatischen Module keinen selbstgew\u00e4hlten Namen, sondern sie bekommen vom System einen Namen zugewiesen, der sich aus dem Dateinamen ergibt.<a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Eigene Dokumente\/Insel\/Band 1\/#_ftn2_9164\" name=\"_ftnref2_9164\">[2]<\/a> Vereinfacht gesagt: Angeh\u00e4ngte Versionsnummern und die Dateiendung werden entfernt und alle nicht-alphanummerischen Zeichen durch Punkte ersetzt, jedoch nicht zwei Punkte hintereinander.<a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Eigene Dokumente\/Insel\/Band 1\/#_ftn3_9164\" name=\"_ftnref3_9164\">[3]<\/a> Die Version wird erkannt. Die Dokumentation gibt das Beispiel foo-bar-1.2.3-SNAPSHOT.jar an, was zum Modulnamen foo.bar und der Version 1.2.3-SNAPSHOT f\u00fchrt.<\/p>\n<p>\u00b7 Automatische Module exportieren immer alle ihre Pakete. Wenn es also eine Abh\u00e4ngigkeit zu diesem automatischen Modul gibt kann der Bezieher alle sichtbare Typen und Eigenschaften verwenden.<\/p>\n<p>\u00b7 Automatische Module k\u00f6nnen alle anderen Module lesen, auch die unbenannten.<\/p>\n<p>Auf den ersten Blick scheint eine Migration in Richtung Java 9 einfach: Alle JARs auf den Modulpfad und nacheinander Modulinfodateien anlegen. Allerdings gibt es JAR-Dateien, die von der JVM als automatisches Modul abgelehnt werden, wenn sie n\u00e4mlich Typen eines Paketes enthalten, und dieses Paket sich schon in einem anderen aufgenommen Modul befindet. Module d\u00fcrfen keine \u201esplit packages\u201c enthalten, also das gleiche Paket noch einmal enthalten. Die Migration erfordert dann entweder a) das Zusammenlegen der Pakete zu einem Modul, b) die Verschiebung in unterschiedliche Pakete oder c) die Nutzung des Klassenpfades<\/p>\n<h3>Unbenanntes Modul<\/h3>\n<p>Eine Migration auf eine neue Java-Version sieht in der Regel so aus, dass zuerst die JVM gewechselt und gepr\u00fcft wird, ob die vorhandene Software weiterhin funktioniert. Laufen die Testf\u00e4lle durch und gibt es keine Auff\u00e4lligkeiten im Testbetrieb kann der Produktivbetrieb unter der neuen Version erfolgen. Gibt es keine Probleme, k\u00f6nnen nach einiger Zeit die neuen Sprachmittel und Bibliotheken verwendet werden.<\/p>\n<p>\u00dcbertragen wir das auf den Wechsel von Java 8 auf Java 9: Eine vorhandene Java-Software muss inklusive aller Einstellungen und Eintr\u00e4ge im Klassenpfad weiterhin laufen. Das hei\u00dft, eine Java 9 Laufzeitumgebung kann den Klassenpfad nicht ignorieren. Da es intern nur einen Modulpfad gibt, m\u00fcssen auch diese JAR-Dateien zu Modulen werden. Die L\u00f6sung ist das unbenannte Modul (eng. unnamed module): jedes JAR im Klassenpfad \u2013 dabei spielt es keine Rolle, ob es eine modul-info.class enth\u00e4lt \u2013 kommt in das unbenannte Modul. Davon gibt es nur eines, wir sprechen also im Singular, nicht Plural.<\/p>\n<p>\u201eUnbenannt\u201c sagt schon, dass das Modul keinen Namen hat, und folglich auch keine Abh\u00e4ngigkeit zu den JAR-Dateien im unbenannten Modul existieren kann; das ist der Unterschied zu einem automatischen Modul. Ein unbenanntes Modul hat die gleiche Eigenschaft wie ein automatisches Modul, dass es alle Pakete exportiert. Und weil es zur Migration geh\u00f6rt, hat ein unbenanntes Modul auch Zugriff auf alle anderen Module.<\/p>\n<h3>Lesbarkeit und Zugreifbarkeit<\/h3>\n<p>Die Laufzeitumgebung sortiert Module in einem Graphen ein. Die Abh\u00e4ngigkeit der Module f\u00fchrt dabei zu sogenannten Lesbarkeit (engl. readibility): Ben\u00f6tigt Modul A das Modul B, so ist liest A das Modul B und B wird von A gelesen. F\u00fcr die Funktionsweise vom Modulsystem ist dies elementar, denn so werden zur \u00dcbersetzungszeit schon Fehler ausgeschlossen, wie Zkylen, oder gleiche Pakete in unterschiedlichen Modulen. Die Lesbarkeit ist zentral f\u00fcr eine zuverl\u00e4ssige Konfiguration, engl. reliable configuration.<\/p>\n<p>Ein Schritt weiter geht der Begriff der Erreichbarkeit\/Zug\u00e4nglichkeit (engl. accessibility). Wenn ein Modul ein anderes Modul grunds\u00e4tzlich lesen kann, bedeutet es noch nicht, dass es an alle Pakete und Typen kommt, denn nur die Typen sind sichtbar, die exportiert worden. Lesbare und erreichbare Typen nennen sich erreichbar.<\/p>\n<p>Die n\u00e4chste Frage ist, welcher Modultyp auf welchen anderen Modultyp Zugriff hat. Eine Tabelle fasst die Lesbarkeit am Besten zusammen:   <\/p>\n<table cellspacing=\"0\" cellpadding=\"0\" border=\"1\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"194\">\n<p>Modultyp<\/p>\n<\/td>\n<td valign=\"top\" width=\"193\">\n<p>Ursprung<\/p>\n<\/td>\n<td valign=\"top\" width=\"169\">\n<p>Exportiert Pakete<\/p>\n<\/td>\n<td valign=\"top\" width=\"194\">\n<p>Hat Zugriff auf<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"194\">\n<p>Plattformmodul<\/p>\n<\/td>\n<td valign=\"top\" width=\"193\">\n<p>JDK<\/p>\n<\/td>\n<td valign=\"top\" width=\"169\">\n<p>explizit<\/p>\n<\/td>\n<td valign=\"top\" width=\"194\">&nbsp;<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"194\">\n<p>Benannte Module<\/p>\n<\/td>\n<td valign=\"top\" width=\"193\">\n<p>Container mit Modulinfo im Modulpfad<\/p>\n<\/td>\n<td valign=\"top\" width=\"169\">\n<p>explizit<\/p>\n<\/td>\n<td valign=\"top\" width=\"194\">\n<p>Plattformmodule, andere benannte Module, automatische Module<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"194\">\n<p>Automatische Module<\/p>\n<\/td>\n<td valign=\"top\" width=\"193\">\n<p>Container ohne Modulinfo im Modulpfad<\/p>\n<\/td>\n<td valign=\"top\" width=\"169\">\n<p>alle<\/p>\n<\/td>\n<td valign=\"top\" width=\"194\">\n<p>Plattformmodule, andere benannte Module, automatische Module, unbenanntes Modul<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"194\">\n<p>Unbenanntes Modul<\/p>\n<\/td>\n<td valign=\"top\" width=\"193\">\n<p>Klassendateien und JARs im Klassenpfad <\/p>\n<\/td>\n<td valign=\"top\" width=\"169\">\n<p>alle<\/p>\n<\/td>\n<td valign=\"top\" width=\"194\">\n<p>Plattformmodule, benannte Module, automatische Module<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Lesbarkeit der Module<\/h3>\n<p>Der Modulinfodatei kommt dabei die gr\u00f6\u00dfte Bedeutung zu, denn sie macht aus einer JAR ein modular-JAR; fehlt die Modulinfomation bleibt es eine normale JAR, wie sie Java-Entwickler seit 20 Jahren kennen. Die JAR-Datei kann neu in den Modulpfad kommen oder in den bekannten Klassenpfad. Das ergibt vier Kombinationen:   <\/p>\n<table cellspacing=\"0\" cellpadding=\"0\" border=\"1\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"248\">&nbsp;<\/td>\n<td valign=\"top\" width=\"248\">\n<p>Modulpfad<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>Klassenpfad<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"248\">\n<p>JAR mit Modulinfomation<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>wird benanntes Module<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>wird ubenanntes Modul<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"248\">\n<p>JAR ohne Modulinfomation<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>wird automatisches Modul<\/p>\n<\/td>\n<td valign=\"top\" width=\"248\">\n<p>wird unbenanntes Modul<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>JARs im Pfad<\/p>\n<p>JAR-Archive im Klassenpfad sind das bekannte Verhalten, weswegen auch ein Wechsel von Java 8 auf Java 9 m\u00f6glich sein sollte.<\/p>\n<h3>Modul-Migration<\/h3>\n<p>Nehmen wir an, unsere monolithische Applikation hat keine Abh\u00e4ngigkeiten zu externen Bibliotheken und soll modularisiert werden. Dann besteht der erste Schritt darin, die gesamte Applikation in ein gro\u00dfes benanntes Modul zu setzen. Als n\u00e4chstes m\u00fcssen die einzelnen Bereiche identifiziert werden, damit nach und nach die Bausteine in einzelne Module wandern. Das ist nicht immer einfach, zumal zyklische Abh\u00e4ngigkeiten nicht unwahrscheinlich sind. Bei der Modularisierung des JDK hatten die Oracle-Entwickler viel M\u00fche.<\/p>\n<h3>Das Problem mit automatischen Modulen<\/h3>\n<p>Traditionell generieren Build-Werkzeuge wie Maven oder Gradle JAR-Dateien und ein Dateiname hat sich irgendwie ergeben. Werden jedoch diese JAR-Dateien zu automatischen Modulen spielt der Dateiname pl\u00f6tzlich eine gro\u00dfe Rolle. Doch bewusst wurde der Dateiname vermutlich nie gew\u00e4hlt. Referenziert ein benanntes Modul ein automatisches Modul bringt das zwei Probleme mit sich: \u00c4ndert sich der Dateiname, lassen wir die Versionsnummer einmal au\u00dfen vor, hei\u00dft auch das automatische Module anders, und die Abh\u00e4ngigkeit kann nicht mehr aufgel\u00f6st werden. Das zweite Problem ist gr\u00f6\u00dfer. Viele Java-Bibliotheken haben noch keine Modulinformationen, und folglich werden Entwickler eine Abh\u00e4ngigkeit zu diesem automatischen Modul \u00fcber den abgeleiteten Namen ausdr\u00fccken. Nehmen wir z. B. die beliebte Open-Source Bibliothek Google Guava. Die JAR-Datei hat den Dateinamen guava-23.0.jar \u2013 guava hei\u00dft folglich das automatische Modul. Ein benanntes Modul (nennen wir es M1) kann \u00fcber required guava eine Abh\u00e4ngigkeit ausdr\u00fccken. Konvertiert Google die Bibliothek in ein echtes Java 9-Modul, dann wird sich der Name \u00e4ndern \u2013 geplant ist com.google.guava. Und \u00e4ndert sich der Name, f\u00fchren alle Referenzierungen in Projekten zu einem Compilerfehler; ein Alias w\u00e4re eine tolle Idee, das gibt es jedoch nicht. Und das Problem besteht ja nicht nur im eigenen Code der Guava referenziert; Referenziert das eigene Modul M1 ein Modul M2, das wiederum Guava referenziert, so gibt es das gleiche Problem \u2013 wir sprechen von einer transitiven Abh\u00e4ngigkeit. Die \u00c4nderung vom Modulnamen von Guava wird zum Problem, denn wir m\u00fcssen warten, bis M2 den Namen korrigiert, damit M1 wieder g\u00fcltig ist.<\/p>\n<p>Eine L\u00f6sung mildert das Problem ab: In der JAR-Manifest-Datei kann ein Eintrag Automatic-Module-Name gesetzt werden \u2013 das \u201e\u00fcberschreibt\u201c den automatischen Modulnamen.<\/p>\n<p><strong>Beispiel: Apache Commons setzt den Namen so:<\/strong><\/p>\n<p><strong>Automatic-Module-Name: org.apache.commons.lang3<\/strong><\/p>\n<p>Benannte Module, die Abh\u00e4ngigkeiten auf automatische Module besitzen, sind also ein Problem. Es ist zu hoffen, dass die zentralen Java-Bibliotheken, auf die sich so viele L\u00f6sungen st\u00fctzen, schnell Modulinformationen einf\u00fchren. Das w\u00e4re eine L\u00f6sung von unten nach oben, englisch Bottom-Up. Das ist das einzige, was erfolgversprechend ist, aber wohl auch eine lange Zeit ben\u00f6tigen wird. Im Monat vom Java 9-Release hat noch keine wichtige Java-Bibliothek eine Modulinformation, Automatic-Module-Name kommt h\u00e4ufiger vor.<\/p>\n<hr size=\"1\" width=\"33%\" \/>\n<p><a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Eigene Dokumente\/Insel\/Band 1\/#_ftnref1_9164\" name=\"_ftn1_9164\">[1]<\/a> Zur Benennung von Modulen gibt es Empfehlungen in dem englischsprachigen Beitrag http:\/\/mail.openjdk.java.net\/pipermail\/jpms-spec-experts\/2017-May\/000687.html<\/p>\n<p><a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Eigene Dokumente\/Insel\/Band 1\/#_ftnref2_9164\" name=\"_ftn2_9164\">[2]<\/a> Automatic-Module-Name in die META-INF-Datei zu setzen ist eine Alternative, dazu sp\u00e4ter mehr. <\/p>\n<p><a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Eigene Dokumente\/Insel\/Band 1\/#_ftnref3_9164\" name=\"_ftn3_9164\">[3]<\/a> F\u00fcr Details siehe http:\/\/download.java.net\/java\/jigsaw\/docs\/api\/java\/lang\/module\/ModuleFinder.html#of-java.nio.file.Path&#8230;-<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Das JPMS (Java Platform Module System), auch unter dem Projektnamen Jigsaw bekannt, ist eine der gr\u00f6\u00dften Neuerungen in Java 9. Im Mittelpunkt steht die starke Kapselung: Implementierungsdetails kann ein Modul geheim gehalten. Selbst Hilfscode innerhalb des Moduls, auch wenn er \u00f6ffentlich ist, darf nicht nach au\u00dfen dringen. Zweitens kommt eine Abstraktion von Verhalten \u00fcber Schnittstellen [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","_links_to":"","_links_to_target":""},"categories":[11,85],"tags":[],"class_list":["post-3980","post","type-post","status-publish","format-standard","hentry","category-insel","category-java-9"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/3980","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/comments?post=3980"}],"version-history":[{"count":1,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/3980\/revisions"}],"predecessor-version":[{"id":3981,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/3980\/revisions\/3981"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=3980"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=3980"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=3980"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}