Titel | Inhalt | Suchen | Index | API | Go To Java 2, Zweite Auflage, Handbuch der Java-Programmierung |
<< | < | > | >> | Kapitel 13 - Strukturierung von Java-Programmen |
Der ausführbare Programmcode einer Java-Applikation befindet sich in den vom Compiler erzeugten .class-Dateien. Sie sind der wichtigste Bestandteil bei der Auslieferung eines Java-Programms. Wir werden uns in diesem Abschnitt ansehen, auf welche Weise die Klassendateien weitergegeben werden können und was dabei zu beachten ist. Neben den Klassendateien müssen mitunter auch zusätzliche Dateien mit Übersetzungstexten, Fehlermeldungen, Icons oder Bilddateien weitergegeben werden. Wie diese zusammen mit den Klassendateien ausgeliefert und verwendet werden können, zeigt der nächste Abschnitt.
Die Ausführungen in diesem und dem nächsten Abschnitt sind möglicherweise schwierig zu verstehen, denn sie setzen Kenntnisse voraus, die teilweise erst in späteren Kapiteln vermittelt werden. Wegen ihres engen thematischen Bezuges zum vorliegenden Kapitel sind sie hier dennoch richtig aufgehoben, und Sie sollten den Ausführungen zumindest in groben Zügen folgen können. Lesen Sie diese Abschnitte bei Bedarf einfach zu einem späteren Zeitpunkt noch einmal, und viele Dinge, die jetzt unklar sind, werden dann leicht zu verstehen sein. |
|
Die einfachste Möglichkeit, ein Java-Programm auszuliefern, besteht darin, die Klassendateien so zu belassen, wie sie sind, und sie entsprechend ihrer Paketstruktur auf das Zielsystem zu kopieren. Das Programm kann dann auf dem Zielsystem gestartet werden, indem der Java-Interpreter aus dem Installationsverzeichnis aufgerufen und der Name der zu startenden Klasse als Argument angegeben wird.
Als Beispiel wollen wir uns eine hypothetische Applikation ansehen,
die aus folgenden Klassen besteht:
com.gkrueger.app.App
com.gkrueger.app.AppFrame
com.gkrueger.util.Logger
Die Klassen App und AppFrame befinden sich im Paket com.gkrueger.app und die Klasse Logger im Paket com.gkrueger.util. Ausgehend vom Basisverzeichnis, liegen die Klassendateien also in den Unterverzeichnissen com\gkrueger\app und com\gkrueger\util. Die main-Methode befindet sich in der Klasse App. Soll das Programm auf dem Zielsystem beispielsweise im Verzeichnis c:\gkapp installiert werden, müssen die drei Dateien wie folgt kopiert werden:
Das Programm kann dann aus dem Installationsverzeichnis c:\gkapp
durch Angabe des qualifizierten Klassennamens gestartet werden:
java com.gkrueger.app.App
Voraussetzung zum Starten der Applikation ist, daß der Klassenpfad
korrekt gesetzt ist. Soll das Programm stets aus dem Installationsverzeichnis
heraus gestartet werden, muß das aktuelle Verzeichnis "." im
CLASSPATH
enthalten sein (alternativ kann seit dem JDK 1.2 auch gar kein Klassenpfad
gesetzt sein). Soll das Programm von beliebiger Stelle aus gestartet
werden können, muß der Pfad des Installationsverzeichnisses
c:\gkapp in den Klassenpfad aufgenommen
werden. Dazu kann entweder die Umgebungsvariable CLASSPATH
modifiziert oder der gewünschte Klassenpfad mit einer der Optionen
-cp
oder -classpath
an den Java-Interpreter übergeben werden. Dafür wäre
beispielsweise folgender Aufruf geeignet:
java -cp c:\gkapp com.gkrueger.app.App
Die Variante, bei der die Umgebungsvariable unverändert bleibt, ist natürlich die bessere, denn Modifikationen an Umgebungsvariablen beeinflussen möglicherweise auch andere Programme. Zudem hat die explizite und präzise Übergabe des Klassenpfades an den Interpreter den Vorteil, daß nicht versehentlich weitere Verzeichnisse im Klassenpfad enthalten sind und unerwünschte Nebeneffekte verursachen können. |
|
Noch einfacher wird die Installation, wenn anstelle der verschiedenen Klassendateien eine einzelne jar-Datei ausgeliefert wird. Auf dem Zielsystem spielt es dann keine Rolle mehr, aus welchem Verzeichnis heraus die Applikation aufgerufen wird und wie die CLASSPATH-Variable gesetzt ist. Weitere Hinweise zur Anwendung des jar-Programms finden sich in Abschnitt 49.6. Dort wird auch erläutert, wie Applets in jar-Dateien ausgeliefert werden können.
Angenommen, das Basisverzeichnis für die Programmentwicklung
auf der Quellmaschine ist c:\prog\java
und die Klassendateien liegen in den Verzeichnissen c:\prog\java\com\gkrueger\app
und c:\prog\java\com\gkrueger\util. Um
eine jar-Datei zu unserem Beispielprojekt zu erstellen, ist aus dem
Entwicklungsverzeichnis c:\prog\java
heraus folgendes jar-Kommando
abzusetzen:
jar cvf myapp.jar com
Dadurch werden alle Dateien aus dem Unterverzeichnis com
und allen darin enthaltenen Unterverzeichnissen unter Beibehaltung
der Verzeichnishierarchie in die jar-Datei myapp.jar
kopiert. Um das Programm auf der Zielmaschine aufzurufen, reicht es
aus, myapp.jar an eine beliebige Stelle
zu kopieren und den Java-Interpreter wie folgt zu starten:
java -cp myapp.jar com.gkrueger.app.App
Anstelle des Unterverzeichnisses mit den Klassendateien haben wir nun die jar-Datei im Klassenpfad angegeben. Für den Interpreter sind beide Varianten gleichwertig und er wird das Programm in der gleichen Weise wie zuvor starten. Vorteilhaft an dieser Vorgehensweise ist, daß auf der Zielmaschine nur noch eine einzige Datei benötigt wird und nicht mehr ein ganzes System von Verzeichnissen und Unterverzeichnissen wie zuvor. Das vereinfacht nicht nur die Installation, sondern erhöht auch ihre Haltbarkeit, denn einzelne Dateien können nicht mehr überschrieben werden oder verlorengehen.
Das oben beschriebene jar-Kommando kopiert alle Dateien aus dem com-Unterverzeichnis und den darin enthaltenen Unterverzeichnissen in die jar-Datei. Dazu zählen natürlich auch die Quelldateien, sofern sie in denselben Verzeichnissen liegen. Da diese aber meist nicht mit ausgeliefert werden sollen, empfiehlt es sich, entweder die Klassendateien (unter Beibehaltung der Unterverzeichnisstruktur) zuvor in ein leeres Verzeichnis zu kopieren und das jar-Kommando von dort aus zu starten. Oder die Klassendateien werden schon bei der Entwicklung von den Quelltexten getrennt, indem beim Kompilieren die Option -d verwendet wird. |
|
Wir können sogar noch einen Schritt weitergehen und die jar-Datei selbst »ausführbar« machen. Dazu müssen wir eine Manifest-Datei erstellen, in der der Name der Klasse angegeben wird, die die main-Methode enthält. Anschließend läßt sich die jar-Datei mir der Option -jar des Interpreters ohne explizite Angabe der Hauptklasse starten.
Die Manifest-Datei ist eine Hilfsdatei mit Informationen über den Inhalt der jar-Datei. Sie hat den Namen manifest.mf und liegt im Unterverzeichnis meta-inf des jar-Archivs. Die Manifest-Datei kann mit einem normalen Texteditor erstellt und mit Hilfe der Option "m" in das jar-Archiv eingebunden werden. In unserem Fall muß sie lediglich einen Eintrag Main-Class enthalten. Weitere Informationen zu Manifest-Dateien finden sich in Abschnitt 44.3.3 bei der Beschreibung der Java-Beans-Architektur. |
|
Wir erstellen also zunächst eine Textdatei manifest.txt
mit folgendem Inhalt (bitte mit einer Zeilenschaltung abschließen,
sonst wird die Manifest-Datei nicht korrekt erstellt):
Main-Class: com.gkrueger.app.App
Nun kann das jar-Archiv erzeugt und die Manifest-Datei einbezogen
werden:
jar cvfm myapp.jar manifest.txt com
Dieses Archiv kann nun mit Hilfe der Option -jar
ohne Angabe des Klassennamens gestartet werden:
java -jar myapp.jar
Auch jetzt verhält sich das Programm genauso wie in den beiden zuvor beschriebenen Beispielen. Diese Variante ist vor allem nützlich, wenn die komplette Applikation in einem einzigen jar-Archiv ausgeliefert wird und nur eine einzige main-Methode enthält. Sie bietet sich beispielsweise für kleinere Programme, Beispiel- oder Testapplikationen an (die Demos des JDK sind beispielsweise in derartigen jar-Dateien untergebracht). Ist die Konfiguration dagegen komplizierter oder gibt es mehr als eine lauffähige Applikation in der jar-Datei, sollte der zuvor beschriebenen Variante der Vorzug gegeben werden.
Wie eingangs erwähnt, benötigt ein Programm neben den Klassendateien meist weitere Dateien mit zusätzlichen Informationen. Wird auf diese ausschließlich lesend zugegriffen, wollen wir sie als Ressourcen-Dateien bezeichnen, also als Dateien, die dem Programm neben den Klassendateien als zusätzliche Informationslieferanten zur Laufzeit zur Verfügung stehen. Die Ressourcen-Dateien könnten zwar als separate Dateien ausgeliefert und mit den Klassen zur Dateiverarbeitung eingelesen werden (diese werden ab Kapitel 18 vorgestellt). Das hätte jedoch den Nachteil, daß sich nicht mehr alle benötigten Dateien in einem jar-Archiv befinden würden und die Auslieferung dadurch verkompliziert würde.
Glücklicherweise gibt es im JDK die Möglichkeit, auch Ressourcen-Dateien in jar-Archiven unterzubringen und zur Laufzeit daraus einzulesen. Mit dem Klassenlader steht das dafür erforderliche Werkzeug allen Java-Programmen standardmäßig zur Verfügung. Während der Klassenlader normalerweise von der virtuellen Maschine dazu verwendet wird, Klassendateien einzulesen, kann er von der Anwendung zum Einlesen beliebiger Dateien zweckentfremdet werden. Einzige Bedingung ist, daß die Ressourcen-Dateien in einem Verzeichnis stehen, das vom Klassenlader erreicht werden kann, das also innerhalb des Klassenpfades liegt.
Dazu kann beispielsweise im Basisverzeichnis der Anwendung ein Unterverzeichnis resources angelegt und die Ressourcen-Dateien dort hineinkopiert werden. In unserem Fall würden wir also ein Unterverzeichnis com.gkrueger.resources anlegen. Mit Hilfe der Methode getResourceAsStream der Klasse Class (siehe Abschnitt 43.2.2) kann ein InputStream beschafft werden, mit dem die Datei Byte für Byte eingelesen werden kann:
public InputStream getResourceAsStream(String name) |
java.lang.Class |
Als Parameter wird der vollständige Name der Ressourcen-Datei angegeben, allerdings unter Beachtung einiger Besonderheiten. Zunächst werden die einzelnen Paketnamen nicht wie gewohnt durch Punkte, sondern durch Schrägstriche "/" voneinander getrennt (auch unter Windows wird dazu nicht der Backslash verwendet). Nur die Dateierweiterung wird wie üblich hinter einem Punkt angegeben. Zudem muß als erstes Zeichen ebenfalls ein Schrägstrich angegeben werden. Soll also beispielsweise die Datei hello.txt aus dem Ressourcenverzeichnis geladen werden, so lautet der an getResourceAsStream zu übergebende Dateiname /com/gkrueger/resources/hello.txt.
Das folgende Listing zeigt eine einfache Methode, in der die angesprochenen Regeln implementiert werden. Sie beschafft zu einer beliebigen Datei, die sich in einem vorgegebenen Ressource-Verzeichnis befindet, das innerhalb des Klassenpfades liegt, einen InputStream, mit dem die darin enthaltenen Daten eingelesen werden können:
001 private InputStream getResourceStream(String pkgname, String fname) 002 { 003 String resname = "/" + pkgname.replace('.', '/') + "/" + fname; 004 Class clazz = getClass(); 005 InputStream is = clazz.getResourceAsStream(resname); 006 return is; 007 } |
Als Argument wird der Paketname (in Punktnotation) und der Name der Ressourcen-Datei angegeben. Den obigen Regeln entsprechend werden beide in einen Ressourcen-Namen umgewandelt und an getResourceAsStream übergeben. Diese liefert einen InputStream, der zum Einlesen der Daten verwendet werden kann. Soll beispielsweise eine Text-Ressource in einen String geladen werden, kann dazu folgende Methode verwendet werden:
001 /* TestResource.inc */ 002 003 import java.io.*; 004 import java.awt.*; 005 006 //... 007 008 public String loadTextResource(String pkgname, String fname) 009 throws IOException 010 { 011 String ret = null; 012 InputStream is = getResourceStream(pkgname, fname); 013 if (is != null) { 014 StringBuffer sb = new StringBuffer(); 015 while (true) { 016 int c = is.read(); 017 if (c == -1) { 018 break; 019 } 020 sb.append((char)c); 021 } 022 is.close(); 023 ret = sb.toString(); 024 } 025 return ret; 026 } 027 028 //... |
TestResource.inc |
Auch das Laden von Image-Ressourcen (Programm-Icons, Abbildungen etc.) kann auf ähnliche Weise realisiert werden:
001 /* ImageResource.inc */ 002 003 import java.io.*; 004 import java.awt.*; 005 006 //... 007 008 public Image loadImageResource(String pkgname, String fname) 009 throws IOException 010 { 011 Image ret = null; 012 InputStream is = getResourceStream(pkgname, fname); 013 if (is != null) { 014 byte[] buffer = new byte[0]; 015 byte[] tmpbuf = new byte[1024]; 016 while (true) { 017 int len = is.read(tmpbuf); 018 if (len <= 0) { 019 break; 020 } 021 byte[] newbuf = new byte[buffer.length + len]; 022 System.arraycopy(buffer, 0, newbuf, 0, buffer.length); 023 System.arraycopy(tmpbuf, 0, newbuf, buffer.length, len); 024 buffer = newbuf; 025 } 026 //create image 027 ret = Toolkit.getDefaultToolkit().createImage(buffer); 028 is.close(); 029 } 030 return ret; 031 } 032 033 //... |
ImageResource.inc |
Voraussetzung ist natürlich, daß das Format der in den Puffer buffer eingelesenen Datei von der Methode createImage verstanden wird. Beispiele für gültige Formate sind gif oder jpeg.
Der große Vorteil bei dieser Vorgehensweise ist, daß sie sowohl mit separaten Klassendateien funktioniert, als auch, wenn die komplette Applikation inklusive der Ressourcen-Dateien innerhalb einer einzigen jar-Datei ausgeliefert wird. Für die Anwendung selbst ist es vollkommen gleichgültig, aus welcher Quelle die Dateien stammen.
Titel | Inhalt | Suchen | Index | API | Go To Java 2, Zweite Auflage, Addison Wesley, Version 2.0 |
<< | < | > | >> | © 2000 Guido Krüger, http://www.gkrueger.com |