Fragen zur Java-Zertifizierung, Flusskontrolle und Ausnahmebehandlung

Frage

Was wird ausgegeben, wenn die folgende Methode aufgerufen wird?

void test() {
one:
two:
  for ( int i = 0; i < 3; i++ ) {   three:
    for ( int j = 10; j < 30; j++ ) {      System.out.println( i + j );
      if ( i > 2 )
      continue one;
    }
  }
}
  1. 10 und 20
  2. 11 und 21
  3. 12 und 22
  4. 13 und 23
  5. 30, 31, 32, 33

Frage

Analysiere den folgenden Programmtext:

1: void schleife() {
2:   int x = 0;
3: one:
4:   while ( x < 100 ) {
5:   two:
6:     System.out.println( ++x );
7:     if ( x > 3 )
8:     break two;
9:   }
0: }
  1. Der Code lässt sich nicht kompilieren.
  2. Die Funktion schreibt die Zahl 0 in die Standardausgabe.
  3. Die Funktion schreibt die Zahl 1 und 2 in die Standardausgabe.
  4. Die Funktion schreibt die Zahl 3 in die Standardausgabe.
  5. Die Funktion schreibt die Zahl 4 in die Standardausgabe.
  6. Die Funktion schreibt die Zahlen 5 bis 9 in die Standardausgabe.
  7. Die Funktion schreibt die Zahl 10 in die Standardausgabe.

Der Programmcode kann nicht kompiliert werden, da Zeile 8 eine ungültige Sprungadresse enthält. Daher ist Antwort A korrekt. Die break Anweisung dient immer dazu, aus einer Schleife zu entkommen und nicht aus einer if Anweisung. Würde der Sprung nach two: gelingen, wäre man aber immer noch in der Schleife. Eine gültige Anweisung für Zeile 8 ist break one;

Frage

Betrachte den folgenden switch Block. (Zwei Antworten.)

char mychar = 'c';
switch ( mychar )
{   default:
  case 'a': System.out.println( "a" ); break;
  case 'b': System.out.println( "b" ); break;
}

Welche der folgenden Antworten ist richtig?

  1. Der switch Block ist illegal, weil nur Ganzzahlen in der switch Anweisung benutzt werden können.
  2. Mit der switch Anweisung ist alles in Ordnung.
  3. Der switch Block ist illegal, da die default Anweisung zum Schluss kommen muss.
  4. Wenn das Programmstück läuft, wird nichts auf die Standardausgabe geschrieben.
  5. Wenn das Programmstück ausgeführt wird, erscheint der Buchstabe "a" auf dem Bildschirm.

Der Ausdruck in einer switch Anweisung muss sich auf ein 32-Bit Wert konvertieren lassen. Also sind auch die kleineren Datentypen byte, char, short und int erlaubt. Im Beispiel ist ein Buchstabe ein char, der auf ein int konvertiert wird. Dies ist korrekt, also ist Antwort A falsch. Da die Kompilation somit in Ordnung ist, ist Antwort B korrekt. Die Reihenfolge, wann default auftaucht ist egal, somit ist C falsch. Daher folgt auch erst die richtige Ausgabe. Denn der Buchstaben ‚c‘ passt nicht auf ‚a‘ und ‚b‘, somit kümmerst sich der default Teil um die Eingabe. Da hinter default jedoch kein break steht läuft der Programmcode direkt in die Ausgabe "a" hinein. Also ist ebenfalls E korrekt und D falsch.

Frage

Welche der folgenden Aussagen über try, catch und finally sind wahr?

  1. Einem try Block muss immer ein catch Block folgen.
  2. Einem try Block kann entweder ein catch Block, ein finally Block oder beides folgen.
  3. Ein catch Block muss immer mit einem try Block verbunden sein.
  4. Ein finally kann nicht ohne ein try Block auftauchen.
  5. Keine dieser Aussagen sind wahr.

Einem try Block muss entweder ein finally ein catch oder beides folgen. Also ist A falsch und B richtig. Wenn man ein catch oder ein finally schreibt, so ist dies immer mit einem try verbunden, daher ist auch C und D richtig. Da mindestes eine Antwort richtig ist, ist E falsch.

Frage

1: public class Clazz {
2:   public static void main( String args[] ) {
3:     String s = null;
4:     if ( s != null & s.length() > 0 );
5:     if ( s != null && s.length() > 0 );
6:     if ( s == null | s.length() > 0 );
7:     if ( s == null || s.length() > 0 );
8:   }
9: }

Welche der Aussagen ist wahr?

  1. Zeile 4 erzeugt eine NullPointerException
  2. Zeile 5 erzeugt eine NullPointerException
  3. Zeile 6 erzeugt eine NullPointerException
  4. Zeile 7 erzeugt eine NullPointerException

Antwort A und C sind korrekt und Zeile 4 und 6 erzeugen eine NullPointerException. Man muss wissen, dass & und | beide Seiten auswerten. Ist also die Referenz null, so ist auch s.length() ungültig. In Zeile 4 und 6 kommen aber diese Auswertungen vor. && wertet den rechten Ausdruck nur dann aus, wenn der linke wahr ist. Also wird in Zeile 5 keine Länge abgefragt. || wertet nur dann die rechte Seite aus, wenn die linke Seite falsch ist. s == null ist aber wahr und die Länge muss nicht ausgewertet werden.

Frage

Welche Methoden kann man für das Kommentar //XX einsetzen?

class Base{
  public void method( int i ) { }
}
public class Extension extends Base {
  public static void main( String args[] ) { }
  //XX
}
  1. void method( int i ) throws Exception {}
  2. void method( long i ) throws Exception {}
  3. void method( long i ) {}
  4. public void method( int i ) throws Exception {}

Eine Methode kann in einer Unterklasse überschieben oder überladen werden. Überladene Funktionen haben den selben Namen wie eine andere Funktion in der Klasse oder der Basisklasse, jedoch verschiedene Parameter. Da in Antwort B und C die Methode mit einem long definiert ist, hat sie mit der in der Klasse Base definierten Methode außer den Namen nichts gemeinsam. So überlädt B und C method(int) aus Base und fügt sie der Klasse Extension zu. B und C sind korrekte Antworten und führen zu keinen Übersetzungsfehler. Anders dagegen A und D. Beide führen zu Compilerfehlern, da sie versuchen die Methode aus der Basisklasse zu überschreiben und eine Exception hinzuzufügen. Auch die Methode in Base müsste die Ausnahme schmeißen.

Fragen zur Java-Zertifizierung, Ein- Ausgabe mit dem Paket java.io

Frage

Sie schreiben ein Programm, um den Text einer 8 Bit Datei, die den Zeichensatz nach ISO 8859-8 benutzt, in einem TextArea Objekt darzustellen. Die lokale Enkodierung ist auf 8859-8 gesetzt. Wie sieht der Programmcode aus, um die erste Zeile dieser Datei zu lesen?
Sie haben dabei Zugriff auf 3 Variablen. myfile ist der Name der Datei, aus der Sie lesen wollen, stream ist ein InputStream Objekt, der dem mit dieser Datei verknüpft ist und s ist ein String Objekt.

  1. InputStreamReader reader = new InputStreamReader( stream, „8859-8“ );
    BufferedReader buffer = new BufferedReader( reader );
    s = buffer.readLine();
  2. InputStreamReader reader = new InputStreamReader( stream );
    BufferedReader buffer = new BufferedReader( reader );
    s = buffer.readLine();
  3. InputStreamReader reader = new InputStreamReader( myfile, „8859-8“ );
    BufferedReader buffer = new BufferedReader( reader );
    s = buffer.readLine();
  4. FileReader reader = new FileReader( myfile );
    BufferedReader buffer = new BufferedReader( reader );
    s = buffer.readLine();

Frage

Was schreibt das folgende Programm auf die Konsole wenn die Datei „Hallo.txt“ im aktuellen Verzeichnis nicht existiert?

import java.io.*;
public class Leser {
  public static void main( String args[] ) {
    Leser l = new Leser();
    System.out.println( l.ließ() );
  }
  public int ließ() {
    try {
      FileInputStream dis = new FileInputStream("Hallo.txt");
    }
    catch ( FileNotFoundException fnfe ) {
      System.out.println( "Keine solche Datei " );
      return -1;
    }
    catch ( IOException ioe ) { }
    finally { System.out.println( "Schlusslicht " ); }
    return 0;
  }
}
  1. Keine solche Datei
  2. Keine solche Datei -1
  3. Keine solche Datei Schlusslicht -1
  4. 0

Ist die Datei nicht vorhanden, so wird eine FileNotFoundException (Spezialisierung von IOException) geschmissen. Da wir diese zuerst abfragen gibt das Programm die erste Meldung aus. Nun muss nicht noch weiter nach catch Klauseln gesucht werden und der finally Block wird ausgeführt. Dieser gibt noch die zweite Ausgabe aus. Vor dem Abarbeiten wird aber der Rückgabewert -1 gesichert und dann nach finallly in der main() Methode ausgegeben. Daher ist C die einzig richtige Antwort.

