Übersicht wird großgeschrieben

Classpath-Kontrolle mit JWhich

Sebastian Eschweiler
Wer in der Vergangenheit bereits mit umfangreicheren
Java-Projekten in Kontakt gekommen ist, wird schnell auf Probleme im Umgang
mit dem Classpath gestoßen sein. Insbesondere dort, wo viele unterschiedliche
Bibliotheken zum Einsatz kommen, kann es sehr schnell passieren, dass der Programmierer
den Überblick über den gesamten Classpath verliert. JWhich versucht mit sehr
einfachen Mitteln, die Übersicht über den Classpath zurückzugewinnen.

Zunächst stellt sich natürlich die Frage, wofür einen Classpath
und was ist ein Classpath? Die CLASSPATH-Variable ist eine System-Variable,
die Pfadangaben enthält, unter denen die verschiedenen Java-Klassen zu finden
sind, die in ein Projekt mit einbezogen werden sollen. Sowohl der Java-Compiler
als auch der Java-Interpreter beziehen ihre Informationen aus den Angaben des
Classpath. Der Classpath nimmt als Angabe Verzeichnisse (mit enthaltenen Java-Class-Dateien)
oder Archive (JAR-Dateien) auf. Wie die CLASSPATH-Variable im Einzelnen zu setzen
ist, hängt weitgehend vom verwendeten Betriebssystem ab. So wird unter Windows
das Semikolon als Trennzeichen zwischen den einzelnen Pfadangaben verwendet
und unter Unix / Linux ein Doppelpunkt.

(Windows) set CLASSPATH=C:javalibssomelibtest.jar;.
(Linux bash-Shell) export CLASSPATH=/home/user/javalibs/somelib/test.jar:.

Auch wenn auf den ersten Blick nicht erkennbar, erhalten beide Anweisungen zwei
Pfadangaben, die der CLASSPATH-Variablen zugewiesen werden. Der abschließende
Punkt stellt hierbei die Pfadangabe für das aktuelle Verzeichnis dar. So sind
Klassen, die sich im aktuellen Arbeitsverzeichnis befinden, automatisch „sichtbar“.

Classloader

Um nun eine bestimmte Klasse zu finden, wird ein so genannter Classloader eingesetzt.
Dieser Vorgang bleibt für den Benutzer unsichtbar und wird von der JVM verwaltet
und durchgeführt. In der Regel benötigt eine Applikation mehrere Classloader,
die in einer Baumhierarchie organisiert sind. Hierbei hat jeder Classloader
einen Elternteil, an welchen er Anfragen weiterreichen kann, falls diese nicht
direkt beantwortet werden können. Die Hierarchie ist so angelegt, dass bei der
Suche nach einer bestimmten Klasse, zunächst die Einträge der CLASSPATH-Variablen
durchlaufen werden. Einträge, die zuerst im Classpath erscheinen, werden auch
zuerst nach den betreffenden Klassen durchsucht. Diese – zunächst sehr einfache
Tatsache – bereitet in der Praxis aber immer wieder Probleme, da es ja durchaus
passieren kann, dass sich zwei Klassen mit identischem Namen im Classpath befinden.
Hier stellt sich dann jeweils die Frage, ob die zuerst gefundene Version auch
die richtige Version der Klasse darstellt. Sobald Archive von Fremdherstellern
mit eigenen Klassen innerhalb der CLASSPATH-Variablen gemischt werden, ergeben
sich die ersten Fallen, denn nur selten haben Sie dann noch einen Überblick,
welche Klassen momentan im Classpath enthalten sind. Somit kann es leicht passieren,
dass Klassen mit gleichem Namen mehrfach vorkommen und jeweils nur immer die
Klasse ausgewählt wird, die sich als Erstes im Classpath befindet.

JWhich – das Konzept

Wer schon einmal mit einem Linux-System gearbeitet hat, wird sicherlich schon
Bekanntschaft mit dem sehr nützlichen Tool which gemacht
haben. Hierdurch lassen sich Programme, die durch die PATH-Umgebungsvariable
zu erreichen sind, schnell mit den jeweiligen Pfaden in Verbindung bringen.
Als Beispiel nehmen wir an, der Java-Interpreter java ist
im PATH enthalten und ausführbar. Der Befehl

which java

liefert dann beispielsweise folgende Ausgabe :

/usr/lib/java2/bin/java

