Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 13 - Strukturierung von Java-Programmen

## 13.4 Auslieferung von Java-Programmen

- 13.4 Auslieferung von Java-Programmen
  - 13.4.1 Weitergabe des Bytecodes
    - Direkte Weitergabe der Klassendateien
    - Verwendung eines jar-Archivs
    - Ausführbare jar-Archive
  - 13.4.2 Einbinden von Ressourcen-Dateien

### 13.4.1 Weitergabe des Bytecodes

#### Direkte Weitergabe der Klassendateien

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. |  |
Warnung

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:

- App.class nach c:\gkapp\com\gkrueger\app
- AppFrame.class nach c:\gkapp\com\gkrueger\app
- Logger.class nach c:\gkapp\com\gkrueger\util

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. |  |
Hinweis

#### Verwendung eines jar-Archivs

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 50.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. |  |
Warnung

#### Ausführbare jar-Archive

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. |  |
Hinweis

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.

### 13.4.2 Einbinden von Ressourcen-Dateien

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 }

Listing 13.7: Einen InputStream zu einer Ressourcen-Datei beschaffen

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

Listing 13.8: Laden einer Text-Ressource

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

Listing 13.9: Laden einer Image-Ressource

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 | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de