Slyh: Pfad der eigenen Klasse feststellen oder rel. Pfad finden

Beitrag lesen

Hallo,

das da oben ist der zugegeben schlechte Versuch eine angemessenen Titel für das Problem zu finden. Die Sache ist folgende: Ich habe einige Java-Klassen die auf verschiedene XML-Konfigurationsdateien zugreifen sollen. In meiner Entwicklungsumgebung (und in der späteren Anwendungsumgebung) sollen diese relativ zur Klasse im Unterordner config liegen.

Eine interessante Problematik, vor der übrigens nicht nur Java-Programme
stehen, sondern eigentlich jedes Programm, das in irgend einer Form
auf Dateien in seinem "eigenen Verzeichnis" zugreifen muß.

Das Problem ist, daß dieses "eigene Verzeichnis" eigentlich nur
theoretisch existiert. Wie du ja selbst schon festgestellt hast, ist
das aktuelle Arbeitsverzeichnis nicht zwingend auch das Verzeichnis,
in dem sich die ausführbare Programmdatei befindet.

Unter Windows läßt sich beispielweise für jede Verknüpfung festlegen,
in welchem Verzeichnis die dort referenzierte Datei ausgeführt werden
soll. (Siehe das Textfeld "Ausführen in:" bei den Verknüpfungs-
Eigenschaften.)

Prinzipiell bestehen wohl 4 Möglichkeiten, mit dieser Problematik
umzugehen:

1.) Man geht davon aus, daß das Arbeitsverzeichnis stets auch das
Verzeichnis ist, in dem sich die Programmdatei befindet. Alle Datei-
Operationen führt man daher relativ zum aktuellen (Arbeits-)Verzeichnis
durch.
Wie wir festgestellt haben, muß das Arbeitsverzeichnis nicht mit dem
Programm-Verzeichnis übereinstimmen, somit handelt es sich hierbei
um die schlechteste Vorgehensweise.

2.) Man legt die Konfigurationsdateien an eine zentrale Stelle im
System ab, die unabhängig vom Programm-Verzeichnis immer absolut
erreichbar ist. Im Falle von Linux wäre dies bspw. das Home-Verzeichnis
des aktuellen Benutzers. Im Falle von Windows wohl soetwas wie
"c:\Dokumente und Einstellungen\Ich".
Damit ist sichergestellt, daß die Daten immer auffindbar sind.
Nachteil: Bei einer Konfigurationsdatei mag sowas noch funktionieren.
Wenn allerdings mehrere oder größere Dateien zugreifbar sein sollen,
ist ein Umkopieren in das Home-Verzeichnis wohl kaum wirklich hilfreich.

3.) Ablage _einer_ Konfigurationsdatei an der genannten zentralen
Stelle. In dieser Konfigurationsdatei ist u.a. angegeben, wo sich das
Programmverzeichnis und damit die anderen Dateien, die für die
Programmausführung benötigt werden, befinden.
Unter Windows könnte zur Ablage auch die Registry verwendet werden.

Nachteil: Ein einfaches Verschieben der Programmdateien ist nach der
Installation nicht mehr möglich, da die Pfade "fest" in der Konfigurations-
Datei abgelegt sind. (Natürlich könnte man die entsprechenden
Einträge in der Konfigurations-Datei oder der Registry einfach
von Hand korrigieren.)

Diese 3. Möglichkeit wird übrigens von der meisten Windows-Programmen
realisiert. Üblicherweise legen Windows-Programme ihre gesamte
Konfiguration in der Registry ab, und verweisen von dort auf das
Programmverzeichnis. Da auf die Registry global zugegriffen werden
kann, kann das Programm stets ermitteln, wo es installiert ist.

(Ein Nachteil der Registry von Windows ist sicherlich, daß diese bei
einer Neuinstallation von Windows üblicherweise nicht gesichert wird
bzw. werden kann, und daher alle Programme neu installiert und
konfiguriert werden müssen.)

Seit JDK 1.4 bietet Java zur globalen Ablage von Konfigurationsdaten
übrigens das Package java.util.prefs, und im speziellen die Klasse
java.util.prefs.Preferences an.

Aus dem JavaDoc der Klasse "Preferences":
"A node in a hierarchical collection of preference data. This class
allows applications to store and retrieve user and system preference
and configuration data. This data is stored persistently in an
implementation-dependent backing store. Typical implementations
include flat files, OS-specific registries, directory servers and SQL
databases. The user of this class needn't be concerned with details
of the backing store."

Es lassen sich also plattformunabhängig hierarchisch Konfigurations-
Daten ablegen. Zur Speicherung wird ein plattformspezifische Ablageort
gewählt. Unter Windows ist dies meines Wissens die Registry. Da die
Ablage aber für den Programmierer völlig transparent erfolgt, hat
das eigentlich gar nicht zu interessieren.

(Ich selbst habe ich die Preferences-Klasse noch nie verwendet, so daß
ich keine weitere Aussage bzgl. des Für und Wider treffen kann.)