Wir erfahren somit, dass sich der Java-Interpreter unterhalb des Verzeichnisses
/usr/lib/java2/bin befindet. JWhich verfolgt
eben diesen Ansatz zum Auffinden von Java-Klassen. Im Prinzip stellt sich beim
Zugriff auf Klassen über den Classpath die Frage, welche Klasse letztendlich
zum Einsatz kommt. Am einfachsten lässt sich diese Frage durch die Ausgabe des
vollständigen Pfades zur jeweiligen Class-Datei beantworten.
Somit können Sie immer sicher sein, dass nicht noch eine weitere Klasse mit
demselben Namen existiert, die sich in einem anderen Verzeichnis bzw. JAR-Archiv
befindet – womöglich von einem Dritthersteller. Denkbar wäre also ein Aufruf
der folgenden Art:

java JWhich org.dbxml.core.Database

Wird die Klasse Database im Package org.dbxml.core
innerhalb des Classpath gefunden, erhalten Sie eine Ausgabe, die Ihnen das Verzeichnis
mitteilt, in dem die Klasse gefunden wurde:

Class ‚org.dbxml.core.Database‘ found in /home/seb/Java/dbXML/java/src/org/dbxml/core/Database.class

Als weitere Möglichkeit muss natürlich der Fall in Betracht gezogen werden,
dass die gesuchte Java-Klasse innerhalb eines JAR-Archives zu finden ist. In
diesem Fall erfolgt die Ausgabe nach folgendem Muster:

Class ‚javax.servlet.http.HttpServlet‘ found in ‚/home/seb/java/lib/servlet.jar!/javax/servlet/http/HttpServlet.class‘

Falls nun mehrere Ressourcen im Classpath eine Datei mit gleichem Namen enthalten,
liefert JWhich immer nur die Pfadangabe der ersten Fundstelle. Somit ist immer
sicher gestellt, dass die angezeigte Klasse auch Verwendung findet, wenn diese
in Ihrem Projekt eingebunden ist.

JWhich – die Technik

JWhich ist als Open-Source-Projekt verfügbar und ist auf www.clarkware.com/software/jwhich.zip
[1]zu finden. Entwickelt und initiiert wurde JWhich von Mike Clark. Da es sich
bei JWhich um ein kleineres und überschaubares Projekt handelt, bietet es sich
an dieser Stelle an, einen kurzen Blick auf die Implementierung zu werfen. In
Listing 1 sehen Sie den leicht gekürzten Quellcode der JWhich-Klasse.

Listing 1

import java.io.*;
import java.util.StringTokenizer;

