Java Beans
Willemers Informatik-Ecke
Eine Bean ist eine Klasse, deren Attribute als Properties bezeichnet werden.

PropertyChange und Veto

Das Besondere an einer Bean ist, dass Änderungen an den Properties durch Ereirgnisse weiterleiten.

Ein Attribut kann als Property verwendet werden, wenn es als privates Attribut definiert ist und passende Methoden existieren, die den Zugriff erlauben. Dazu ist konventionsgemäß folgende Benennung erforderlich:

private String name = "";
public String getName() {
    return name;
}
public void setName(String str) {
    name = str;
}

Beispiel: Person und Personenkontrolle

Die folgende Bean beschreibt eine Person. Sie hat die Property name, die durch die Methoden getName und setName gelesen und geschrieben werden können.

Die Bean bietet Methoden an, um sich als PropertyChangeListener und als VetoableChangeListener einzutragen und wieder abzumelden. Der Unterschied zwischen beiden liegt darin, dass der erstere über eine Änderung der Property informiert wird. Der zweitere kann ein Veto einlegen und eine Änderung durch Werfen einer Exception unterbinden.

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;

public class Person {
    private String name = ""; // Diese Eigenschaft soll ueberwacht werden
    public static final String NAME_ATTR = "NameAttribute";

    private PropertyChangeSupport changes = new PropertyChangeSupport(this);
    private VetoableChangeSupport vetos = new VetoableChangeSupport(this);

    // Im Setter wird bei einer Aenderung ein Veto angeboten und
    // die Aenderung gemeldet.
    public void setName(String neuName) throws PropertyVetoException  {
        String oldName = this.name; // sichere den Bestand
        // Informiere die Veto-Maechte, die ggf eine Exception
        // ausloesen, um die darauf folgenden Schritte zu vereiteln.
        vetos.fireVetoableChange(NAME_ATTR, oldName, neuName);

        this.name = neuName; // Die Aenderung erfolgt
        // Nun werden die angemeldeten Listener informiert:
        changes.firePropertyChange(NAME_ATTR, oldName, neuName);
    }

    // Der Getter ist voellig unauffaellig
    public String getName() {
        return name;
    }

    // Anmeldung fuer Aenderungs-Events des kompletten Objekts
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        changes.addPropertyChangeListener(listener);
    }
    
    // Anmeldung fuer Aenderungs-Events eines speziellen Attributs
    public void addPropertyChangeListener(String propStr, PropertyChangeListener listener) {
        changes.addPropertyChangeListener(propStr, listener);
    }

    // Abmeldung
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        changes.removePropertyChangeListener(listener);
    }

    // Anmeldung fuer den Widerspruch gegen eine Aenderung
    public void addVetoableChangeListener(VetoableChangeListener listener) {
        vetos.addVetoableChangeListener(listener);
    }

    // Abmeldung
    public void removeVetoableChangeListener(VetoableChangeListener listener) {
        vetos.removeVetoableChangeListener(listener);
    }

}
Die folgende Klasse zeigt, wie die Überwachung einer Property-Änderung funktioniert. Sie legt sich ein Objekt der Klasse Person an. Darüber meldet sie zunächst einen ChangeListener an, der hier einfach anzeigt, dass sich etwas geändert hat.

Im zweiten Schritt meldet sie sich für ein Veto an. Wie oben gesehen wird die Bean dieses Ereignis direkt vor einer Änderung abfeuern. So kann der Überwacher rechtzeitig eine Exception werfen, die eine Änderung verhindert.

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;

/**
 * Kleines Programm zur Demonstration eines PropertyChangeListeners
 */
public class PersonenKontrolle {

    public PersonenKontrolle() {
        Person person = new Person();
        // Anmelden fuer alle PropertyChangeEvents des Objekts person
        person.addPropertyChangeListener(new PropertyChangeListener() {
            // Im Falle eines firePropertyChange wird dies aufgerufen:
            @Override
            public void propertyChange(PropertyChangeEvent e) {
                System.out.println(
                        "Aenderung an: " + e.getPropertyName()
                        + "bisher: " + e.getOldValue() + " nun: "
                        + e.getNewValue());
            }
        });
        // Anmeldung fuer fireVetoableChange zur Verhinderung einer Aenderung
        person.addVetoableChangeListener(new VetoableChangeListener() {
            @Override
            public void vetoableChange(PropertyChangeEvent e)
                    throws PropertyVetoException {
                if (Person.NAME_ATTR.equals(e.getPropertyName())) {
                    // Die Eigenschaft NAME_ATTR soll geaendert werden.
                    String neuName = (String) e.getNewValue();
                    if (neuName.equals("Kevin")) {
                        // Die Aenderung soll "Kevin" sein.
                        // Das muss durch Werfen einer Exception verhindert werden.
                        throw new PropertyVetoException(
                                "Alles, nur nicht Kevin!", e);
                    }
                }
            }
        });
        // Nun testen wir die Aenderungs-Listener!
        try { // Es kann und wird Exceptions hageln!
            person.setName("Martin"); // Aenderung wird gemeldet
            person.setName("Martin"); // Keine Aenderung, keine Meldung
            person.setName("Kevin"); // Das duerfte am Veto scheitern
            person.setName("Matthias"); // Wird wegen der Exception nicht mehr erreicht
        } catch (PropertyVetoException e) {
            System.out.println(""+e.getMessage()); // Zeige mal den Kommentar!
            //e.printStackTrace(); // Komplette Exception darstellen
        }
        // Tatsächlich: Es bleibt bei Martin!
        System.out.println("Name ist nun: " + person.getName());
    }
    public static void main(String[] args) {
        new PersonenKontrolle(); // Starte Instanz!
    }

}