Es gibt noch eine weitere Möglichkeit zum Auffinden von Dateien des
Programms:

4.) Java bietet die Möglichkeit Dateien, die sich irgendwo im aktuellen
Classpath befinden, aufzufinden. Da sich die Programmdatei des
Hauptprogramms üblicherweise im Classpath befindet -- auch wenn dies
nicht immer der Fall sein muß -- lassen sich alle Dateien, die sich
im selben oder einem der untergeordneten Verzeichnisse zum
Hauptprogramm befinden, auf diese Weise auffinden und laden.

Java bietet die Methode java.lang.Class.getResource() an, die eine
Datei durch den ClassLoader, der die Klasse des Class-Objekts, auf das
die Methode aufgerufen wird, auffinden läßt.
Üblicherweise hat dieser ClassLoader mindestens das Verzeichnis, in
dem sich die genannte Hauptprogramm-Klasse befindet, in seinem
Classpath.

Beispiel:

class MyMainProgram
{
    public MyMainProgram()
    {
        // Tu was...
        // [...]

URL myConfigFile = getClass().getResource("/config/general.cfg");

// Lade die Daten aus "general.cfg".
        // [...]
    }
}

Was passiert hier?
Die Methode "getClass()", die in jedem Objekt existiert, liefert das
Class-Objekt der Klasse. (Genauer gesagt das Class-Objekt zur Klasse,
in deren Instanz wir uns gerade befinden.)
Diese Klasse sei das Hauptprogramm.

Der Klasse ist intern der ClassLoader zugeordnet, der die Klasse
geladen hat. Dieser ClassLoader hat den Pfad der Klasse in seinem
Classpath. Alle Aufrufe von "getResource()" auf das Class-Objekt werden an diesen ClassLoader gegegeben. Dieser wiederum versucht die
genannte Datei in seinem Classpath zu finden, und liefert im
Erfolgsfall ein URL-Objekt mit einer Referenz auf eben diese Datei
zurück.
Die Datei selbst muß dabei nicht einmal zwingend in einem Verzeichnis
liegen, das im Classpath ist. Wie im Beispiel zu sehen ist, reicht
es, daß sich das Verzeichnis "config" in einem Verzeichnis befindet,
das sich im Classpath befindet.

Um wieder auf das Ausgangsproblem zurückzukommen:
Die Konfigurationsdateien lassen sich über die Methode "getResource()"
finden und dann laden, sofern sich die Klasse des Hauptprogramms im
Classpath befindet. (Das ist üblicherweise nur dann nicht der Fall,
wenn die Klasse z.B. über einen eigenen ClassLoader oder den
URLClassLoader "manuell" anhand eines Dateinamens nachgeladen wurde.)

Nachteil: Wenn sich im aktuellen Classpath sehr viele Verzeichnisse
(oder auch JAR-Dateien) befinden, ist es durchaus möglich, daß sich
in mehreren dieser Verzeichnisse bspw. eine Datei "general.cfg"
befindet -- evtl. sogar in Kombination mit dem Verzeichnis "config",
somit also "/config/general.cfg" mehrmals im Classpath vorkommt.

Mir ist nicht bekannt, auf welche Weise Java (bzw. der ClassLoader)
diesen Konflikt auflöst. Ich vermute, daß einfach die Datei genommen
wird, die in dem Verzeichnis liegt, das weiter vorne im Classpath
steht. (Man kann damit allgemein sagen, daß das Verhalten bei
Doppeldeutigkeiten des Dateinamens undefiniert ist, da man den
Classpath evtl. selbst gar nicht kontrollieren kann.)

Umgehen kann man dieses Problem sicherlich durch die Vergabe von
eindeutigen Verzeichnis- oder Dateinamen, z.B. durch Voranstellen des
Package-Namens ("/de-slyh-mycoolapp-config/general.cfg") oder
dergleichen.

Natürlich läßt sich die letztbeschriebene Möglichkeit des Findens von
Dateien auf dem Classpath mit der Ablage des Programmverzeichnisses
in einer Konfigurationsdatei kombinieren. Hierzu legt man die
Konfigurationsdatei (mit einem möglichst eindeutigen Namen) in dasselbe
Verzeichnis ab, in dem sich auch die Programmdatei des Hauptprogramms
befindet, so daß sie vom ClassLoader gefunden werden kann. In die
Konfigurationsdatei speichert man den Programmpfad ab.

Im Hauptprogramm ruft man dann einfach beim Programmstart die Methode
"getResource()" des ClassLoaders auf, läßt sich die Konfigurations-
Datei ermitteln, und liest aus dieser den Programmpfad aus.
Alle nachfolgenden Dateioperationen läßt man dann nicht mehr über
denn ClassLoader abwickeln, sondern verwendet den Absolutpfad aus
der Konfigurations-Datei.
Grund: Da der ClassLoader beim Aufruf von "getResource()" immer den
gesamten Classpath nach der gewünschten Datei absuchen muß, wäre bei
vielen zu suchenden Dateien die Performanz ganz sicher nicht die beste...

Gruß
Slyh