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:\javalibs\somelib\test.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 < args.length; 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
(Windows) set CLASSPATH=C:\javalibs\somelib\test.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 < args.length; 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




