Der Introspektionsmechanismus erlaubt es anderen Komponenten oder dem Container, in dem sich die JavaBeans-Komponente befindet, Einblick in den inneren Aufbau der Komponente zu erhalten. Dies ist besonders dann sinnvoll, wenn JavaBeans-Komponenten in einer visuellen Entwicklungsumgebung zusammengesetzt werden. Der Anwender muss dann nicht länger die Dokumentation des jeweiligen Beans zu Rate ziehen, da die Entwicklungsumgebung Informationen darüber zur Verfügung stellt, welche Eigenschaften, Events und Methoden die jeweilige JavaBeans-Komponente zur Verfügung stellt. Das Introspektionskonzept ist aus diesem Grund ein Prozess, der es erlaubt, Informationen über Eigenschaften, Events und Methoden eines Beans automatisch abzuleiten. Ziel der Introspektion ist es, den Entwickler von einer Vielzahl von Informationen abzuschirmen. Aus diesem Grund und um es einer Entwicklungsumgebung zu ermöglichen, Beans zu analysieren, existiert eine Introspektionsklasse, die die Inspektion in Java ermöglicht. Hierzu werden ein Standard-Interface und verschiedene Design-Patterns dazu eingesetzt, um die Beans zu analysieren. Introspektionsarten In Java werden zwei Introspektionsarten verwendet: Die implizite und die explizite Introspektion. Die implizite Methode basiert auf einem einfachen Reflexionsmechanismus, der alle von der Klasse unterstützten Methoden als Ergebnis liefert. Mit Hilfe von Design-Patterns (siehe Kapitel 9.2) werden anschließend die Eigenschaften, Events und Methoden abgeleitet. Der explizite Ansatz verwendet das Interface BeanInfo, das auf Anfrage Deskriptor-Objekte liefert, die die gewünschten Informationen enthalten. Da man ein Objekt, dass das BeanInfo-Interface implementiert, selbst implementieren kann, ist beim zweiten Ansatz die Wahl der Methodennamen der JavaBeans-Komponenten beliebig. Introspektoren Da die Ermittlung von Informationen über ein Bean auf einem vorgegebenen Algorithmus basieren muss, übernimmt diese Aufgabe eine besondere Klasse: Die Introspektionsklasse. Diese Klasse durchläuft die Vererbungshierarchie eines Beans und erfragt die notwendigen Informationen bei den verschiedenen BeanInfo-Objekten. Wenn kein Objekt gefunden wird, das das BeanInfo-Interface implementiert, verwendet die Introspektionsklasse die Technik der Reflexion und ermittelt aus den Methodensignaturen die fehlenden Informationen. Als Ergebnis liefert die Introspektionsklasse ein BeanInfo-Objekt, das über alle Informationen eines Beans verfügt. Abb. 9.12: Bean-Analyse mittels Introspektion Implizite Introspektion durch Reflexion Die implizite Introspektion basiert auf den in JavaBeans verwendeten Design-Patterns. Wie bereits bei der Namensvergabe von Eigenschaften erläutert wurde, können Informationen über Beans aus Methodennamen abgeleitet werden. Aus der Existenz der Methoden public <propertyType> getTheProperty(); public void setTheProperty( <propertyType> theValue); kann auf die Schreib-/Lese-Eigenschaft theProperty vom Typ PropertyType geschlossen werden. Als Beispiel würde die automatische Introspektion von JavaBeans das Objekt anzahlDerSchiffe als einfache Eigenschaft vom Typ int identifizieren, wenn folgende Code-Zeilen im Bean vorkommen: public int getAnzahlDerSchiffe(); public void setAnzahlDerSchiffe(int n); Diese Art der Codierung von Informationen in Methodennamen besitzt Vor- und Nachteile. Als Nachteil gilt die Einschränkung der Namenswahl der Eigenschaftsmethoden. Zu den Vorteilen zählen unter anderem: leichtere Lesbarkeit von Code ermöglicht schnelleren Einsatz fremder Klassenbibliotheken Kein Zusatzaufwand für die Introspektion. Explizite Introspektion: BeanInfo-Klasse Um die im letzten Absatz genannte Einschränkung der Namenswahl zu vermeiden bzw. um eine erweiterte Flexibilität bei der Definition von Eigenschaften von JavaBeans-Komponenten zu ermöglichen, steht eine zweite Art der Introspektion zur Verfügung, die explizite Introspektion, die mittels der Klasse BeanInfo realisiert ist. In Java können beliebige Objekte das BeanInfo-Interface implementieren und damit Methoden verwenden, die Informationsobjekte zu einzelnen Beans liefern. Darüber hinaus ist durch die Angabe eines Customization-Objekts (siehe Kapitel 9.6) eine dialoggesteuerte Anpassung eines Beans an seine Umgebung möglich. Zu jedem Bean kann folglich die Metaklasse BeanInfo instantiiert werden, die durch den Namen, den Namen des Beans und die Endung BeanInfo charakterisiert ist. Für ein Bean-Objekt namens Schiff.java wäre bspw. die Klasse SchiffBeanInfo.java die komplementäre BeanInfo-Klasse. Mittels der Klasse BeanInfo beschreibt der Entwickler explizit die wichtigsten Eigenschaften eines Beans. Im regulären Fall ist die BeanInfo-Klasse im gleichen Package wie die Bean-Klasse enthalten. Zur Verwendung muss die BeanInfo-Klasse das Interface java.beans.BeanInfo implementieren, das Methoden zur Analyse der als public deklarierten Informationen eines Beans bereitstellt. Die Klasse muss weiterhin eine Liste an Methoden, Events und Eigenschaften, die als public deklariert sind, zur Verfügung stellen. Stellt die BeanInfo-Klasse lediglich Teilinformationen zur Verfügung, bspw. nur Informationen über Events, oder ist keine derartige Klasse vorhanden, so fordert die Entwicklungsumgebung die notwendigen Informationen implizit über das Java-Reflection-API an. Aus Gründen der Programmiereffizienz empfielt es sich, die BeanInfo-Klasse immer als Subtyp der Klasse SimpleBeanInfo aufzubauen. Die SimpleBeanInfo-Klasse enthält für alle erforderlichen Funktionen eine Standardimplementierung, die im Regelfall als Rückgabewert den Wert null zurückgibt. Der Wert null ist in diesem Fall nicht gleich der natürlichen Zahl null, sondern weist auf eine fehlende Definition hin. Das folgende Beispiel demonstriert eine mögliche Implementierung einer BeanInfo-Klasse für das oben eingeführte Bean myBean1. Diese Klasse legt fest, welches Icon das Bean repräsentieren soll. Alle weiteren Informationen werden durch die Reflexion gewonnen: import java.beans.*; public class myBean1BeanInfo extends SimpleBeanInfo { public java.awt.Image getIcon(int iconKind) { if (iconKind == BeanInfo.ICON_COLOR_16x16) { java.awt.Image img = loadImage("myIcon16_16.gif"); return img; } if (iconKind == BeanInfo.ICON_COLOR_32x32) { java.awt.Image img = loadImage("myIcon32_32.gif"); return img; } return null; } } Wie Abb. 9-12 zu entnehmen ist, werden für die explizite Introspektion die im Folgenden beschriebenen Klassen benötigt. Klasse FeatureDescriptor Die Klasse FeatureDescriptor ist die Superklasse aller anderen Klassen, die in diesem Abschnitt beschrieben sind. Sie enthält allgemeine Informationen, die für alle Unterklassen gültig sind. Klasse BeanDescriptor Diese Klasse stellt allgemeine Informationen über eine JavaBeans-Komponente zur Verfügung, bspw. den Namen des Beans und den Bean-Customizer. Die Klasse enthält zwei Konstruktoren. Der erste Konstruktor enthält als Parameter den Namen des Beans: public BeanDescriptor getBeanDescriptor() { BeanDescriptor bd = new BeanDescriptor(beanClass); return bd; } Der zweite Konstruktor enthält zwei Parameter: Den Namen des Beans und dessen Customizer-Objekt: public BeanDescriptor getBeanDescriptor() { BeanDescriptor bd = new BeanDescriptor(beanClass,customizerClass); return bd; } Klasse PropertyDescriptor Diese Klasse stellt allgemeine Informationen über die Eigenschaften eines Beans zur Verfügung. Die Funktion getPropertyDescriptors liefert einen Array zurück, der alle Eigenschaften eines Beans enthält. Hierbei existieren drei Verfahren, mit denen einer JavaBeans-Komponente explizit Eigenschaften zugeordnet werden können. Diese können über den Konstruktor dieser Klasse geregelt werden. Der erste Konstruktor enthält als Parameter den Namen der Eigenschaft und das zugehörige Bean: PropertyDescriptor(String propertyName, Class beanClass) Der zweite Konstruktor enthält vier Parameter: Zusätzlich zum Namen der Eigenschaft und zum zugehörigen Bean werden hier auch Setter- und Getter-Methoden angegeben: PropertyDescriptor(String propertyName, Class beanClass, String getterMethodenName, String setterMethodenName) Der dritte Konstruktor enthält nur den Namen der Eigenschaft und deren Setter- und Getter-Methoden. Dieser Konstruktor wird allerdings selten verwendet. PropertyDescriptor(String propertyName, String getterMethodenName, String setterMethodenName) Jeder der Konstruktoren erzeugt eine Exception vom Typ IntrospectionException, wenn ein Fehler auftritt. Eine mögliche Anwendung der Klasse PropertyDescriptor in einer BeanInfo-Klasse sieht wie folgt aus: import java.beans.*; public class myBean1BeanInfo extends SimpleBeanInfo { public PropertyDescriptor[] getPropertyDescriptors() { try{ PropertyDescriptor farbe = new PropertyDescriptor("farbe", myBean1, "getFarbe", "setFarbe"); PropertyDescriptor preis = new PropertyDescriptor("preis", myBean1, "getPreis", "setPreis"); PropertyDescriptor[] pd = { farbe, preis }; return pd; } catch (IntrospectionException e) { throw new Error(e.toString()); } } } Diese Klasse kann aber nur für die normalen Eigenschaften verwendet werden. Handelt es sich bei einer Eigenschaft um eine indizierte Eigenschaft, so muss analog die Klasse IndexedPropertyDescriptor verwendet werden, die eine Erweiterung der Klasse PropertyDescriptor darstellt. Auch für diese Klasse existieren wiederum verschiedene Konstruktoren. Da die Verwendung dieser Klasse dem Umgang mit der PropertyDescriptor-Klasse sehr ähnlich ist, wird hier nur der ausführliche Konstruktor erläutert: public IndexedPropertyDescriptor(String propertyName, Class beanClass, String getterMethodenName, String setterMethodenName, String indexedGetterMethodenName, String indexedSetterMethodenName) throws IntrospectionException Klasse MethodDescriptor Das Komponentenmodell, das in JavaBeans verwendet wird, definiert alle als public deklarierten Methoden eines Beans als Komponentenmethoden. Die Zugriffsmethoden der Eigenschaften bzw. der Events gehören nicht zu dieser Kategorie. Der Zugriff auf diese Methoden ist daher wie bei einer regulären Java-Klasse jederzeit erlaubt. Derartige Methoden werden durch die Reflexionseigenschaft von Java in der visuellen Entwicklungsumgebung dargestellt. Möchte der Programmierer von Beans dem Entwickler, der JavaBeans in einer Entwicklungsumgebung einsetzt, allerdings Methoden vorenthalten, so empfiehlt es sich, diese Klasse einzusetzen. Im folgenden Beispiel sei angenommen, dass ein Bean zwei als public deklarierte Methoden beinhaltet, die Methoden copy() und delete(). Die Entwicklungsumgebung soll allerdings lediglich die Methode delete() anbieten. Zur Implementierung dieses Beispiels ist die folgende BeanInfo-Klasse notwendig: import java.beans.*; public class myBean1BeanInfo extends SimpleBeanInfo { public MethodDescriptor[] getMethodDescriptors() { try{ MethodDescriptor md1 = new MethodDescriptor(getMethod(myBean1.class, "delete")); MethodDescriptor[] md = { md1 }; return md; } catch (IntrospectionException e) { throw new Error(e.toString()); } } } Klasse EventSetDescriptor Diese Klasse stellt allgemeine Informationen über Events, die ein Bean auslösen kann, zur Verfügung. Zur Unterscheidung der Events durch diese Klasse sind nicht die Events selbst wesentlich, sondern die Anzahl der verschiedenen EventListener-Objekte, die das JavaBean verwaltet. Die Spezifikation dieser Klasse ist ähnlich wie bei den anderen bisher erläuterten Deskriptoren, allerdings beinhaltet diese Klasse eine Vielzahl von Konstruktoren. Der einfache Konstruktor verlangt als Argument lediglich den Namen des Beans, das den Event auslöst, den Event-Namen, den Namen des Listener-Objekts und die Methode, die beim Event-Listener aufgerufen wird, wenn der Event eintritt. public EventSetDescriptor(Class sourceClass, String eventSetName, Class listenerType, String listenerMethodName) throws IntrospectionException Zusammenfassung Beans und Anwendungen tauschen mittels Events diverse vorab definierte Informationen und Zustandsänderungen aus. Sie verwenden dabei das flexible Ereignismodell des JDKs, um zur Laufzeit dynamisch den Ereignisfluss steuern zu können. Der Entwickler eines Beans kann hierbei alle Möglichkeiten von Java nutzen, die Sichtbarkeit (im Sinne der Aufrufbarkeit) von außen frei zu bestimmen. Der Endanwender kann ausschließlich die als public deklarierte Methoden eines Beans direkt aufrufen. Jede Funktionalität, die der Entwickler gekapselt hat, bleibt dem Benutzer verborgen. Über die optionale Klasse BeanInfo ist es darüber hinaus möglich, gezielt auf das äußere Erscheinungsbild eines Beans Einfluss zu nehmen. Dieser Einfluss kann mittels des Introspektionskonzepts erreicht werden. In diesem Unterkapitel wurde auf die Introspektion und ihre Bedeutung für die Programmierung von JavaBeans-Komponenten eingegangen. Dabei wurde darauf hingewiesen, dass vorrangig die Klasse SimpleBeanInfo anstelle der Klasse BeanInfo zu implementieren ist, da der Programmierer hierbei auf Implementierung aufwendiger BeanInfo-Klassen verzichten kann, und da Entwicklungsumgebungen Gebrauch von den Reflexionseigenschaften machen. Sollen Informationen verborgen werden, so muss die Klasse BeanInfo implementiert werden. Beinhaltet diese Klasse nicht alle der in diesem Unterkapitel beschriebenen Deskriptorklassen (PropertyDescriptor, EventSetDescriptor und MethodDescriptor), so wird bei den fehlenden Deskriptorklassen auf das Prinzip der Reflexion zurückgegriffen. Zum Abschluss dieses Unterkapitels wurden die Klassen und Schnittstellen des JavaBeans-APIs beschrieben, die die Introspektion ermöglichen.