Fragen zur Java-Zertifizierung, Grundlegende Spracheigenschaften

Frage

In welcher Reihenfolge können die folgenden Klassen in der Quelldatei gesetzt werden, ohne dass Fehler oder Warnungen zur Übersetzungszeit auftreten.

// A
 import java.applet.*;
// B
 class Helper { }
// C
 package myclasses;
// D
 public class MyApplet extends java.applet.Applet { }
  1. A, B, C, D
  2. A, C, B, D
  3. C, A, B, D
  4. C, A, D, B
  5. C, B, A, D

Befindet sich eine Klasse in einem Paket, so muss in der ersten Zeile die package Deklaration stehen. So sind Antworten A, B falsch. Anschließend müssen die Import Anweisungen folgen. So ist auch E falsch. Antworten C und D sind korrekt. Bei D würde aber ein extends Applet reichen.

Frage

Was ist die korrekte Reihenfolge für Importanweisungen, Klassen und Pakete, wenn diese in einer einzigen Datei vorkommen?

  1. Paketanweisung, Importanweisung, Klasse
  2. Klasse, Importanweisung, Paketanweisung
  3. Importanweisung, Paketanweisung, Klassen
  4. Paketanweisung, Klassen, Importanweisung

Die korrekte Antwort ist A.

Frage

Was sind gültige Schlüsselwörter in Java?

  1. sizeof
  2. abstract
  3. native
  4. NULL
  5. BOOLEAN
  6. goto

sizeof ist ein Operator in C(++), der in Java keine Bedeutung hat. Er kann daher ein kein gültiges Schlüsselwort. A ist flasch. abstract und native sind reservierte Schlüsselwörter, daher sind Antwort B und C richtig. NULL ist ein Makro unter C(++), welches in Java nicht existiert. Nur null ist ein eingebautes Schlüsselwort. Auch BOOLEAN ist kein Schlüsselwort, da Java Groß/Kleinschreibung trennt. So sind D und E keine Schlüsselwörter. Obwohl es in Java kein goto gibt (jedoch Multibreak continue, was im Prinzip das gleiche ist), ist das Schlüsselwort reserviert und man kann es nicht als Bezeichner wählen. Auch F ist richtig.

Frage

Welche der folgenden Bezeichner ist illegal?

  1. #_pound
  2. _underscore
  3. 4Interface
  4. Interface5
  5. _5_

Bezeichner dürfen nur mit einem Unterstrich oder einem Buchstaben beginnen. Demnach sind A und C illegale Bezeichner. Die anderen sind gültige Namen, es sind B, D und E korrekt.

Frage

Welche der folgenden Deklarationen für die main() Methode in einer Konsole Applikation ist nicht korrekt?

  1. public static void main()
  2. public static void main( String[] string )
  3. public static void main( String args )
  4. public static void main( String[] args )
  5. static void main( String[] args )

Die korrekte Signatur ist public static void main( String [] ). Also B und D korrekt, da es auf die Namensgebung nicht ankommt. Da die main() Funktion immer als Argument ein String Feld hat, ist A und C falsch. E ist im Prinzip richtig, jedoch wird mit public die main() Funktion auch außerhalb des Paketes sichtbar. Also ist auch E falsch.

Frage

Wie lautet die Spezifikation für den Paramter der pulic static void main Methode?

  1. String args []
  2. String [] args
  3. Strings args []
  4. String args

Als Parameter wird ein Feld von Strings erwartet. Also ist D falsch. Da die Klammern bei der Deklaration eines Feldes vor oder hinter dem Bezeichner stehen können und der Name des Feldes unerheblich ist, sind A, B und C richtige Antworten.

Frage

Was enthält das erste Erste Element des Stringfeldes, das der public static void main Methode übergeben wird.

  1. Der Name des Programms.
  2. Die Anzahl der Argumente.
  3. Das erste Argument falls verfügbar.

In Java gibt es keine zusammengebunden Programme, sondern nur Klassen. Daher ist A falsch. Die Anzahl der Argumente ergibt sich dem Attribut length der Feldvariablen. Es bleibt C als richtige Antwort.

Frage

Was ist das Ergebnis des folgenden Programmes:

public class Alter {
 public static void main( String args [] ) {
  int age;
  age = age + 1;
  System.out.println( "Das Alter ist " + age);
  }
 }
  1. Kompiliert und läuft ohne Ausgabe.
  2. Kompiliert und die Ausgabe ist „Das Alter ist 1“.
  3. Kompiliert aber erzeugt einen Laufzeitfehler.
  4. Kompiliert nicht.

Das Programm kann nicht kompiliert werden, da auf die lokale Variable age vor dem Lesezugriff kein Schreibzugriff stattfand. Da lokale Variablen aber vor der Benutzung initialisiert werden müssen, meldet der Compiler einen Fehler.

Frage

Was ist das Ergebnis des folgenden Programmcodes?

public class Alter {
 static int age;
 public static void main( String args [] ) {
  age = age + 1;
  System.out.println( "Das Alter ist " + age );
 }
}
  1. Kompiliert und läuft ohne Ausgabe
  2. Kompiliert und liefert die Ausgabe „Das Alter ist 1 „
  3. Kompiliert aber erzeugt einen Laufzeitfehler.
  4. Kompiliert nicht.

Das Programm kompiliert und schreibt das Alter 1 auf den Schirm. Die int Objektvariablen wird mit Null initialisiert. Es ist egal, ob sie dabei static ist oder nicht. Nach dem einmaligen Erhöhen wird sie auf 1 gesetzt. Also ist B die richtige Antwort.

Frage

Welche der folgenden Anweisungen ist korrekt, um ein Zeichenliteral mit dem Wert a zu erzeugen.

  1. ‚a‘
  2. „a“
  3. new Character(a)
  4. \000a

Nur Antwort A ist korrekt. Ein Zeichenliteral muss in einfachen Hochkommas stehen. Nur Zeichenkette, also String-Objekte, stehen in doppelten Anführungszeichen. String Objekte sind aber Objekte und keine primitiven Datentypen wie ein char. Antwort C ist falsch, da dort versucht wird ein Character Objekt zu konstruieren. Der new Operator erzeugt allerdings ein Objekt und kein Literal. Außerdem muss Buchstabe a in Hochkomma stehen. Sonst würde die Zeile auf eine char Variable a zurückgreifen. Das Zeichen \0000a ist die Hexadezimalschreibweise und steht für die Zahl 10. Also ist auch D falsch.

Frage

Wie ist der Zahlenbereich eines byte?

  1. 0 bis 65.535
  2. –128 bis 127
  3. –32.768 bis 32.767
  4. –256 bis 255

Ein byte hat die Datengröße von 8 Bit. So sind Antwort A und C schon falsch. Mit 8 Bit kann man den Zahlenbereich von 0 bis 255 abdecken. Ein Vorzeichen kostet aber noch ein zusätzliches Bit. So ist auch D falsch, da wir hier 9 Bit bräuchten. Da alle primitiven Datentypen außer char mit einem vorzeichenbehaftet sind, bleibt als richtige Antwort B.

Frage

Welche der Anweisungen ist illegal?

  1. int i = 32;
  2. float f = 45.0;
  3. double d = 45.0;

Die erste Anweisung ist korrekt, da 32 im Bereich eines int liegt. Auch Anweisung C ist korrekt. Nur bei B ist das Literal ein double. Dies kann nicht ohne möglichen Verlust in ein float kopiert werden. Also ist Antwort B korrekt. Um die Zeile zu korrigieren schreiben wie float f = 4.0f.

Daten speichern in Google App Engines Datastore