public class JWhich {

private static String _classpath;

public static void which(String className) {

String resource = new String(className);

if (!resource.startsWith(„/“)) {
resource = „/“ + resource;
}
resource = resource.replace(‚.‘, ‚/‘);
resource = resource + „.class“;

java.net.URL classUrl = JWhich.class.getResource(resource);

if (classUrl == null) {
System.out.println(„nClass ‚“ + className +
„‚ not found.“);
} else {
System.out.println(„nClass ‚“ + className +
„‚ found in n'“ + classUrl.getFile() + „‚“);
}

printClasspath();
}

public static void validate() {

StringTokenizer tokenizer =
new StringTokenizer(getClasspath(), File.pathSeparator);

while (tokenizer.hasMoreTokens()) {
String element = tokenizer.nextToken();
File f = new File(element);

if (!f.exists()) {

System.out.println(„n'“ + element + „‚ “ +
„does not exist.“);

}

else if ( (!f.isDirectory()) &&
(!element.toLowerCase().endsWith(„.jar“)) &&
(!element.toLowerCase().endsWith(„.zip“)) ) {

System.out.println(„n'“ + element + „‚ “ +
„is not a directory, .jar file, or .zip file.“);

}
}

printClasspath();
}

public static void printClasspath() {

System.out.println(„nClasspath:“);
StringTokenizer tokenizer =
new StringTokenizer(getClasspath(), File.pathSeparator);
while (tokenizer.hasMoreTokens()) {
System.out.println(tokenizer.nextToken());
}
}

public static void setClasspath(String classpath) {
_classpath = classpath;
}

protected static String getClasspath() {
if (_classpath == null) {
setClasspath(System.getProperty(„java.class.path“));
}

return _classpath;
}

private static void instanceMain(String[] args) {

if (args.length == 0) {
printUsage();
}

for (int cmdIndex = 0; cmdIndex

String cmd = args[cmdIndex];

if („-validate“.equals(cmd)) {
validate();
} else if („-help“.equals(cmd)) {
//printUsage();
} else {
which(cmd);
}
}
}

public static void main(String args[]) {
JWhich.instanceMain(args);
}
}

Besonders zu beachten ist zunächst die Methode which(Sting className).
Der an das Programm übergebene String einer Klasse, wird dieser Methode übergeben
und anschließend verarbeitet. Der als Package übergebene Klassenname wird zunächst
umformatiert, sodass ein gültiger Verzeichnispfad entsteht. Hierzu müssen jeweils
alle Punkte innerhalb der Packageangaben durch „/“ ersetzt
werden. In der nächsten Zeile kann der umformatierte String dann in eine URL
umgesetzt werden. Hierzu dient folgende Anweisung:

java.net.URL classUrl = JWhich.class.getResource(resource);

Hierzu wird der Classloader der JWhich-Klasse eingesetzt. Nun kann anhand der
Überprüfung des classUrl-Objektes entschieden werden,
ob die Anfrage an den Classloader erfolgreich war oder der Rückgabewert null
geliefert wurde. In letzterem Fall bedeutet die Rückgabe, dass vom Class loader
keine geeignete Klasse ermittelt werden konnte – folglich keine Klasse mit entsprechendem
Namen im Classpath enthalten ist. Das Programm reagiert auf diesen Fall mit
einer entsprechenden Ausgabe. Neben der Methode jwhich(String
className)
, die das Auffinden einer übergebenen Klasse im Classpath
umsetzt, existiert zusätzlich noch die Methode validate().
Ausgeführt wird diese Methode nur dann, wenn beim Aufruf von JWhich zusätzlich
der Parameter -validate mit angegeben wurde.
validate() stellt in diesem Fall sicher, dass
der übergebene Classpath auch gültig ist. Hierzu wird zunächst eine Zerlegung
in die einzelnen Bestandteile des Klassenpfades vorgenommen:

StringTokenizer tokenizer = new StringTokenizer(getClasspath(), File.pathSeperator);

Nun kann jedes Element einzeln durchlaufen werden und auf Gültigkeit überprüft
werden. Da die Klassenangabe mit vorausgehendem Package immer durch ein Verzeichnis
wiedergespiegelt werden muss, gestaltet sich die Überprüfung an dieser Stelle
sehr einfach. Aus den einzelnen Elementen muss jeweils nur ein Objekt vom Typ
File erzeugt werden, um anschließend durch die Methode
exists() zu überprüfen, ob ein entsprechender
Pfad existiert:

if (!f.exists())
{
System.out.println(„n'“ + element + „‚ “ + „does not
exist.“);
}

Neben jwhich und validate existieren
noch eine Reihe weiterer Methoden, die jedoch weitgehend selbsterklärend sein
dürften: setClasspath(String Classpath) und
getClasspath() sind Methoden für den Zugriff
auf die interne _classpath-Variable. printClasspath()
übernimmt die formatierte Ausgabe des Verzeichnisses zur angefragten
Klasse und instanceMain() implementiert die
Auswertung der übergebenen Programm-Parameter und ruft die entsprechenden Methoden
auf.

Fazit

Mike Clark zeigt mit JWhich, wie auf einfache Weise mehr Durchblick im Programmieralltag
geschaffen werden kann. JWhich bietet schnelle Informationen zu Classpath-Informationen
und stellt so sicher, dass Klassenvertauschungen durch doppeltes Auftauchen
im CLASSPATH vermieden werden können. Besonders bei umfangreichen Projekten,
die auf viele Bibliotheken und Klassen von Drittherstellern zurückgreifen, kann
dieses Tool einen guten Überblick verschaffen. Schnell ist es passiert, dass
gleiche Klassen mit unterschiedlichen Versionen mehrfach im Classpath vorkommen
und deshalb Änderungen keine Wirkung zeigen, weil weiterhin eine frühere Version
der Klasse benutzt wird. Falls Sie also beim nächsten Mal nicht sicher sein
sollten, ob eventuelle Konflikte durch den Classpath hervorgerufen werden –
probieren Sie es einfach mal mit JWhich.

Links und Literatur

[1] www.clarkware.com/software/jwhich.zip

Geschrieben von
Sebastian Eschweiler
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.