Ich wollte der WebEngine aus JavaFX eine html gerechte Textressource als Seite übergeben. Leider fordert sie entweder eine Adressangabe dieser Seite in Form eines Strings, der URL gerecht ist, oder die vollständige html gerechte Seite als String. Der zweite Fall ist nur zu empfehlen, wenn der erste Fall garnicht funktionieren will, da damit keine Historie möglich ist. Damit wollte ich unbedingt die Adressangabe als Mittel nehmen.
Leider traten beim Versuch immer wieder unerwartete Unverträglichkeiten auf. Ich entschloss mich deshalb, das Laden von Resourcen im modularen Java ab Version 9 genauer zu untersuchen. Dabei beschränke ich mich aber auf Text-, Bild- und Tonresourcen. Das Laden von *.class bleibt unberücksichtigt.
Welche Methoden aus welchen Klassen gibt es um solche Resourcen zu laden?
Als Methoden gibt es mehrere:
InputStream getResourceAsStream( String name ); - in den Klassen Module, Class und ClassLoader URL getResource( String name ); - in den Klassen Class und ClassLoader static InputStream ClassLoader.getSystemResourceAsStream( String name ); - statische Methode im ClassLoader, dient eigentlich nur zum Laden von Klassen und benutzt den SystemClassLoader. Eigentlich sollen damit Dateien vom Typ *.class geladen werde. static URL ClassLoader.getSystemResource( String name ); - statische Methode im ClassLoader, der dann den SystemClassLoader benutzt. Eigentlich sollen damit Dateien vom Typ *.class geladen werden.
außerdem gibt es noch die Methode, die ich aber nicht weiter untersuche
static Enumeration<URL> getSystemResources( String name ) - Sie sucht alle Ressourcen ( eigentlich vom Typ *.class ) im angegebenen Suchpfad.
Die Klasse Module ist erst mit der Modularisierung von Java eingeführt worden, die anderen beiden gab es schon immer, scheinen aber erweitert bzw geändert worden zu sein.
Aufpassen muss man beim Parameter. Hier ist (meistens) die Schreibweise wichtig. Grundsätzlich muss natürlich der Name mit Endung der Ressource übergeben werden, aber muss mit oder ohne Paketpfad, mit oder ohne einem führenden Schrägstrich angegeben werden.
Mit Paketpfad ist die Paketangabe mit Schrägstrichen statt der Punkte gemeint. Ohne führenden Schrägstrich nenne ich ihn relativ und mit führendem Schrägstrich absolut.
Die Ressource befindet sich bei den folgende Dingen immer im Modul 'm', dort im Paket 'de.firma.res' und hat den Namen 'datei.res'.
1. Aufrufende Instanz und Resource befinden sich im gleichen Paket und damit auch im gleichen Modul.#
(Es gibt unbenannte Module. Dürfen sich da gleichnamige Pakete befinden?)- Methode 'InputStream strom = getResourceAsStream( String name )'
- Module
strom = getClass().getModule().getResourceAsStream( "de/firma/res/datei.res" ) ist ok strom = getClass().getModule().getResourceAsStream( "/de/firma/res/datei.res" ) ist ok strom = getClass().getModule().getResourceAsStream( "datei.res" ) ist nicht ok
Der Paketpfad muss mit angegeben werden. Dabei kann er absolut wie relativ angegeben sein.
- Class
strom = getClass().getResourceAsStream( "de/firma/res/datei.res" ) ist nicht ok strom = getClass().getResourceAsStream( "/de/firma/res/datei.res" ) ist ok strom = getClass().getResourceAsStream( "datei.res" ) ist ok
Findet die Datei ohne Paketpfad und mit absolutem Paketpfad.
strom = getClass().getClassLoader().getResourceAsStream( "de/firma/res/datei.res" ) ist nicht ok strom = getClass().getClassLoader().getResourceAsStream( "/de/firma/res/datei.res" ) ist nicht ok strom = getClass().getClassLoader.getResourceAsStream( "datei.res" ) ist nicht ok - Es klappt mit dem ClassLoder nur, wenn die Datei ins oberste Verzeichnis des Moduls verlegt wird und dann nur der Dateiname übergeben wird.
strom = ClassLoader().getResourceAsStream( "de/firma/res/datei.res" ); ist nicht ok strom = ClassLoader().getResourceAsStream( "/de/firma/res/datei.res" ); ist nicht ok strom = ClassLoader().getResourceAsStream( "datei.res" ); ist nicht ok
Befinden sich beide im gleichen Paket, wird nichts gefunden.
- Methode 'URL url = getResource( String name )'
- Module
Ein Modul hat keine Methode getResource( String name );
- Class
url = getClass().getResource( "de/firma/res/datei.res" ); ist nicht ok url = getClass().getResource( "/de/firma/res/datei.res" ); ist ok url = getClass().getResource( "datei.res" ); ist ok
Findet die Datei ohne Paketpfad und mit absolutem Paketpfad.
- ClassLoader der Klasse
url = getClass().getClassLoader().getResource( "de/firma/res/datei.res" ); ist nicht ok url = getClass().getClassLoader().getResource( "/de/firma/res/datei.res" ); ist ok url = getClass().getClassLoader().getResource( "datei.res" ); ist ok
Findet die Datei ohne Paketpfad und mit absolutem Paketpfad.
url = ClassLoader.getSystemResource( "de/firma/res/datei.res" ); ist nicht ok url = ClassLoader.getSystemResource( "/de/firma/res/datei.res" ); ist nicht ok url = ClassLoader.getSystemResource( "datei.res" ); ist nicht ok
Befinden sich beide im gleichen Paket, wird nichts gefunden.
2. Aufrufende Instanz und Ressource befinden sich in verschiedenen Paketen, aber gleichem Modul bzw jar-Datei.#
- Methode 'strom = getResourceAsStream(...)'.
- Modul
strom = getClass().getModul().getResourceAsStream( "de/firma/res/datei.res" ); ist ok strom = getClass().getModul().getResourceAsStream( "/de/firma/res/datei.res" ); ist ok strom = getClass().getModul().getResourceAsStream( "datei.res" ); ist nicht ok strom = getClass().getModul().getResourceAsStream( "../../../de/firma/res/datei.res" ); ist nicht ok Die Anzahl der ../ hängt vom Paketpfad des aufrufenden Objektes ab
Der Paketpfad muss mit angegeben werden. Dabei kann er absolut wie relativ angegeben sein.
- Class
strom = getClass().getResourceAsStream( "de/firma/res/datei.res" ); ist nicht ok strom = getClass().getResourceAsStream( "/de/firma/res/datei.res" ); ist ok strom = getClass().getResourceAsStream( "datei.res" ); ist nicht ok strom = getClass().getResourceAsStream( "../../../de/firma/res/datei.res" ); ist nicht ok Die Anzahl der ../ hängt vom Paketpfad des aufrufenden Objektes ab
Findet die Datei nur mit Angabe des absoluten Paketpfades.
strom = getClass().getClassLoader().getResourceAsStream( "de/firma/res/datei.res" ); ist nicht ok strom = getClass().getClassLoader().getResourceAsStream( "/de/firma/res/datei.res" ); ist nicht ok strom = getClass().getClassLoader().getResourceAsStream( "datei.res" ); ist nicht ok strom = getClass().getClassLoader().getResourceAsStream( "../../../de/firma/res/datei.res" ); ist nicht ok Die Anzahl der ../ hängt vom Paketpfad des aufrufenden Objektes ab
Den ClassLoader sollte man in Java mit Javamodulen lieber vergessen. Ich habe nicht geprüft, ob die Datei gefunden wird, wenn sie im obersten Paket des Modules ist.
**SystemClassLoader strom = ClassLoader().getResourceAsStream( "de/firma/res/datei.res" ); ist nicht ok strom = ClassLoader().getResourceAsStream( "/de/firma/res/datei.res" ); ist nicht ok strom = ClassLoader().getResourceAsStream( "datei.res" ); ist nicht ok
Auch in verschiedenen Paketen wird nichts gefunden( .res ist ja auch keine Klasse )
- Methode 'url = getResource( String name )'
- Modul
Ein Modul hat keine Methode getResource(...);
- Class
url = getClass().getResource( "de/firma/res/datei.res" ); ist nicht ok url = getClass().getResource( "/de/firma/res/datei.res" ); ist ok url = getClass().getResource( "datei.res" ); ist nicht ok url = getClass().getResource( "../../../de/firma/res/datei.res" ); ist nicht ok Die Anzahl der ../ hängt vom Paketpfad des aufrufenden Objektes ab
Findet die Datei nur mit Angabe des absoluten Paketpfades.
url = getClass().getClassLoader().getResource( "de/firma/res/datei.res" ); ist nicht ok url = getClass().getClassLoader().getResource( "/de/firma/res/datei.res" ); ist nicht ok url = getClass().getClassLoader().getResource( "datei.res" ); ist nicht ok url = getClass().getClassLoader().getResource( "../../../de/firma/res/datei.res" ); ist nicht ok Die Anzahl der ../ hängt vom Paketpfad des aufrufenden Objektes ab
Siehe Bemerkung oben bei getResourceAsStream(...).
url = ClassLoader.getSystemResource( "de/firma/res/datei.res" ); // ist ok url = ClassLoader.getSystemResource( "/de/firma/res/datei.res" ); // ist nicht ok url = ClassLoader.getSystemResource( "datei.res" ); // ist nicht ok
Ein erstaunliches Ergebnis! Befinden sich aufrufende Klasse und Ressource im gleichen Paket, wird nichts gefunden. Sind sie aber in verschiedenen Paketen, wird sie mit relativem Paketpfad gefunden.
3 Aufrufende Instanz und Ressource befinden sich in verschiedenen Modulen.#
Hat man das zugehörige Objekt der Klasse Module vom Modul 'm', ich nenne es 'modul' oder ein Klassenobjekt einer im Modul 'm' befindlichen Klassen, ich nenne sie 'klasse', und kennt man das Paket, indem sich die Resource befindet, führen folgende Aufrufe zum Erfolg. Das Modul 'm' muss allerdings auch im Modulpfad der Anwendung existieren.
Dann geht es genau wie oben mit der Methode getResourceAsStream(...) aus dem Module;
InputStream strom = modul.getResourceAsStream( Paketpfad + name der Datei im Modul modul );
oder mit Hilfe des Klassenobjektes aus dem Modul:
InputStream strom = klasse.getModule().getResourceAsStream( Paketpfad + name der Datei im Modul modul );
InputStream strom = klasse.getResourceAsStream( Paketpfad + name der Datei im Modul modul );
URL url = klasse.getResource( absoluter Paketpfad + name der Datei im Modul von 'klasse' );
Ebenfalls zum Erfolg führt folgendes sogar bei unbenannten Modulen. Eine jar-Datei, die nicht modularisiert ist, muss per Classpath eingebunden sein.
URL url = ClassLoder.getSystemResource( relativer Paketpfad + name der Datei im Modul der Resource ); URL url = ClassLoder.getSystemResourceAsStream( relativer Paketpfad + name der Datei im Modul der Resource );
Da wo der Paketpfad kein Attribut hat, darf er relativ wie auch absolut sein.
Ergebnis:#
Man ist auf der sichersten Seite, wenn man die Methoden aus dem Class-Objekt benutzt und im Parameter den absoluten Paketpfad mit angibt. Hat man allerdings weder ein Modul- noch eine Class-Objekt, da das Modul nur Resourcen enthält und damit auch keine Klasse, dann muss man ClassLoader.getSystemResource(...) oder ClassLoader.getSystemResourceAsStream(...) benutzen. Dabei ist es egal, ob es ein benanntes oder unbenanntes Modul ist. Es wird sich nach der jar-Datei gerichtet und intern Klasse JarUrlConnection benutzt. Was nun allerdings passiert, wenn ich all diese einzelnen jar-Dateien in einer einzigen jar-Datei zusammenführe, habe ich noch nicht ausprobiert.
Mit dem Eingangsstrom kann man nur die Resource einlesen und komplett übergeben oder dem Objekt, das die Resource zur gedachten Darstellung bringen soll, kann man den Eingangsstrom als Parameter übergeben.
In Swing nehmen viele solcher Objekte ein URL-Objekt oder File-Objekt, das man auch aus dem URL-Objekt bilden kann, an.
Mit url.toExternalForm() erhält man die URL-Adresse als String, wie es bei vielen Klassen in JavaFX gefordert wird.
Wer die Mühe nicht scheut, kann die URL-Adresse auch von Hand als String angeben: Sie beginnt mit "jar:file://", dann folgt das absolute Verzeichnis aus dem Dateisystem bis zum Verzeichnis der jar-Datei, anschließend kommt der Name der jar-Datei mit abschließendem Ausrufungszeichen, dann der absokute Paketpfad und zum Schluss der Name der Resource. Es gibt im Internet auch viele Beschreibungen, wo der doppelte Schrägstrich hinter "jar:file:" fehlt. Ob das auch in Ordnung ist, habe ich nicht ausprobiert. Im einfachen Fall reicht ein einfaches "file:" vor der Pfadangabe und dem Namen. 'uri.toExternalForm()' jedenfalls gibt immer das doppelte Schrägzeichen aus. Es fügt auch immer ein "./" zwischen dem Verzeichnis, aus dem java die Anwendung gestartet hat, und dem Rest des Verzeichnisses bis zur jar-Datei ein, was eindeutig weggelassen werden darf.
Es gibt noch das Problem, dass man jar-Dateien einbinden kann, die nicht nach Art von Java modularisiert sind. Hier muss man wohl ein Objekt der Klasse 'JarUrlConnection' oder die statischen Methoden des ClassLoaders bzw den SystemClassLoader benühen.
Kai Ehlers