Die Google App Engine (GAE) ist eine freie Hosting-Plattform auf der Java Web-Applikationen deployt werden können. Google bietet für die GAP eine Reihe von Diensten an, etwa zur Datenspeicherung, Mail-Versand/-Empfang, Messaging und mehr. Wer sich für die GAE entscheidet und Daten speichern möchte, dem steht quasi unendliche Speicherkapazität zur Verfügung. Google speichert die Daten in einer Cloud, der Google BigTable (http://en.wikipedia.org/wiki/BigTable). Sie ist im Wesentlichen ein riesiger verteilter Assoziativspeicher.

Der Datestore – so wird die Datenspeicher für die Google App Engine genannt – speichert Entities. Eine Entity hat einen Typ (Entity-Kind genannt) und eine Menge von Schlüssel/Werte-Paaren. Eine konkrete Entity für eine Person könnte etwa die Schlüssel-Wert-Menge { name = „Chris“, age = 36 } speichern. Die Entities sind nicht mit klassischen Tabellenzeilen von relationalen Datenbanken zu vergleichen, das sie schemalos sind: Es gibt also keine Spalten mit festen Datentypen. Es können also beliebig viele „Spalten“ für gewisse Entities hinzukommen. Eine Person in der Datastore kann wie im ersten Beispiel Name und Alter speichern, eine andere Person Name und Schuhgröße. Die Anzahl Schlüssel ist wirklich beliebig; eine Tatsache die vom relationalen Design totales Umdenken erfordert.

Zum Zugriff auf den Datastore steht auf unterster Schicht eine Low-Level API (https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/datastore/package-summary). Diese API bietet grundlegende CRUD-Operationen und ist mit 10 Interfaces und etwas mehr als 50 Klassen leicht verständlich und übersichtlich. Wenn eine Person gespeichert werden soll, sieht das so aus:

Entity chris = new Entity( "Person" );
chris.setProperty("name", "chris");
chris.setProperty("age", 20 );
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
datastore.put(chris);

Jede Entity ist durch einen Schlüssel gekennzeichnet. Nach dem Speichern einer Entity lässt sich dieser Schlüssel über getKey() erfragen. Ist der Schlüssel bekannt, kann eine Anfrage an den Datastore gemacht werden.

Key key = chris.getKey();
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Entity e = datastore.get( key );
Long age = (Long) alice.getProperty("age");

Die Anfragen werden über den Entity-Typ gestellt, der in Key steckt. Alle Entities haben einen Entity-Kind und die Suche auf dem Datastore ist immer mit diesem Entity-Kind verbunden.

Der Datastore bietet eine hohe Lese- und Anfrage-Geschwindigkeit ist aber nicht unbedingt komfortablen.

https://developers.google.com/appengine/docs/java/datastore/?hl=de gibt auch auf Deutsch ein paar Hinweise. Aufbauend auf dieser Low-Level API implementiert Google die Standards Java Persistence API (JPA) und Java Data Objects (JDO). Im Open-Source-Bereich gibt es noch https://code.google.com/p/objectify-appengine/.

Dynamisch. Praktisch. Gut Mit dem BeanUtils Properties einfach beherrschen

Die Java-Beans sind ein allgegenwärtiges Gut. Zwar sind die Anforderungen minimal, aber für viele Bereiche wie grafische Oberflächen, JSPs, OR-Mappern sind sie nicht wegzudenken. Damit eine Bean zur Bean wird, muss eine Klasse

  • öffentlich sein
  • einen öffentlichen Standard-Konstruktur besitzen und
  • öffentliche Zugriffsfunktionen anbieten.

Nach dieser Vorgabe ist Folgendes eine gültige Bean:

class Weihnachtsmann
{
  private String name;

  public String getName() {
    return name;
  }

  public void setName( String name ) {
    this.name = name;
  }
}

Die Zugriffsfunktionen kapseln die internen Eigenschaften und sind nach einem festen Muster aufgebaut. Für ein Attribut String name würden die beiden Methoden void setName(String name) und String getName() angelegt. Für boolean-Eigenschaften ist die Vorsilbe is- üblich. Wenn die Beans nicht nach dem Schema aufgebaut sind, kann eine zusätzliche BeanInfo-Beschreibung helfen.

Während auf der einen Seite die Bean als Datencontainer Einsatz findet, sind sie noch aus einen anderen Grund reizvoll: durch ihren festen Aufbau lässt sich leicht mit Reflection/Instrospection auf die Bean und ihre Eigenschaften zugreifen. Nicht umsonst beschreiben Bean grafische Komponenten, so dass ein GUI-Builder völlig automatisch die Eigenschaften auslesen kann und Manipulationen erlaubt. Ein Exemplar einer Bean lässt sich über ihren Namen einfach anlegen; mit einem bekannten Class-Objekt liefert newInstance() ein neues Exemplar.

Class beanClass = Class.forName( "Weihnachtsmann" );
Object santa = beanClass.newInstance(); 

Für das Auslesen der Methodennamen definiert Java Introspection und für den Zugriff Reflection. Vor dem Zugriff auf die Bean-Properties sind jedoch die Präfixe get- und set- zu setzen und Datentypkonvertierungen durchzuführen.

Um den Umgang mit Bean-Eigenschaften zu vereinfachen, hat die Apache-Gruppe in ihren Commons-Paket die BeanUtils aufgenommen. Die aktuelle Version der BeanUtils ist 1.6.1 und ist beim Indianer-Server http://jakarta.apache.org/commons/beanutils/ zuhause. BeanUtils setzt zwei weitere Pakete aus Commons voraus: Collections und Logging. Der prominenteste Nutzer der Bibliothek ist Struts.

Bean-Eigenschaften lesen und setzen

Eine Bean-Property kennzeichnet ähnlich wie ein Servlet/JSP-Attribut zwei Merkmale: Einen Namen (immer ein String) mit einer Belegung (beliebiger Objekttyp). Um bei einer Bean eine Property zu setzen, müssen ihr Name und die neue Belegung natürlich bekannt sein, beim Lesen nur der Name. Auslesen und Setzen von Eigenschaften erlauben zwei statische Funktionen der Utility-Klasse org.apache.commons.beanutils.PropertyUtils: PropertyUtils.getSimpleProperty(Object bean, String name) und PropertyUtils.setSimpleProperty( Object bean, String name, Object value). Um unserem Weihnachtsmann einen Namen zu geben — und diesen gleich wieder auszulesen — ist zu schreiben:

PropertyUtils.setSimpleProperty( santa, "name", "Santa Clause" );
String name = (String) PropertyUtils.getSimpleProperty( santa, "name" );

Die Methoden sind jedoch nicht ohne ausführliche Fehlerbehandlung auszuführen; auftreten kann eine IllegalAccessException, IllegalArgumentException (eine RuntimeException), InvocationTargetException und NoSuchMethodException.

Zu bedenken ist, dass eine Bean-Property ganz spezielle Methoden erzwingt. Das geht so weit, dass BeanUtils nur genau eine get-Methode erwartet. Gibt es neben der erforderlichen get-Methode eine weitere, so liefert BeanUtils beim Zugriff einen Fehler. Der Zugriff auf bounds einer java.awt.Component gelingt daher nicht, da neben der parameterlosen Variante von getBound() auch getBounds(Rectange) existiert — ein Aufruf ähnlich wie der Folgende funktioniert daher nicht und führt zu einer NoSuchMethodException: PropertyUtils.getSimpleProperty(new Label(), "bounds");

PropertyUtils, BeanUtils und Konvertierungen

Neben der Klasse PropertyUtils, die bisher Verwendung fand, gibt es noch zusätzlich die Klasse BeanUtils. Sie erinnert von der Methodenvielfalt an PropertyUtils, doch sie führt für Datentypen mögliche Konvertierungen durch. Das Beispiel zum Weihnachtsmann soll erweitert werden, so dass er ein Attribute int alter besitzt. Der Versuch, das Attribut mit einem Wert vom Datentyp String über PropertyUtils.setProperty() zu setzen scheitert mit einer IllegalArgumentException. Ein int muss mit einem Integer-Objekt belegt werden. Wird jedoch anstelle der Klasse PropertyUtils die Klasse BeanUtils gesetzt, so funktioniert alles, denn BeanUtils führt die notwendigen Konvertierungen von String in int selbst aus.

BeanUtils.setProperty( santa, "alter", "612" );

Ergibt sich bei der Konvertierung ein Fehler, so macht das nichts, denn dann wird die Zuweisung nicht durchgeführt. (Apropos Konvertierungen: Die Firma Lockheed, die die Raumsonde Mars Climate Orbiter in England konstruierte, nutze die englische Einheit Inch, die NASA in Amerika Zentimeter; beim Landeversuch crashte die Sonde 1999 auf den Mars.)

Die Konvertierung aus einem String für die unterschiedlichsten Datentypen nimmt BeanUtils automatisch vor. Im oberen Fall findet eine Konvertierung von String in ein Integer Objekt statt. Die Converter selbst sind Klassen, die die Schnittstelle org.apache.commons.beanutils.Convert implementieren und auf diese Weise von einem String in BigDecimal, BigInteger, boolean, Boolean, byte, Byte, char, Character, Class, Double, float, Float, int, Integer, long, Long, short, Short, String,

java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp und mehr konvertieren. Den passenden Konverter sucht ConvertUtils.lookup(). Falls also BeanUtils.setProperty() vor der Aufgabe steht, einen passenden Typ herauszufinden, so sucht es zuerst nach dem Typ (von alter wäre das int) und anschließend den passenden Konverter. Da ConvertUtils.lookup(int.class) den IntegerConverter liefert, darf er die Zeichenfolgen umwandeln. Diese Konvertierung ist die übliche Konvertierung mit Integer.parseInt().

Falls die Standard-Konverter einen Typ nicht abdeckt, kann mit register() ein neuer Konverter eingehängt werden. Nicht nötig ist das für sprachabhängige Konvertierungen, denn hier bietet das Paket schon Hilfe an. So gibt es den IntegerConverter in der lokalisierten Fassung auch als IntegerLocaleConverter. Zum Nachschlagen dient aber nicht mehr ConvertUtils, sondern LocaleConvertUtils. Die Basisklassen für lokalisierte eigene Konvertiert ist LocaleConverter. Es gibt für alle normalen Konverter auch lokalisierte Konverter. Das lokalisierte Parsen scheint allerdings noch nicht so ganz ausgereift.

Eine Liste aller Eigenschaften

Eine Belegung aller Eigenschaften liefert BeanUtils.describe() beziehungsweise PropertyUtils.describe().

Map description = BeanUtils.describe( new JButton() );

Der Assoziativspeicher unter dem Namen description enthält als Schlüssel alle Properties und als Werte die Belegungen der Eigenschaften.

verifyInputWhenFocusTarget=true
displayedMnemonicIndex=-1
width=0
...

Formular-Parameter übertragen

In JSPs erfreut eine Schreibweise den Entwickler, die alle Parameterwerte eines Requests in eine Bean überträgt. Das Tag <jsp:setProperty> wird dann mit dem Attribut property="*" belegt.


Diese Möglichkeit sieht zwar die JSP-Spezifikation vor, doch in Servlets fehlt diese Möglichkeit. Sie wird aber insbesondere für MVC-Frameworks interessant, in dem der Controller selbständig die Formulardaten in eine Bean überträgt. Das kann schnell nachimplementiert werden. Die Servlet-API bietet in ServletRequest die Funktion getParameterNames(), um mit einer Enumeration über die Parameter zu laufen. Für jeden der Parameter kann setProperty() auf einer passenden Bean aufgerufen werden.

for ( Enumeration params = request.getParameterNames(); params.hasMoreElements(); ) {
  String name = (String) params.next();
  if ( name != null )
    BeanUtils.setProperty( bean, name, request.getParameterValues(name) );
}

getParameterValues() liefert ein Feld, was von der Bean auch verarbeitet werden muss. Sonst tut’s bei eindeutigen Parametern auch getParameter().

Werte einer Map in eine Bean kopieren

Aufgabe: Eine Ini-Datei enthält Schlüssel/Werte-Paare und alle Schlüssel sollen auf Eigenschaften einer Bean übertragen werden. Für Schlüssel soll es also entsprechende set- und get-Methoden geben. Eine einfache Aufgabe mit den passenden Klassen. Zum Laden der Paar-Datei findet Properties und die Objektfunktion load() Einsatz. Ein Properties-Objekt ist eine spezialisierte Hashtable — eine dumme Designentscheidung Begründung ? —, und alle Schlüssel lassen sich aufzählen. An dieser Stelle kommen wieder die BeanUtils ins Spiel. Die statische Funktion setProperty() sieht jeden Schlüssel als Property und überträgt den Wert auf die Bean.

Unter der Annahme, dass alle Eigenschaften in einer Map vorliegen, bietet BeanUtils eine feine Funktion: static void populate(Object bean, Map properties). Die Map schreibt Schlüssel als String vor, die Werte sind beliebige Objekte. populate() iteriert über die Schlüssel von properties und initialisiert über viele setProperty()-Funktionen die Werte der Bean. Die Aufgabe löst sich somit in wenigen Zeilen Quellcode.

Die Funktion populate() ließe sich auch bei Formular-Parametern verwenden, wenn denn die Schlüssel/Werte-Paare in einer Map vorliegen würden. Doch das können sie so ohne weiteres nicht, denn ein Schlüssel darf durchaus mehrmals mit verschiedenen Werten vorkommen — eine Map kann das nicht unterstützen. Gilt, dass überladene Parameter nicht zu erwarten sind, können von Hand die Parameter in eine Map einsortiert werden, so dass doch wieder populate() nutzbar ist. Seit der Servlet 2.3 Spezifikation bietet HttpServletRequest die Methode getParameterMap(), was direkt eine Map liefert. Sie speichert die Werte jedoch immer als Felder; wenn der Schlüssel einmalig ist, dann ist das Feld nur ein Element groß.

Indexierte Properties

Da ein Weihnachtsmann immer Rentiere besitzt, soll ihn eine zusätzliche Klasse Rentier für die Zugtiere bei der Arbeit unterstützen. Er soll in einem internen Feld zugtier die Tiere speichern und dafür auch zwei neue Zugriffsfunktionen definieren.

class Weihnachtsmann
{
  private Rentier[] zugtier = new Rentier[8];
  public Rentier getZugtier( int index ) {
    return zugtier[index];
  }
  public void setZugtier( int index, Rentier zugtier ) {
    this.zugtier[index] = zugtier;
  }
}

Ist ein Weihnachtsmann aufgebaut und mit setZugtier() ganz klassisch ein Rentier zugewiesen kann im nächsten Schritt die Eigenschaft erfragt werden.

Weihnachtsmann w = new Weihnachtsmann();
w.setZugtier( 0, new Rentier() );
Rentier z = (Rentier) PropertyUtils.getIndexedProperty( w, "zugtier", 0 );

Diese indexierten Eigenschaften lassen sich mit getIndexedProperty() erfragen und mit setIndexedProperty() setzen; die Signaturen sind PropertyUtils.getIndexedProperty(Object bean, String name, int index) und PropertyUtils.setIndexedProperty(Object bean, String name, int index, Object value).

Rentier rudolph = new Rentier();
PropertyUtils.setIndexedProperty( w, "zugtier", 1, rudolph );

Eine Alternative zu den Funktionen, die den Index als Parameter erwarten ist eine Schreibweise, die den Index in den Eigenschaftennamen kodiert. Die Schreibweise ist bekannt von Arrays: name[index].

Rentier zt = (Rentier) PropertyUtils.getIndexedProperty( w, "zugtier[1]" )

Mapped Properties

Für indexierte Properties ist es üblich, dass der Index eine positiver ganzzahliger Wert ist. Eine Erweiterung der Java-Beans gehen die BeanUtils mit den Mapped Properties, was indexierte Beans mit Schlüsseln sind — das Konzept erinnert an einen Assoziativspeicher. Genauso wie indexierte Properties mit eckigen Klammern einen Index aufnehmen, gibt es eine Abkürzung auch bei den abgebildeten Eigenschaften. Dabei wird der Schlüssel in runden Klammern hinter die Eigenschaft gesetzt. Die Signaturen sind PropertyUtils.getMappedProperty(Object bean, String name) mit den alternativen Parametern (Object bean, String name, String key), (Object bean, String name, Object value) und (Object bean, String name, String key, Object value).

Ein Beispiel: System.getProperties() liefert ein Properties mit den Systemeigenschaften. Die Objektmethode getProperty() liefert zu einem gegebenen Schlüssel den assoziierten Wert. BeanUtils lässt mit getMappedProperty() die abgebildeten Eigenschaften erfragen.

Properties props = System.getProperties();
System.out.println( PropertyUtils.getMappedProperty( props, "property(user.name)" ) );

Wo ist der Vorteil gegenüber props.get("user.name") ;

Der Schlüssel muss nicht in einfachen oder doppelten Anführungszeichen eingeschlossen werden, wie es etwa die Expression Language unter JSP 2.0 oder Velocity vorschreibt.

Geschachtelte Properties

Um eine Liste aller Zeichensätze des Betriebssystems auszulesen, lässt sich über GraphicsEnvironment getLocalGraphicsEnvironment() aufrufen.

GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();

Ausdruck Der Name des ersten Fonts erfragt

String s = ge.getAllFonts()[0].getName();

Wie sieht das nun mit den BeanUtils aus? Sicherlich ist es denkbar, für den indexierten Property einmal getIndexedProperty() und für den Namen setSimpleProperty() aufzurufen, aber commons.beanutils kann es besser, und zwar mit getNestedProperty().

Object o = PropertyUtils.getNestedProperty( ge, "allFonts[0].name" );

Während mit getNestedProperty() eine Eigenschaft ausgelesen werden kann, lässt sie sich mit setNestedProperty() setzen. Die Signaturen für set- und get sind: PropertyUtils.getNestedProperty(Object bean, String name) und PropertyUtils.setNestedProperty(Object bean, String name, Object value).

Nun sind drei Möglichkeiten bekannt, auf Bean-Eigenschaften zuzugreifen: Einfache Properties (getSimpleProperty(), setSimpleProperty()), indexierte Properties (getIndexedProperty()/setIndexedProperty()), mapped Properites und geschachtelte Properites (getNestedProperty()/setNestedProperty()). Um den Umgang zu vereinfachen, hilft die allgemeine Funktion getProperty(), die sich den passenden Typ heraussucht. Damit lässt sich alles mit nur einer Art von Funktion beschreiben: PropertyUtils.getProperty(Object bean, String name) und PropertyUtils.setProperty(Object bean, String name, Object value).

Kopien von Bean-Eigenschaften

Stehen zwei Beans gegenüber, die sich Properties teilen, so kopiert BeanUtils. copyProperties() die Eigenschaften. Die Klasse Point definiert genauso wie Rectangle das Property x und y. Die Belegungen der Eigenschaften lassen sich übertragen, so dass das Rectangle alles annimmt, was in Point definiert wird:

Point p = new Point( 10, 20 );
Rectangle rec = new Rectangle(); 
BeanUtils.copyProperties( rec /* = */, p );
System.out.println( rec ); // java.awt.Rectangle[x=10,y=20,width=0,height=0]

Intern geht copyProperties() alle Eigenschaften vom Punkt durch, findet x, y und auch location. Für diese drei Eigenschaften werden die entsprechenden set-Methoden beim Rectangle-Objekt gesucht; sie werden nur für x und y gefunden, denn setLocation() ist überladen und so ungültig.

Ein Beispiel, unter dem Bean-Attribute zu kopiert sind, liefert Struts. Die Action Form Beans werden zwar allzu oft als Model eingesetzt und weiterverarbeitet, das ist aber falsch. Die Form Beans sind lediglich eine Hilfs-Struktur, die nur für die Formulardaten eine Berechtigung haben. Demnach sind sie auch nur in einem Struts-Durchlauf bekannt und werden nicht als Datencontainer weitergegeben. Trennt man allerdings Form-Bean von tatsächlicher Model-Bean (falls eine Model-Bean benötigt wird) sieht das Model sehr ähnlich der Form-Bean aus und entsteht leider doppelter Quellcode, insbesondere für die set- und get-Methoden. Weiterhin ist lästig, dass bei einer gültigen Formularbelegung alle Form-Eigenschaften auf die Model-Bean übertragen werden. Das artet aus zu einer Reihe von Aufrufen der Art

modelBean.setXXX( formBean.getXXX() );

An diese Stellt helfen die BeanUtils und die praktische Funktion copyProperties(). Sie überträgt automatisch die Eigenschaften der Form-Bean in die Model-Bean.

BeanUtils.copyProperties( modelBean, formBean );

Bean-Objekte klonen

Auf copyProperties() aufbauend lassen sich Kopien von Beans anlegen lassen. Da das Kopieren auf Grund der Eigenschaften und set/get-Methoden geschieht, ist eine Implementierung der Schnittstelle Clonable nicht nötig. Mit der clone()-Methode teilt cloneBean() jedoch mit, dass die Kopie wieder eine flache und keine tiefe ist: static Object cloneBean(Object bean).

Dynamische Beans

Bisher waren es existierende Beans, die über wirkliche set- und get-Methoden verfügten. Mit den BeanUtils lassen sich jedoch auch "virtuelle" Beans erzeugen, also Beans, die mit den BeanUtils verwaltet werden können, aber keine passenden set- und get-Funktionen besitzen. Diese Spezial-Beans implementieren die Schnittstelle DynaBean und verwalten über diese Schnittstelle Schlüssel/Werte-Paare. Genauso wie eine Klasse den Bauplan für Objekte beschreibt, beschreibt die Schnittstelle DynaClass den Bauplan für DynaBean-Ojekte. Exemplare der virtuellen Beans führen zuerst über die DynaClass. Die BeanUtils bringen eine Basisimplementierung in Form der Klasse BasicDynaClass mit; ihre Eigenschaften werden als Array von DynaProperty-Objekten übergeben.

DynaProperty[] dynaProp = new DynaProperty[] {
  new DynaProperty( "schenker", String[].class ),
  new DynaProperty( "bringer", Weihnachtsmann.class ),
  new DynaProperty( "volumen", int.class )
};

BasicDynaClass dynaClass = new BasicDynaClass( "geschenk", null, dynaProp ); 

Die BasicDynaClass beschreibt für ein Geschenk ein Feld von Schenkern (wie "Mama", "Papa"), den Weihnachtsmann und die Größe des Geschenks.

Nach der Beschreibung der Klasse bildet die Objektfunktion newInstance() ein Exemplar der dynamischen Bean:

DynaBean paket = dynaClass.newInstance();

Alternativ zu dieser Schreibweise lässt sich ein Exemplar BasicDynaBean bilden, der im Konstruktor die beschreibende Klasse annimmt: paket = new BasicDynaBean(dynaClass);

Diese DynaBean besitzt über die Schnittstelle Methoden zum Erfragen, Setzen oder Löschen von Attributbelegungen. Die Methode set() setzt zum Beispiel eine Eigenschaft.

paket.set( "schenker", new String[1] );
paket.set( "bringer", new Weihnachtsmann() );

Die set()-Funktion ist überladen, um auch indexierte und abgebildete Eigenschaften zu unterstützten. Das gleiche gilt für die get-Funktion. Während bei den herkömmlichen Beans der Zugriff get<Property>() steht, so heißt der nun get("<Property>").

System.out.println( paket.get("volumen") );

Da das BeanUtils-Paket bei den bekannten Funktionen wie PropertyUtils.getProperty() neben den "normalen" Beans auch die DynaBean unterstützen, müssen die über die Schnittstelle DynaBean vorgeschriebenen Funktionen nicht verwendet werden.

PropertyUtils.setSimpleProperty( paket, "volumen", new Integer(123) );
Integer volumen = (Integer) PropertyUtils.getSimpleProperty( paket, "volumen" );

Da im Beispiel vorher schon das Feld der Schenker mit einem String-Array der Länge eins initialisiert wurde, lässt sich ein konkreten Element einfach mit der allgemeinen Funktion setProperty() setzen.

PropertyUtils.setProperty( paket, "schenker[0]", "Ulli" );
System.out.println( PropertyUtils.getProperty(paket, "schenker[0]") ); // Ulli

Mantel um existierende Beans

Die DynaBeans bietet einen einheitlichen Standard für einfache, indexierte oder abgebildeten Eigenschaften über setXXX() und getXXX()-Funktionen. Um auch existierende Beans als DynaBeans zu verwaltet, sind die Klasse WrapDynaBean und WrapDynaClass definiert. Eine WrapDynaBean legt sich um eine bekannten Bean, so dass diese sich als DynaBean verwalten lässt.

Weihnachtsmann myWeih = new Weihnachtsmann();
DynaBean wrapper = new WrapDynaBean( myWeih );
wrapper.set( "name", "Schlittenheld" );
String nameW = (String) wrapper.get( "name" );
System.out.println( "Wrapper: " + nameW + ", Bean: " + myWeih.getName() );
wrapper.set( "alter", new Integer(891) );
int alterW = ((Integer) wrapper.get( "alter" )).intValue();
System.out.println( "Wrapper: " + alterW + ", Bean: " + myWeih.getAlter() );

DynaBeans in Struts *

In Struts führt die Implementierung der Form-Beans für die Abbildung der Formulardaten zu lästigen Aufwand. Die DynaBeans helfen, diesen Aufwand zu minimieren, da keine wirkliche Bean-Klasse mehr zu implementieren ist, sondern die "virtuelle" Bean Verwendung findet. In der struts-config.xml wird zunächst unter form-bean wie üblich die Bean eingetragen. Allerdings ist der Typ nun nicht der Form-Bean Typ, sondern DynaActionForm. In den folgenden Elementen der XML-Datei wird genau die schon bekannten DynaProperty für eine DynaClass eingetragen — nur nicht in Java, sondern in einer Konfigurationsdatei.

<form-bean name="dynaWeihnachtsmannForm"
           type="org.apache.struts.action.DynaActionForm">
  <form-property name="name" type="java.lang.String"/>
  <form-property name="alter" type="java.lang.Integer"/>
</form-bean>

Da Struts mit den HTML-Tags die DynaBeans berücksichtigt, muss an der View (JSP-Seite) keine Veränderung vorgenommen werden. Nur die Action muss eine Veränderung erfahren, da ja ohne DynaBeans das ActionForm aus der execute()-Funktion auf die konkrete Bean typangepasst würde, um die entsprechenden set- und get-Funktionen zu nutzen. Dieser Programmcode muss nun umgebaut werden. Statt dessen gibt Struts eine Klasse DynaActionForm vor, zu der eine Typanpassung vorgenommen wird. Über diese Klasse lässt sich mit get- und set-Funktionen die Bean nutzen.

public ActionForward execute(
 ActionMapping mapping, ActionForm form,
 HttpServletRequest request,
 HttpServletResponse response )
  throws ServletException, IOException
{
  DynaActionForm weihForm = (DynaActionForm) form;
  String name = weihForm.get( "name" );
  ...

Nicht umsonst sieht der Zugriff auf die Attribute über DynaActionForm so aus wie schon im vorangehenden Beispiel beschrieben: DynaActionForm implementiert die Schnittstelle org.apache.commons.beanutils.DynaBean.

Die DynaActionForm ersetzt vollständig die Form-Bean, wobei eine Sache auf der Streckte bleibt: die Validierung. Für die Überprüfung der Formularbelegungen wurde in der Unterklasse von ActionForm die Methode validate() überschrieben. Bei den DynaBean gibt es aber keine eigene Bean-Klasse! Eine Lösung ist eine Validierung über das Validator-Framework (auch eine Jakarta Commons), also über eine XML-Datei, eine andere ist doch wieder eine Unterklasse. Diese spezielle Bean-Klasse leitet von der schon bekannten DynaActionForm ab und implementiert wie bekannt die validate()-Funktion. In struts-config.xml muss natürlich DynaActionForm wieder verschwinden und der Name der Unterklasse eingesetzt werden.

DynaBeans für Datenbanken

Die DynaBeans finden auch an anderer Stelle Verwendung: bei Datenbanken. Ein DynaBeans-Objekt kann eine Zeile eines ResultSet repräsentieren. Um das zu erreichen, legt sich ein ResultSetDynaClass-Objekt um ein ergebnislieferndes ResultSet.

ResultSetDynaClass rsdc = new ResultSetDynaClass( rs, true );

ResultSetDynaClass implementiert die Schnittstelle DynaClass. Das ist wichtig, denn DynaClass verwaltet die Beschreibung der Eigenschaften über DynaProperty-Objekte. Bei der ResultSetDynaClass werden die Namen der Spalten zu den Namen der Eigenschaften. Der Konstruktor von ResultSetDynaClass ist überladen und verträgt ein zusätzliches boolean, damit die Namen der Eigenschaften nicht exakt in Groß- und Kleinbuchstaben übereinstimmen müssen. Ist die Belegung true, so reichen alle Namen in Kleinbuchstaben. Wichtiger ist aber die Funktion iterator() von ResultSetDynaClass, die der Reihe nach die Zeilen als DynaBean liefert; die Beschreibung der Klassen-Informationen war ja wichtiges Teil in den DynaProperty-Objekten.

for ( Iterator rows = rsdc.iterator(); rows.hasNext(); ) {
  DynaBean row = (DynaBean) rows.next();
  System.out.println( row.get("bla") );
}

Nach dem Durchlaufen kann das ResultSet geschlossen werden, das bedeutet, dass zur Zeit des Durchlaufens eine Verbindung zur Datenbank bestehen muss.

Die Frage ist nun, was ein ResultSetDynaClass bringt; zum Durchlaufen tut es auch das ResultSet. Ein Vorteil ist, dass die Daten, die über die DyaBean veröffentlicht werden, leicht auf andere Beans kopiert werden können; man erinnere sich an BeanUtils.copyProperties(ziel, quelle). Oftmals müssen die Datenbankinhalte auf das Model einer Applikation übertragen werden, so dass ein ResultSetDynaClass dazu gut geeignet ist.

Eine Eigenschaft der Klasse BasicDynaBean (und BasicDynaClass) ist ihre Serialisierungs-Möglichkeit, da die Klasse die Schnittstelle java.io.Serializable implementiert, und somit persistent gemacht werden kann. Eine normale DynaBean implementiert diese Schnittstelle nicht, so dass man an dieser Stelle die Serialisierung nicht erzwingt. Aber natürlich kann eine andere Klasse DynaBean und Serializable gleichzeitig implementieren und das wäre sogar praktisch, denn könnte die Daten gleich abgespeichert werden. Wurde zum Beispiel eine BasicDynaBean angelegt, können Daten auf diese Bean übertragen werden und sie lässt sich später abspeichern. Es wäre doch praktisch, wenn auch die Zeilen vom ResultSetDynaClass serialisierbar wären. Dann wäre das Problem gelöst, wie sich Datenbankinhalte einfach übertragen lassen — O.K., ein RowSet ist auch eine Lösung. Die Antwort aus den BeanUtils ist die Klasse RowSetDynaClass, die Serializable implementiert. RowSetDynaClass liest aus einem ResultSet alle Zeilen aus und speichert sie, so dass sie serialisierbar werden.

RowSetDynaClass rsdc = new RowSetDynaClass( rs );

Nach dieser Zeile kann das ResultSet rs schon geschlossen werden. Der Zugriff auf die Zeilen geschieht anschließend mit getRows(), was eine java.util.List mit den bekannten DynaBean-Objekten liefert. Mit dem serialisier-fähigen RowSetDynaClass lassen sich die Inhalte über RMI leicht transportieren, ohne dass viel Programmcode entwickelt werden muss.

Links

GUI-Tipp: Verschicke EMails mit der richtiger Kodierung

Also nicht so:

Hi Christian,<BR><BR>We have received a request to permanently delete your account. Your account has been deactivated from the site and will be permanently deleted within 14 days. <BR><BR>If you did not request to permanently delete your account, please login to Facebook to cancel this request:<BR><BR>https://www.facebook.com/login.php<BR><BR><BR>Thanks,<BR>The Facebook Team

Session-Verwaltung in Servlets mit HttpSession

Die Servlet-API bietet die Klasse HttpSession an, eine Bibliothek auf hohem Niveau für die Verwaltung einer Sitzung. Sie basiert entweder auf Cookies oder URL-Rewriting, doch wird das von der API transparent gehalten. Als Programmierer bekommen wir so gut wie gar nichts davon mit. Falls der Client keine Kekse mag, wandeln wir alle Informationen in URLs um, die wir dann anbieten. Ein Sitzungsobjekt verwaltet die gesicherten Daten auch selbstständig in einer Datenstruktur. Hier fällt für uns keine Arbeit an.

Das mit einer Sitzung verbundene Objekt HttpSession

Jede Sitzung ist mit einem Sitzungsobjekt verbunden, das die Klasse HttpSession abbildet. Bei JSPs repräsentiert das implizite Objekt session die aktuelle Sitzung.

Werte mit einer Sitzung assoziieren und auslesen

Um Informationen mit der Sitzung zu verbinden, verwenden wir die Methode setAttribute(), die einen Schlüssel und einen Wert verbindet. Daten werden mit getAttribute() wieder aus der Datenstruktur gelockt, so wie es das folgende Beispiel zeigt:

List l = (List) session.getAttribute( "artikel" );

Hier verbinden wir mit einem Schlüssel eine Liste von Waren. Im Hintergrund werden die Informationen auf der Serverseite gesichert. Die Informationen selbst werden nicht in Cookies oder in der URL abgelegt, daher spielt die Größe der Daten auch keine Rolle. Ein HttpSession-Objekt verwaltet einen Assoziativspeicher, der die Wertepaare speichert. Es ist günstig, die Elemente serialisierbar zu gestalten, um die Daten dauerhaft zu speichern.

Werfen wir abschließend einen Blick auf das Programmstück, das eine neue Ware hinzufügt:

List l = (List) session.getAttribute( "artikel" );
if ( l == null )
{
  l = new ArrayList();
  session.setAttribute( "artikel", l );
}
l.add( w );

interface javax.servlet.http.HttpSession

  • Object getAttribute( String name )

    Liefert das mit name verbundene Objekt; null, wenn es keine Assoziation gab.
  • Enumeration getAttributeNames()

    Liefert eine Aufzählung aller mit der Sitzung verbundenen Objekte.
  • void setAttribute( String name, Object value )

    Bindet name mit dem Objekt value an die Sitzung. Existierte das Objekt, wird es ersetzt. Angemeldete HttpSessionBindingListener werden über die Methode value Bound() beziehungsweise valueUnbound() informiert.
  • void removeAttribute( String name )

    Entfernt das Attribut von der Sitzung. Ungültige Namen werden ignoriert. HttpSession BindingListener werden durch Aufruf von valueUnbound() informiert.

Alle Methoden liefern eine IllegalStateException, wenn die Sitzung ungültig ist. Die Methoden putValue() und setValue() sind veraltet und wurden durch setAttribute() und getAttribute() ersetzt.

URL-Rewriting

Das Session-Management sollte im Prinzip unabhängig von der technischen Umsetzung sein. Doch leider greift das Sitzungsmanagement beim URL-Rewriting schon sehr stark ein: Bei jedem Verweis auf eine neue Seite muss die URL entsprechend angepasst werden, weil die Sitzungs-ID mitgeschickt werden muss. Cookies verwalten die Sitzungs-ID völlig anders. Das bedeutet: Werden Cookies eingesetzt, ändert sich die URL nicht und jeder kann problemlos auf eine neue Seite verweisen. Nur beim URL-Rewriting muss an die URL eine Sitzungskennung angehängt werden.

Beispiel Eine URL für einen Cookie besitzt keine Sitzungskennung.

  • http://localhost/servlet/URLRewritingSession

Mit URL-Rewriting sieht das dann etwa so aus:

  • http://localhost/servlet/URLRewritingSession;jsessionid=abcde234

Wenn wir innerhalb eines Servlets auf eine andere generierte Seite verweisen wollen, haben wir eine URL vor uns, zu der wir verzweigen möchten. Die Servlet-API kümmert sich darum, an eine Benutzer-URL die Sitzungs-ID automatisch anzuhängen. Dazu dienen die HttpServletResponse-Methoden encodeURL() und encodeRedirectURL().

Beispiel: Aufgrund einer Formularbestätigung soll auf eine JSP-Seite mit dem Namen validate.jsp verwiesen werden:

<form action='<%= response.encodeURL("/validate.jsp") %>'>

Werden der Verweis und die Kodierung aus Versehen vergessen, ist dies das Ende der Sitzung. Ob eine Sitzung mit einem Cookie behandelt wird, lässt sich mit isRequestedSessionIdFromCookie() testen. Dann kann aufgrund einer Fallunterscheidung encodeURL() verwendet werden oder nicht. Allgemein ist es aber nicht schlecht, grundsätzlich alle Verweise innerhalb einer Webapplikation mit encodeURL() zu sichern. Im Fall von Cookies wird zwar keine Kennung angehängt, eine spätere Umstellung gestaltet sich aber einfacher, falls der Nutzer die Cookies einmal ausschaltet.

Zusätzliche Informationen

Ein Sitzungsobjekt verwaltet neben den assoziierten Daten noch weitere Informationen. Jede Sitzung bekommt eine eindeutige ID, die sich mit getId() erfragen lässt. Ist die Sitzung neu und hat der Client noch nie eine Verbindung gehabt, gibt isNew() den Wert true zurück. Existiert dann die Sitzung, gibt getCreationTime() ein long zurück – kodiert sind wie üblich die vergangenen Millisekunden seit dem 1.1.1970 –, in dem sich das Erstellungsdatum erfragen lässt. Dagegen erfragt getLastAccessedTime() die Zeit, die seit dem letzten Zugriff durch den Client vergangen ist. Falls der Server die Informationen dauerhaft speichert und der Cookie nicht abläuft, erlaubt dies Meldungen der Art: »Schön, Sie nach zwei Wochen zum fünften Mal bei unserer Partnervermittlung wiederzusehen. Hat’s wieder nicht geklappt?«

Das Ende der Sitzung

Eine Sitzung ist nicht automatisch unendlich lange gültig. Bei Cookies lässt sich der Gültigkeitszeitraum einstellen. Auch Sitzungsobjekte lassen sich in der Zeit anpassen. Die Methode setMaxInactiveInterval() setzt den Wert, wie lange eine Sitzung gültig ist. Ist der Wert negativ, zeigt er an, dass die Sitzung nicht automatisch beendet wird. Die entsprechende Methode getMaxInactiveInterval() liefert die Zeit in Sekunden, in der eine Sitzung gültig ist.

interface javax.servlet.http.HttpSession

  • long getCreationTime()

    Gibt in Millisekunden ab dem 1.1.1970 an, wann die Sitzung eröffnet wurde.
  • String getId()

    Liefert eine eindeutige Kennung, die die Sitzung identifiziert.
  • long getLastAccessedTime()

    Gibt in Millisekunden ab dem 1.1.1970 zurück, wann der Client zum letzten Mal auf den Server zugegriffen hat.
  • int getMaxInactiveInterval()
  • void setMaxInactiveInterval( int interval )

    Liefert und setzt die Zeit, für die der Servlet-Container die Sitzung aufrechterhalten soll, bis sie ungültig wird.
  • boolean isNew()

    Der Rückgabewert ist true, wenn die Sitzung neu ist.

Beispiel Zum Schluss wollen wir ein Programm formulieren, das alle diese Informationen auf einmal ausgibt.

<%@ page language="java" import="java.util.*" %>
<%
int cnt = 0;
if ( session.isNew() )
{
out.println( "Willkommen Neuling!\n" );
}
else
{
out.println( "Hallo, alter Freund!\n" );
String o = (String) session.getAttribute( "cnt" );
if ( o != null )
cnt = Integer.parseInt( o );
cnt++;
}
session.setAttribute( "cnt", ""+cnt );
%>
<p>
Session-ID: <%= session.getId() %> <p>
Erzeugt am: <%= new Date(session.getCreationTime()) %> <p>
Letzter Zugriff: <%= new Date(session.getLastAccessedTime()) %> <p>
Ungültig in Minuten: <%= session.getMaxInactiveInterval()/60 %> <p>
Anzahl Zugriffe: <%= cnt %>

Das Programm liefert beispielsweise folgende Ausgabe:

Hallo, alter Freund!
ID: 91410050092487D9B5D0D2A7A3D0F072
Erzeugt am: Fri Jan 18 20:16:49 CET 2002
Letzter Zugriff: Fri Jan 18 20:23:33 CET 2002
Ungültig in Minuten: 30
Anzahl Zugriffe: 4

Die ID sieht bei jedem Server anders aus. Der Webserver von Sun erzeugt beispielsweise ganz andere Kennungen.

Eine Sich-Selbst-Implementierung

Der Zusammenhang zwischen inneren Klassen und äußeren Klassen und wie sie sich gegenseitig implementieren können.

Eine Klasse kann entweder von einer Klasse erben oder eine Schnittstelle erweitern. Es ergibt sich ein Sonderfall, wenn wir eine Schnittstelle implementieren, die innerhalb der eigenen Klasse liegt, die die Schnittstelle deklariert. Das sieht etwa so aus:

class Outer implements Outer.InterfaceInner
{
  interface InterfaceInner
  {
  void knexDirDeineWelt();
  }

  public void knexDirDeineWelt()
  {
  }
}

Prinzipiell spricht erst einmal nichts gegen diese Implementierung. Innere Klassen, wie InterfaceInner eine ist, werden auf eine extra Klassendateien abgebildet, da es innerer Klassen beziehungsweise Schnittstellen sowieso nicht für die Laufzeitumgebung gibt. In unserem Fall könnte der Compiler die Datei Outer$InterfaceInner erzeugen. Im nächsten Schritt würde dann Outerdiese Schnittstelle erweitern und wie im Beispiel eine Methode überschreiben.

So schön dies auch aussieht: Es funktioniert nicht. Frühe Compiler erlaubten diese Schreibeweise, aber sie führt zu zirkulären Abhängigkeiten.

cyclic inheritance involving Outer

Wenn InterfaceInner zuerst übersetzt würde und dann Outer, wäre es noch zu verstehen, doch das Problem machen zum Beispiel Deklarationen in der inneren Klasse, die abhängig sind von der Äußeren. Wir könnten etwa den Rückgabewert von knexDirDeineWelt() ändern, dass es ein Outer Objekt zurückliefert.

class Outer implements Outer.InterfaceInner
{
  interface InterfaceInner
  {
    Outer knexDirDeineWelt();
  }

  public void knexDirDeineWelt()
  {
  }
}

Jetzt sehen wir: Ohne InterfaceInner kein Outer, da dies knexDirDeineWelt() vorschreibt und ohne Outer kein InterfaceInner, da sonst der Rückgabewert nicht bekannt ist. Mitunter wäre das Problem noch lösbar, aber hier lässt der Compiler lieber die Finger von.

Innere Klasse vor der äußeren

Dass es nicht unmöglich ist, eine innere Klasse von der äußeren Abzuleiten zeigt folgendes Beispiel:

interface I
{
  void boo();
  interface J extends I
  {
  }

  J foo();
}

Es ist problemlos möglich, dass die innere Schnittstelle die äußere erweitert. Im Gegensatz zum vorherigen Beispiel ist in diesem Fall die Problematik genau umgekehrt. Es ist auch möglich, dass eine innere Klasse eine äußere erweitert.

class O
{
  class I extends O
  {
    void bar()
    {
    }
  }

  void bar() { }
}

Automatisches Neuladen von Servlet-Seiten

Über das implizite Objekt response lassen sich Antworten von der JSP-Seite an den Client formulieren. Das Setzen von Content-Type ist für nahezu alle Servlets unabdingbar. Daneben gibt es noch weitere, die beispielsweise für Cookies interessant sind. Ein spezieller Header kann auch das Caching beeinflussen (mit dem Datum der letzten Modifizierung) oder die Seite nach einer bestimmten Zeit neu laden. Letzteres wollen wir verwenden, um eine einfache Ausgabe zu erzeugen, die jede Sekunde neu geladen wird. (Die Seite darf jedoch nicht im Cache liegen. Um das Caching explizit auszuschalten, sollte Pragma: no-cache gesetzt werden. Bei einer lokalen Installation spielt dies jedoch keine Rolle.

<%! private String result = "*"; %>
<% response.setHeader( "Refresh", "1" ); %>
<%= result += "*" %>

Dieses Servlet erzeugt eine Reihe von Sternchen, wobei es sich die Zeichenkette jede Sekunde neu vom Server holt. Das dargestellte Programm zeigt in einfacher Weise auf, was sich noch wesentlich komplexer mit Threads anstellen lässt. Im Hintergrund hätten wir einen Thread starten können, der ständig eine neue Berechnungen durchführt, die wir dann in der println()-Zeile hätten ausgeben können.

Selbst definierte Cursor in AWT/Swing

Bei einem Cursor sind wir nicht allein auf die vordefinierten Muster angewiesen. Leicht lässt sich aus einer Grafik ein eigener Cursor definieren. Dazu bietet das Toolkit die Methode createCustomCursor() an. Als Argument geben wir ein Image-Objekt, einen Hotspot und einen Namen an. Der Hotspot definiert eine Art Nullpunkt, der die Spitze angibt. Zeigt etwa der Standardcursor mit einem Pfeil nicht wie üblich nach oben, sondern nach unten, so gibt der untere Punkt den Nullpunkt an. Der Name ist nur nötig, wenn Java-Accessibility genutzt wird, also eine Möglichkeit gegeben ist, den Cursor zum Beispiel ohne Maus anzusprechen.

Beispiel: Weise einer java.awt.Component den Cursor mit der Grafik cursor.gif und dem Hotspot auf (10,10) zu.

Cursor c = getToolkit().createCustomCursor(
new ImageIcon( "cursor.gif" ).getImage(),
new Point(10,10), "Cursor" );
component.setCursor( c );

Hinweis Animierte Cursor bietet die Java-Umgebung nicht an. Wir könnten selbstständig in einem Thread immer wieder mit setCursor() unterschiedliche Cursor setzen, um etwa eine drehende Sanduhr oder eine rotierende Festplatte zu bekommen.

Da grafische Oberflächen in der Regel keine Cursor beliebiger Auflösung und Farbanzahl zulassen, lässt sich das Toolkit auch über diese Größen erfragen. Die Methode getBestCursorSize() liefert die mögliche Größe des Cursors zurück. Es ist sinnvoll, diese Methode vorher aufzurufen, um ein passendes Bild auszuwählen. Ähnlich wie bei den Icons in der Titelleiste werden die Grafiken sonst skaliert, was nicht unbedingt schön aussehen muss. Gleiches gilt für die Farben. Nicht alle Systeme erlauben beliebig viele Farben für den Cursor. Die maximale Farbanzahl liefert die Funktion getMaximumCursorColors(). Notfalls wird der Cursor auf die Farbanzahl heruntergerechnet.

Tipp: Unterstützt die Plattform Cursor beliebiger Größe, so lässt sich dadurch einfach eine Bubble-Help realisieren, die nicht rechteckig ist. An Stelle des Cursors wird eine Grafik mit dem Cursor zusammen mit einer Hilfe angezeigt. Das Betriebssystem verwaltet den Cursor, und wir müssen den Hintergrund nicht sichern und mit der Hilfe verknüpfen.

abstract class java.awt.Toolkit

  • Cursor createCustomCursor( Image cursor, Point hotSpot, String name )

    throws IndexOutOfBoundsException

    Erzeugt ein neues Cursor-Objekt. Liegt der Hotspot außerhalb der Grenzen der Grafik, wird eine IndexOutOfBoundsException ausgelöst.

  • Dimension getBestCursorSize( int preferredWidth, int preferredHeight )

    Liefert die unterstützte Cursor-Größe, die den gewünschten Ausmaßen am nächsten liegt. Oft werden die Argumente ignoriert, wenn die Umgebung keine beliebige Cursor-Größe zulässt. Erlaubt das System überhaupt keine selbst definierten Cursor, erhalten wir ein Objekt der Dimension (0,0).
  • int getMaximumCursorColors()

    Liefert das Maximum an Farben, das das Toolkit für Cursor unterstützt. Der Rückgabewert ist null, wenn selbst definierte Cursor nicht gestattet sind.

public abstract class java.awt.Component implements ImageObserver, MenuContainer, Serializable

  • void setCursor( Cursor cursor )

    Weise einen neuen Cursor zu.

In Servlets Seiten über HTTP-Redirect umlenken

Ist eine Seite nicht mehr korrekt, kann sie umgelenkt werden. Hierfür wird ein spezieller Header gesetzt.

sendRedirect()

Dazu dient die Methode sendRedirect(String), die auf eine neue Seite verweist. Als Argument kann eine relative oder absolute URL aufgeführt werden, die auf eine temporäre neue Seite weist. Wir könnten auch mit setHeader() arbeiten, müssten dann aber von Hand den Statuscode ändern, der für Umleitungen auf 302 gesetzt sein muss. Die Arbeit können wir uns sparen. Nach dem Setzen der Umleitung sollte nicht mehr in die Ausgabe geschrieben werden.

Wozu kann nun diese Umleitung eingesetzt werden? Zum Beispiel, um über Formular-Parameter zu externen Seiten weiterzuleiten:

response.sendRedirect( url );

Nach der Umleitung steht der Ort der neuen Seite in der URL-Zeile des Browsers. Das folgende Programm verweist nun einfach auf ein anderes Servlet. Die Pfadangabe kann absolut oder relativ sein.

String url = "http://www.tutego.de/";
response.sendRedirect( url );

Was passiert beim Umlenken?

Technisch gesehen ist eine Umlenkseite eine ganz normale Webseite. Das wirkliche Umlenken ist eine Fähigkeit des Browsers und nicht des Servers. Dies ist wichtig anzumerken, weil eigene Programme, die URL-Verweise aufbauen, hier oft nicht korrekt vorgehen.

Das Servlet setzt beim sendRedirect() den Content-Type auf "text/html". Wichtig sind zwei weitere Informationen: die eine in der Statuszeile und die andere im Header. In der Statuszeile wird die Nummer 302 gesendet, die das Umlenken bezeichnet. Die Information darüber, wohin verwiesen wird, steht in einem weiteren Header namens »Location«. Somit können wir unser Redirect prinzipiell auch selbst ausformulieren, indem wir Folgendes schreiben:

response.setStatus( 302 );
response.setContentType( "text/html" );
response.setHeader( "Location", url );

Der String url ist dann eine Referenz auf die neue Seite. Der Verweis auf die externe Seite muss dann natürlich absolut sein. Dies regelt jedoch sendRedirect() automatisch.

Rechte (Permissions) und Mengenbeziehungen

Für jede geladene Klassen gilt eine Sammlung von Rechten, die für diese Klasse vergeben wurden. In der Regel wurden sie per Policy-Datei vergeben, doch natürlich sind auch andere Möglichkeiten denkbar. Diese Sammlung selbst wird in einem PermissionCollection-Objekt gespeichert, welches einer ProtectionDomain zugeordnet ist.

ProtectionDomain domain = ListPermissions.class.getProtectionDomain();
PermissionCollection permissonColl = Policy.getPolicy().getPermissions( domain );

Dem PermissionCollection-Objekt lässt sich mit einer Enumeration die gespeicherten Permissions rauskitzeln. Ein System.out liefert ebenso eine schöne Ausgabe, etwa für das eigene Programm ListPermissions ohne Sicherheitsmanager:

java.security.Permissions@c21495 (
(java.util.PropertyPermission java.specification.vendor read)
(java.util.PropertyPermission java.vm.specification.vendor read)
(java.util.PropertyPermission path.separator read)
(java.util.PropertyPermission java.vm.name read)
(java.util.PropertyPermission java.class.version read)
(java.util.PropertyPermission os.name read)
(java.util.PropertyPermission java.vendor.url read)
(java.util.PropertyPermission java.vendor read)
(java.util.PropertyPermission java.vm.vendor read)
(java.util.PropertyPermission file.separator read)
(java.util.PropertyPermission os.version read)
(java.util.PropertyPermission java.vm.version read)
(java.util.PropertyPermission java.version read)
(java.util.PropertyPermission line.separator read)
(java.util.PropertyPermission java.vm.specification.version read)
(java.util.PropertyPermission java.specification.name read)
(java.util.PropertyPermission java.vm.specification.name read)
(java.util.PropertyPermission java.specification.version read)
(java.util.PropertyPermission os.arch read)
(java.lang.RuntimePermission exitVM)
(java.lang.RuntimePermission stopThread)
(java.net.SocketPermission localhost:1024- listen,resolve)
(java.io.FilePermission \D:\JavaBook\programme\24_Sicherheitskonzepte\- read)
)

Die Rechte sind natürlich genau diejenigen, die vom System bereitgestellt worden sind. Sie beziehen sich genau auf unsere Klasse ListPermissions. Für die Systemklassen, wie java.lang.Object oderString gelten keine Einschränkungen. Ersetzen wie ListPermissions durch Objekt, so würde ein System.out genau die alles-erlaubende Permission ergeben:

(java.security.AllPermission <all permissions> <all actions>)

Schließt eine Permission eine andere Permission ein?

Permission-Objekte definieren selbst nicht nur Rechte für spezielle Eigenschaften (Lesen in einem speziellen Verzeichnis), sondern ziehen auch Recht für andere Eigenschaften nach sich (Lesen aller Dateien ab einem Verzeichnis). Wird etwa für ein Verzeichnis das Recht auf Lesen und Schreiben gesetzt, dann impliziert dies auch das Lesen. Die Fähigkeit, dass ein Recht ein anderes bedingt, ist eine Fähigkeit der Permission-Objekte. Die Klasse bietet eine implies()-Funktion, die testet, ob eine Permission eine andere Permission einschließt.

Beispiel: Wir wollen zwei FilePermission-Objetke anlegen, wobei das erste (perm1) das zweite (perm2) einschließt.

import java.io.FilePermission;
import java.security.Permission;

public class PermissionImplies
{
  public static void main( String args[] )
  {
    Permission perm1 = new FilePermission( "c:\\windows\\*", "read,write" );
    Permission perm2 = new FilePermission( "c:\\windows\\fonts", "read" );
    
    if ( perm1.implies(perm2) )
      System.out.println( perm1 + " implies " + perm2 );
    
    if ( !perm2.implies( new FilePermission("c:\\windows\\fonts", "write") ) )
      System.out.println( perm1 + " not implies " + perm2 );
  }
}

Die Ausgabe zeigt, dass diese Rechte vom System korrekt unterstützt werden.

(java.io.FilePermission c:\windows\* read,write) implies (java.io.FilePermission c:\windows\fonts read)
(java.io.FilePermission c:\windows\* read,write) not implies (java.io.FilePermission c:\windows\fonts read)