In diesem Abschnitt werden zunächst die Konzepte der Sprache Java sowie Sicherheitsprobleme, die sich bei ihrem Einsatz ergeben, vorgestellt. Java Java ist eine objektorientierte, plattformunabhängige Programmiersprache, die von der Tochterfirma von Sun Microsystems, JavaSoft, entwickelt und vertrieben wird. Auch wenn Java eine vollständige Programmiersprache ist, ist sie insbesondere zur Erstellung interaktiver Webseiten interessant, da sie eine Einbettung von in Java programmierten Komponenten in Webseiten erlaubt (sog. Applets). Im Unterschied dazu bezeichnet man eine Java-Anwendung, die ohne Verwendung eines Browsers ausgeführt werden kann, und die daher ein eigenständiges Programm darstellt, als Application. Die genauen Unterschiede zwischen Applets und Applications hinsichtlich der Programmierung werden in Kapitel 4.1 noch detailliert erklärt. Plattformunabhängigkeit: Java VM und Java API Eine der bereits angesprochenen Besonderheiten von Java ist die Plattformunabhängigkeit. Unter einer Plattform versteht man hierbei die Hardware- oder Software-Umgebung, in der ein Programm abläuft. Typischerweise wird ein in einer Programmiersprache wie z. B. C++ erstelltes Programm von einem Compiler übersetzt und so aus der Form einer vom Menschen verständlichen Hochsprache in eine maschinenausführbare Sprache transferiert. Diese Übersetzung (Kompilierung) ist eng gekoppelt an die jeweilige Hardware, auf der der Compiler ausgeführt wird. Programme, die z. B. auf einem PC unter Windows 98 übersetzt wurden, sind auf einer UNIX-Workstation nicht ausführbar und müssen dort neu übersetzt werden, um lauffähig zu sein. Die Java-Plattform unterscheidet sich weiterhin von anderen Betriebssystemen, die meist in einer Kombination aus Hardware- und Software-spezifischen Funktionen bestehen. Java ist ausschließlich Software-basiert und läuft daher auf anderen, Hardware-basierten Plattformen. Die Java-Plattform besteht aus zwei Komponenten (siehe auch Abb. 1-1): der Java Virtual Machine (Java-VM) und dem Java Application Programming Interface (Java-API). Im Gegensatz zu Programmiersprachen wie C oder C++ verwendet Java bei der Übersetzung Bytecode, der vor der Ausführung auf einem speziellen Rechner von der sog. Java Virtual Machine in die jeweilige Maschinenrepräsentation überführt wird. Java-Bytecode kann hierbei als Instruktionsfolge von Maschinencodes für die Java Virtual Machine (Java-VM) angesehen werden. Jeder Java-Interpreter, der Java-Applets ausführen kann (bspw. Web-Browser oder Java-Entwicklungsumgebungen) muss daher eine Implementierung der Java VM sein. Die Java-VM kann hierbei auch in Hardware implementiert sein. Java-Bytecode wird in der Regel einmal geschrieben und auf vielen verschiedenen Plattformen, die einen Java-Compiler zur Verfügung stellen, ausgeführt. Dies stellt einen erheblichen Gegensatz zu anderen Sprachen wie C oder C++ dar, deren Code meist angepasst (portiert) werden muss, bevor er auf anderen Hardware-Plattformen übersetzt werden kann. Eine weitere wichtige Eigenschaft ist hierbei, dass der Bytecode auch auf jeglicher Implementierung der Java-VM ausgeführt werden kann. Dasselbe Java-Programm kann daher bspw. auf Windows 98, Windows NT oder auch unter Sun Solaris betrieben werden. Abb. 1.1: Java-Programm, Java-VM und Java-API Werden Java-Applets auf einem kompatiblen Browser ausgeführt, so fungiert dieser als virtuelles Betriebssystem und stellt wiederum den Kontakt zum tatsächlichen Betriebssystem her. Dies garantiert, dass Bytecode auf beliebigen Maschinen ausgeführt werden kann. Manchmal jedoch verwenden Programmierer spezielle, z. B. nur unter Windows 98 verfügbare Kommandos zur Grafikprogrammierung. Diese werden zwar auch in Bytecode übersetzt, finden aber auf anderen Betriebssystemen kein Gegenstück. Daher ist das Konzept der Plattformunabhängigkeit durch die immer wieder vorkommenden Standardabweichungen nur teilweise realisiert. Das Java Application Programming Interface (API) ist eine umfassende Sammlung von vorgefertigten Software-Komponenten, die nützliche Eigenschaften, wie bspw. Teilbausteine für Graphical User Interfaces (GUIs, dt. grafische Benutzerschnittstellen) zur Verfügung stellen. Die Java-API ist zur besseren Strukturierung in Bibliotheken (sog. Libraries bzw. Packages) eingeteilt. Eine ausführliche Darstellung der Java-Packages findet sich in Kapitel 3.2. In Abb. 1-1 ist ein Java-Programm dargestellt (Java-Applet oder Java-Application), das in der Java-Plattform ausgeführt wird. Hierbei wird das Programm von der Java-API und von der Java-VM von der jeweiligen Hardware abgeschirmt. Die Entwicklung eines in Java programmierten Applets und die Einbettung in eine Webseite kann sehr einfach erfolgen. Man übersetzt dazu mit einem Compiler den Quellcode des Applets. Dadurch wird eine sog. Class-Datei erzeugt. Diese wird dann, wie in Abb. 1-2 gezeigt, in eine in HTML geschriebene Webseite integriert. Abb. 1.2: Einbindung von Applets in Webseiten In diesem Beispiel wurde ein Applet mit Namen Beispiel erzeugt, in die Datei Beispiel.class übersetzt und in der Größe 240 sowie in der Breite 240 in eine Webseite integriert. Objektorientierung Java ist als modulare Programmiersprache aus Einheiten, sog. Klassen, aufgebaut. Eine Klasse legt hierbei fest, welche Konstanten, Variablen und Methoden hierin verwendet werden dürfen. Mit einer Klasse kann man Objekte der Klasse bilden, die über instantiierte Variablen und Konstanten der Klasse verfügen. Die in der Klasse beschriebenen Methoden (Funktionen) bestimmen, wie die Daten verarbeitet werden und regeln weiterhin die Kommunikation mit anderen Objekten. Beispiel: Man betrachte das Beispiel der Klasse Fahrzeuge. Variablen sind hier bspw. die Anzahl der Räder, die PS-Leistung, die Farbe oder der Hubraum. Methoden sind u. a. fahren, anhalten oder bremsen. Ein Objekt der Klasse Fahrzeug ist bspw. ein Mercedes. Hier sind die Variablen nun mit konkreten Werten belegt (bspw. Farbe rot, PS-Leistung 133 PS). Ein weiteres wichtiges Merkmal der Objektorientierung ist die Vererbung. Hierbei erbt eine Klasse, die von einer Vaterklasse abgeleitet ist, deren Attribute (Variablen und Prozeduren), verfügt aber meist über weitere Eigenschaften, die die Vaterklasse nicht hat. Betrachtet man das Beispiel der Klasse Fahrrad, die von der Klasse Fahrzeug abgeleitet ist, so wäre eine dieser weiteren Funktionen schieben, eine Funktion, die wiederum bei der Vererbung auf eine Klasse Auto (in den meisten Fällen) nicht erforderlich ist. Die zum Verständnis der Objektorientierung notwendigen Kenntnisse werden in Kapitel 2 noch detailliert beschrieben. Es ist aber unmittelbar einsichtig, dass dieses Konzept ein wertvolles Werkzeug bei der modularen Planung von Programmen darstellt. Einfachheit von Java Im Gegensatz zu C++ verzichtet man in Java auf eine Reihe von Funktionen, die häufig Ursachen von Fehlern sind, bspw. auf Zeigervariablen (Pointer) und auf Mehrfachvererbungen (Polymorphismen). Zeiger sind Konstrukte, die auf Speicherbereiche zeigen. Man kann sich leicht vorstellen, dass ein Programm abstürzen muss, wenn man einen Zeiger aus Versehen auf den Programmbereich im Speicher umsetzt und diesen dann modifiziert. Unter einer Mehrfachvererbung versteht man, dass eine Klasse Eigenschaften von mehreren Elternklassen erben kann. Dies kann unter Umständen zu einer erheblichen Verwirrung und damit zu Fehlern in der Programmierung führen. Typisierung Java arbeitet äußerst präzise mit Variablen und wird daher auch als streng typisierte Sprache bezeichnet. Techniken wie die automatische Typenumwandlung (Type Casting) sind in Java ausdrücklich verboten. Bei der Typenumwandlung weist man bspw. in C oder C++ einem Speicherbereich einen neuen Typ zu. Man betrachte hierzu folgendes Beispiel: Das Einlesen einer Datei in den Hauptspeicher ist dann ein langsamer Vorgang, wenn man in kleinen Einheiten, bspw. in Zeichen liest. Ist nun bekannt, dass in einer derartigen Datei nur Zahlen à 4 Ziffern stehen und weiß man, wie lang diese Datei ist, so kann man den gesamten Text in einem Schritt in einen Speicherbereich kopieren. Anschließend weist man diesem Bereich einen neuen Typ, bspw. einen benutzerdefinierten Typ Zahl, zu. Dies klingt zwar äußerst verlokkend, birgt aber das Risiko von Programmierfehlern. Ein Programm, das dieses Vorgehen anwendet, ist bei einer anschließenden Verarbeitung der Zahlen dann zum Scheitern verurteilt, wenn aus Versehen keine Zahlen, sondern Buchstaben oder Steuerzeichen eingelesen wurden. Organisation des Arbeitsspeichers Möchte man in einer Programmiersprache neue Objekte erzeugen und mit diesen arbeiten, so muss hierfür Arbeitsspeicher reserviert werden. Eine häufige Fehlerquelle, die man bspw. in C oder in C++ findet, liegt darin begründet, dass entweder der Speicherplatz dieser Objekte zu früh freigegeben wird und ein anschließender Zugriff „im Nichts" endet oder dass vergessen wird, den Bereich wieder freizugeben. Das Vergessen der Freigabe ist ein häufig anzutreffender Fehler, der sich mit der Zeit aufsummiert. Dies führt dazu, dass ein Programm bei einer Neuanforderung eines Objekts in Ermangelung an freiem Speicherplatz abbricht. Im Gegensatz hierzu verfügt Java über die Garbage Collection, eine Funktion, die nicht mehr benötigten Arbeitsspeicher selbstständig wieder freigibt. Schnelligkeit Java wurde ursprünglich für den Einsatz in elektronischen Konsumgeräten konzipiert. Das Laufzeitsystem, der zur Ausführung von Java-Programmen benötigte Interpreter, geht deshalb sehr sparsam mit den zur Verfügung stehenden Ressourcen um. Java-Applets sind aus diesem Grund im Verhältnis zu Programmen, die in einer anderen Programmierhochsprache geschrieben wurden, relativ klein. Dies ist vor allem darin begründet, dass Java-Programme Klassenbibliotheken erst bei der Ausführung temporär zum eigentlichen Programm binden, wohingegen Sprachen wie C oder C++ solche Bibliotheken bereits bei der Übersetzung fest einbinden. Ein weiterer Grund für die Schnelligkeit von Java besteht darin, dass Basisklassen als Bestandteile des Browsers verfügbar sind und nicht beim Aufruf über das Internet geladen werden müssen. Multithreading In Java wird das Konzept der Threads verwendet. Ein Thread ist eine Art von Prozess, der eine einzelne Handlungsabfolge ausführen kann. Ein Entwickler kann zumindest theoretisch eine unbegrenzte Zahl von Threads definieren. Bei der Ausführung verwendet man eine Interrupt-Steuerung, um den Zugriff der Threads auf den Prozessor zu regeln. Eine parallele Ausführung der Threads (Multitasking) kann daher simuliert werden, wenn die Maschine, auf der ein Applet ausgeführt wird, schnell genug ist. Offenheit Java unterstützt die Einbindung anderer Sprachen durch die Integration von Fremdmodulen. Hierdurch können auch die Schwerpunkte anderer Sprachen gut ausgenutzt werden. Hinsichtlich der Plattformunabhängigkeit und bezüglich der Sicherheitsaspekte stellt diese Technik jedoch ein Problem dar. Nachdem bisher die grundlegenden Eigenschaften von Java vorgestellt wurden, wird nun die Entwicklung der Sprache betrachtet, die mittlerweile in der Version 1.2 verfügbar ist. Java Development Kit 1.0 Von den Anfängen einer Programmiersprache mit besonderen Grafikfähigkeiten und Sicherheitsrestriktionen hat sich Java zu einer umfassenden Menge von Anwendungsgebieten weiterentwickelt, die bspw. die Anbindung an CORBA oder Datenbanken mit einschließt. Diese Entwicklung manifestiert sich in den Java Development Kits (JDKs). Im Unterschied zu anderen Entwicklungsumgebungen kann das JDK unter der URL http://www.java.sun.com stets kostenlos bezogen werden. Andere Entwicklungswerkzeuge sind neben vielen weiteren bspw. Visual Café der Firma Symantec Visual Age der Firma IBM JBuilder der Firma Borland oder Java WorkShop der Firma SunSoft. In der Grundversion des Java Development Kit 1.0 (JDK 1.0) wurde die folgende Funktionalität realisiert, die vor allem in Kapitel 3 vorgestellt wird: Grundlegende Objekte : Objekte, Strings, Threads, Zahlen, Ein- und Ausgabe, Datenstrukturen, Systemeigenschaften. Grafikprogrammierung: Einfache Programmierung von Benutzerschnittstellen bzw. Verknüpfung mit auszuführenden Programmbausteinen durch das Event-Konzept. Netzwerkbetrieb: URLs, TCP- und UDP-Sockets sowie IP-Adressen. Die umfangreichen Sicherheitsgarantien, die in Java zur Verfügung stehen, werden gesondert in Kapitel 1.3 betrachtet. Java Development Kit 1.1 Nach der schnellen Verbreitung von Java 1.0 stellte sich heraus, dass einige wichtige Erweiterungen vorgenommen werden mussten, um den Anforderungen einer sich schnell ändernden technischen Umwelt gerecht zu werden. In der Folge wurde daher das Java Development Kit 1.1 und anschließend das Java Development Kit 1.2 veröffentlicht. Wichtige Neuerungen in Java 1.1 sind unter anderem: Erweiterungen : Die Architektur wurde dahingehend erweitert, dass die Entwicklung von grafischen Benutzerschnittstellen (GUIs) vereinfacht wurde. Hierzu wurde bspw. die Konsistenz von Methodennamen, Argumenten und Funktionalitäten neu konzipiert. Weiterhin wurden eine Reihe von Qualitätsverbesserungen von Java vorgenommen und neue Merkmale integriert. Zusätzlich wurden die Netzwerkklassen sowie die Eingabe- und Ausgabemöglichkeiten erweitert. Internationalisierung: Eine wichtige Neuerung in Java 1.1. ist die Unterstützung von Programmen, die unabhängig von der Kultur und Sprache eines Programmierers entwickelt werden (sog. globale Programme). Wird ein solches Programm in einer kulturspezifischen Umgebung ausgeführt, so wird bspw. die Sprache des jeweiligen Benutzers eingesetzt. Die Besonderheiten der Internationalisierung werden in Kapitel 6 vorgestellt. Sicherheit: Die Sicherheits-API in Java wurde derart gestaltet, dass Entwickler Java-Anwendungen in verschiedenen Sicherheitsabstufungen entwickeln können. Eine detaillierte Beschreibung hierzu findet sich in Kapitel 1.3 und in Kapitel 5.2. JavaBeans: Die JavaBeans-Architektur definiert das Software-Modell wiederverwendbarer Komponenten in Java. Eine detaillierte Beschreibung hierzu findet sich in Kapitel 9. JAR-Format: Das Java-ARchive-Format (JAR) ist ein plattformunabhängiges Dateiformat, das die Sammlung der Komponenten eines Applets in einer Datei gestattet. Wesentlicher Vorteil dieser Technologie ist, dass ein Applet anschließend in einem einzigen Zugriff über das Internet geladen werden kann. Die Verwendung von JAR wird in Kapitel 4.3 erläutert. Remote Method Invocation: Die Remote Method Invocation (RMI) ermöglicht es Programmierern, verteilte Java-Anwendungen zu implementieren, in denen entfernte Java-Objekte von anderen Java-VMs aufgerufen werden können, die auch auf anderen Hosts eines Netzwerks oder des Internets ablaufen können. Reflection: Die Reflection (dt. Reflexion) ermöglicht es Java-Code, Informationen über geladene Klassen zu sammeln und mittels diesen unter strengen Sicherheitsrestriktionen Informationen aus einzelnen Klassen abzurufen. Durch das Reflection Application Programming Interface (API) werden daher die Klassen, Schnittstellen und Objekte der derzeit verwendeten Java Virtual Machine sichtbar. Dies kann für Anwendungen wie Debugger oder GUI-Programmierwerkzeuge sehr wichtig sein. Datenbankanbindung: Die Java Database Connectivity (JDBC) ist eine Schnittstelle zu Standard-SQL-Datenbanken, die in Kapitel 10 erläutert wird. Java Native Interface (JNI): Bereits in Java 1.0 konnte der Code anderer Programmiersprachen eingebunden werden. In Java 1.1. wurde hierzu eine einheitliche Schnittstelle definiert, das Java Native Interface (JNI), das in Kapitel 3.5 betrachtet wird. Integration einer Mathematikklasse: Mit der Klasse java.math wurden die Klassen BigDecimal und BigInteger realisiert. BigInteger erlaubt die effiziente Verarbeitung großer ganzzahliger Werte, während BigDecimal auch monetäre Rechnungen gestattet. Java Development Kit 1.2 Im Java Development Kit 1.2 (JDK 1.2) wurden folgende Neuerungen vorgenommen: Erweiterung des Sicherheitskonzepts : In JDK 1.2 wurden unter anderem verschiedene Sicherheitsstufen für einzelne Klassen sowie die Zertifizierung realisiert. Java Foundation Classes (JFC): Teil von Java 1.2 sind die Java Foundation Classes (JFC), die eine leichtere Interaktion von Java-Programmen mit der Außenwelt realisieren. Hierzu zählen Java Swing, das zur effizienten Programmierung von Benutzerschnittstellen eingesetzt wird, Java 2D, womit erweiterte Verarbeitungsmöglichkeiten von Grafik und Bildern ermöglicht werden, Drag and Drop, wodurch ein einfacher Datenaustausch zwischen plattformabhängigen Programmen und Java-Programmen möglich wird, sowie erleichterte Zugriffsoptionen, die bspw. die Integration von Java-Programmen und Spracherkennungs-Software gestatten. Collections-API: Das Java Collections Application Programming Interface (API) ist eine Umgebung, in der Collections unabhängig von Details ihrer Repräsentation manipuliert und repräsentiert werden können. Collections, die manchmal auch als Container bezeichnet werden, sind Objekte, die mehrere Objekte in einer Einheit gruppieren können, bspw. APIs, die nicht zueinander in Beziehung stehen müssen. Man verwendet Collections, um Daten zu speichern, diese abzurufen oder sie zu manipulieren, oder um Daten von einer Methode an eine andere zu übergeben. In dieser Form stellen Collections meist Datencontainer dar. Beispiele für Collections sind Mail-Folder, die Mails nach verschiedenen Gesichtspunkten gruppieren oder auch Adressbücher. Das Collections-API beinhaltet Schnittstellen, abstrakte und konkrete Implementierungen und einige polymorphe Algorithmen. Hierdurch wird die Wiederverwendbarkeit von Software entscheidend verbessert, da bspw. eine bereits bestehende Schnittstelle mit einem anderen Programmteil verbunden werden kann, ohne eine neue Schnittstelle implementieren zu müssen. Java Interface Definition Language (IDL): Die Java-IDL fügt Java die CORBA-Funktionalität (Common Object Request Broker Architecture) zu. Hierzu kann die OMG-IDL (Object Management Group Interface Definition Language) sowie das IIOP (Internet Inter-ORB Protocol) verwendet werden. JAVA-IDL wird in Kapitel 12.3 beleuchtet. Diverse Erweiterungen: In JDK 1.2 wurde eine Reihe von Erweiterungen vorgenommen, bspw. bei JavaBeans, bei der Remote Method Invocation, bei der Verarbeitung von Audio und bei der Verbesserung der Leistung von Java. Schwache Referenzen: Programme können Referenzen zu Objekten aufbauen, die bei einem Speichermangel trotzdem von der Garbage Collection gelöscht werden. Hierdurch können bspw. Web-Caches realisiert werden. Im Regelfall werden Referenzen zu bestehenden Objekten niemals gelöscht. Dies kann nur durch die Verwendung von schwachen Referenzen erreicht werden. Versionsidentifikation: Die Versionsidentifikation ermöglicht die Identifikation einer spezifischen Java-Laufzeitumgebung, wenn ein Programm ausgeführt wird (zur Laufzeit). Der Leser sollte an dieser Stelle einen Überblick über die grundsätzliche Entwicklung der Java Development Kits (JDKs) gewonnen haben. Im Folgenden werden die besonderen Sicherheitsrestriktionen von Java mit Bezug auf die jeweiligen JDKs vorgestellt.