11. JavaBeans
11.1 Wprowadzenie
JavaBeans to model przenośnych komponentów, stworzony w języku
Java (od 1996 r.), przeznaczony do tworzenia małych,
komponentów
programowych
wielokrotnego
użytku,
w
szczególności wykorzystywanych w narzędziach programowania
wizualnego (np. BeanBox, VisualAge, JBuilder).
Bean jest komponentem programowym – może być obiektem
wizualnym lub niewizualnym (jak np. kolejka czy stos), który może
wchodzić w skład apletów i aplikacji.
Pakiet java.beans należy do podstawowego “Core API” środowiska
Java.
W. Kasprzak: Programowanie zdarzeniowe.
11- 1
11. JavaBeans
Ziarno ( Bean) jest klasą, z której można korzystać w narzę dziu projektowym (w fazie tworzenia programu), gdyż posiada ona:
1. gwarantowane cechy podstawowe, takie jak serializacja i
klonowanie;
2. implementuje ustalone konwencje (nazw);
3. deklaratywna (abstrakcyjna) postać składowych 3 rodzajów:
a. zdarzenia,
b. własności,
c. metody.
4.korzysta z mechanizmu introspekcji i refleksji (meta-danych).
W. Kasprzak: Programowanie zdarzeniowe.
11- 2
Gwarantowane cechy podstawowe:
• bezparametrowy konstruktor (wzorce „Factory“),
• interfejs Clonable, metoda Clone ;
• interfejs Serializable, pusty bez metod (serializacja składowych za wyjątkiem transient i static) - metody writeObject, readObject; lub
• interfejs Externalizable, metody writeExternal, readExternal.
Konwencje nazw
• Zdarzenie E – para metod addEListener, removeEListener
• Własność P – para metod (getP, setP), względnie (isP, setP) dla
logicznego (Boolean) P:
- własność związana – wymaga powiadamiania obserwatorów
zdarzenia typu PropertyChange
- własność z ograniczeniem - z prawem weta klientów, realizowana
poprzez wyjątek PropertyVetoException
W. Kasprzak: Programowanie zdarzeniowe.
11- 3
11. JavaBeans
11.2 Definiowanie zdarzeń dla klasy ziarna
EventObject
Klasa java.util.EventObject jest bazową klasą dla reprezentacji zdarzeń dla komponentów „ziaren” ( Bean).
public class EventObject extends Object
implements java.io.Serializable {
public EventObject (Object source);
public Object getSource();
public String toString();
}
Można korzystać bezpośrednio z tej klasy dla wyrażenia zdarzenia, ale
zwykle jednak tworzy się jej klasy pochodne dla specyficznych rodzajów
zdarzeń. Wtedy obiekt obsługi zdarzenia (obserwator) uzyskuje w
powiadomieniu o zdarzeniu obiekt specyficznej klasy pochodnej od
EventObject.
Np.
public class HireEvent extends EventObject {
W. Kasprzak: Programowanie zdarzeniowe.
11- 4
private long hireDate;
public HireEvent (Object source) {
super (source);
hireDate = System.currentTimeMillis();
}
public HireEvent (Object source, long hired) {
super (source);
hireDate = hired;
}
public long getHireDate () {
return hireDate;
}
}
EventListener
Pusty interfejs EventListener służy do zaznaczania klasy jako
potencjalnego obserwatora zdarzeń. Konkretny rodzaj obsługi zdarzeń
wynika z implementowania specyficznego interfejsu EventTypeListener, pochodnego od EventListener.
W. Kasprzak: Programowanie zdarzeniowe.
11- 5
11. JavaBeans
Np. dla nowego zdarzenia HireEvent odpowiedni interfejs nazywałby się HireListener. Taki interfejs deklaruje metody obsługi tego zdarzenia. Np.
public interface HireListener extends java.util.EventListener {
public abstract void hired (HireEvent e); // Metoda obsługi zdarzenia
}
Źródło zdarzenia
Obiekty obserwatorów rejestrują się w obiekcie-źródle zdarzenia lub
wyrejestrowują dzięki odpowiednim metodom rejestracji, tworzonym
według wzoru:
public synchronized void add ListenerType( ListenerType l); public synchronized void remove ListenerType( ListenerType l); Przykładowy kod obu metod dla naszego komponentu:
private Vector hireListeners = new Vector();
public synchronized void addHireListener ( HireListener l) {
hireListeners. addElement (l);
}
public synchronized void removeHireListener ( HireListener l) {
hireListeners. removeElement (l);
W. Kasprzak: Programowanie zdarzeniowe.
11- 6
}
Po zajściu zdarzenia wymagane jest powiadomienie obserwatorów tego
zdarzenia. Np. zrealizujemy metodę dla rozgłaszania zdarzenia:
protected void notifyHired () {
Vector l;
// Utwórz obiekt opisu zdarzenia:
HireEvent h = new HireEvent (this);
// Kopiuj wektor z obiektami obsługi – zabezpiecza to przed zmianą
// obserwatorów podczas rozgłaszania:
synchronized (this) {
l = (Vector)hireListeners.clone();
}
// Powiadom po kolei wszystkich obserwatorów:
for (int i=0; i<l.size(); i++) {
HireListener hl = (HireListener)l.elementAt (i);
hl.hired(h);
}
}
W. Kasprzak: Programowanie zdarzeniowe.
11- 7
11. JavaBeans
11.3 Własności
Jest to publiczna cecha klasy ziarna, reprezentowana zwykle przez
nie-publiczne pole. Może mieć dostęp: read-write, read-only, lub
write-only. Wyróżnia się 4 rodzaje własności:
• zwykłe,
• indeksowane (indeksery),
• związane,
• ograniczone.
Zwykłe własności
Dla własności P o dostępie „read-write” wymagana jest definicja pary metod – konwencją jest nazywanie ich: set P, get P.
Np. własność salary :
float salary;
public void setSalary (float newSalary) {
salary = newSalary;
}
W. Kasprzak: Programowanie zdarzeniowe.
11- 8
public float getSalary () {
return salary;
}
Własności typu logicznego mogą zamienić metodę get P na is P. Np.
boolean trained;
public void setTrained (boolean trained) {
this.trained = trained;
}
public boolean isTrained () {
return trained;
}
Indeksery
Indekser to własność przechowująca tablicę wartości. Konwencja:
public void set PropertyName ( PropertyType[] list)
public void set PropertyName ( PropertyType element, int position)
public PropertyType[] get PropertyName ()
public PropertyType get PropertyName (int position)
W. Kasprzak: Programowanie zdarzeniowe.
11- 9
11. JavaBeans
Np. indekser item w klasie List może wyglądać następująco:
public class ListBean extends List {
public String[] getItem () {
return getItems ();
}
public synchronized void setItem (String item[]) {
removeAll();
for (int i=0;i<item.length;i++)
addItem (item[i]);
}
public void setItem (String item, int position) {
replaceItem (item, position)
}
}
W. Kasprzak: Programowanie zdarzeniowe.
11- 10
Własności związane
Teraz uczynimy własność salary własnością związaną. Jeśli dwa obiekty korzystają z tej własności to o jej zmianie, powodowanej przez jeden z
nich, jest powiadamiany drugi obiekt.
W
tym
celu
należy
zrealizować
listę
obserwatorów
dla
PropertyChangeEvent poprzez klasę PropertyChangeSupport.
Najpierw tworzymy listę obserwatorów:
private PropertyChangeSupport changes = new PropertyChangeSupport (this);
Należy nią zarządzać:
public void addPropertyChangeListener ( PropertyChangeListener p) {
changes.addPropertyChangeListener (p);
}
public void removePropertyChangeListener (PropertyChangeListener p) {
changes.removePropertyChangeListener (p);
}
W. Kasprzak: Programowanie zdarzeniowe.
11- 11
11. JavaBeans
Jeśli zajdzie zdarzenie (tu jest nim wywołanie metody setSalary),
sprawdzamy czy wartość własności uległa zmianie i jeśli tak, to
powiadamiamy obserwatorów:
public void setSalary (float salary) {
Float oldSalary = new Float (this.salary);
this.salary = salary;
changes.firePropertyChange (
"salary", oldSalary, new Float (this.salary));
}
Po stronie obserwatora potrzebna jest metoda propertyChange :
public void propertyChange(PropertyChangeEvent e);
W metodzie tej sprawdzamy metodą getPropertyName to czy otrzymaliśmy
zdarzenie właściwego typu - PropertyChangeEvent .
Własności z ograniczeniami
Te własności są podobne do własności związanych ale dodatkowo do listy
PropertyChangeListener ziarno zarządza listą VetoableChangeListener.
Wtedy przed wykonaniem zmiany własności ziarno pyta obserwatorów na
W. Kasprzak: Programowanie zdarzeniowe.
11- 12
liście VetoableChangeListener o pozwolenie. Jeśli odpowiedź jest
negatywna to ziarno zgłasza wyjątek PropertyVetoException, deklarowany
przez metodę set P.
Np. uzupełniamy własność salary o :
private VetoableChangeSupport vetoes = new VetoableChangeSupport (this);
public void addVetoableChangeListener ( VetoableChangeListener v) {
vetoes.addVetoableChangeListener (v);
}
public void removeVetoableChangeListener (VetoableChangeListener v){
vetoes.removeVetoableChangeListener (v);
}
Zmieniamy metodę setSalary:
public void setSalary (float salary) throws PropertyVetoException {
Float oldSalary = new Float(this.salary);
vetoes.fireVetoableChange("salary", oldSalary, new Float(salary)); this.salary = salary;
changes.firePropertyChange("salary", oldSalary, new Float(this.salary));
}
W. Kasprzak: Programowanie zdarzeniowe.
11- 13
11. JavaBeans
Po
stronie
obserwatora
dodajemy
metodę
interfejsu
VetoableChangeListener o nazwie vetoableChange :
public void vetoableChange(PropertyChangeEvent e)
throws PropertyVetoException;
Możliwe jest też rozdzielenie obu list – zdarzenia zmiany własności i
zdarzenia wetowalnej zmiany własności – zarządzanie oddzielną listą dla
każdej własności. Wzorzec tego rozwiązania:
public void add PropertyName Listener ( PropertyChangeListener p);
public void remove PropertyName Listener ( PropertyChangeListener p);
public void add PropertyName Listener (VetoableChangeListener v);
public void remove PropertyName Listener (VetoableChangeListener v);
W. Kasprzak: Programowanie zdarzeniowe.
11- 14
11.4 Trwałość / serializacja
Ziarna są obiektami ze stanen i powinny być trwałymi obiektami.
1) Standardowa serializacja - interfejs Serializable
Jest to zdolność obiektu do zapamiętania swojego stanu w celu
późniejszego odtworzenia. W tym celu komponenty korzystają z
mechanizmu serializacji w języku Java.
Podstawowe interfejsy w zakresie serializacji to ObjectInput i
ObjectOutput a podstawowe klasy implementujące te interfejsy to
ObjectInputStream i ObjectOutputStream.
Obiekt klasy implementującej Serializable jest serializowany metodą
ObjectOutput.writeObject
(z
obiektem
będącym
parametrem)
i
deserializowany metodą ObjectInput.readObject.
Standardowa realizacja mechanizmu serializacji pozwala zapamiętać
wszystkie niestatyczne i nietymczasowe ( non-transient) pola obiektu w W. Kasprzak: Programowanie zdarzeniowe.
11- 15
11. JavaBeans
pliku binarnym o formacie danych przyjętym w metodach writeObject /
readObject. Plik posiada rozszerzenie .ser.
Jeśli w polu serializowanego obiektu referowany jest inny obiekt, to
również ten inny obiekt jest rekursywnie zapamiętywany - ta
dekompozycja jest kontynuowana, aż pozostaną wyłącznie dane typów
prostych, które są bezpośrednio zapamiętywane.
Przykład. Utwórzmy obiekty klasy TreeNode:
TreeNode top = new TreeNode("top");
top.addChild(new TreeNode("left child"));
top.addChild(new TreeNode("right child"));
Zapamiętujemy te obiekty:
FileOutputStream fOut = new FileOutputStream("test.ser"); ObjectOutput out = new ObjectOutputStream(fOut);
out.writeObject(top); // Zapisz obiekt – zapisze również jego 2
// podobiekty
out.flush();
out.close();
W. Kasprzak: Programowanie zdarzeniowe.
11- 16
Po zakończeniu programu i ponownym jego restarcie możliwe będzie
odtworzenie obiektu na podstawie pliku test.ser.
FileInputStream fIn = new FileInputStream("test.ser");
ObjectInputStream in = new ObjectInputStream(fIn);
TreeNode n = ( TreeNode)in.readObject();
W metodzie readObject() następuje alokacja pamięci dla obiektu ziarna i wywoływany jest bezargumentowy konstruktor jego klasy.
2) Interfejs Externalizable
Jeśli plik z serializowanym obiektem ma posiadać specyficzny format, to
programista powinien skorzystać z interfejsu Externalizable i odpowiednio
zdefiniować jego 2 metody: readExternal i writeExternal. Klasa serializowanego obiektu musi posiadać bezargumentowy konstruktor.
W. Kasprzak: Programowanie zdarzeniowe.
11- 17
11. JavaBeans
3) Trwałość długo-okresowa
Pojęcie to oznacza model trwałego pamiętania ziaren w formacie XML.
XMLEncoder, XMLDecoder
Klasa XMLEncoder przeznaczona jest to wypisywania serializowalnych
obiektów do plików tekstowych w formacie XML.
Przykład. Wypisanie ziarna i jego własności w postaci XML:
XMLEncoder encoder = new XMLEncoder(
new BufferedOutputStream(
new FileOutputStream( "Beanarchive.xml" ) ) );
encoder.writeObject( object );
encoder.close();
Klasa XMLDecoder przeznaczona jest do odtworzenia obiektu z
dokumentu XML, wcześniej utworzonego przez XMLEncoder.
W. Kasprzak: Programowanie zdarzeniowe.
11- 18
Przykład
XMLDecoder decoder = new XMLDecoder(
new BufferedInputStream(
new FileInputStream( "Beanarchive.xml" ) ) );
Object object = decoder.readObject();
decoder.close();
Znaczniki XML
Dla reprezentacji ziarna w języku XML używane są następujące znaczniki:
• W preambule XML podana jest wersja XML i rodzaj kodowania.
• Znacznik <java> zawiera wszystkie elementy obiektu ziarna.
• Znacznik <object> reprezentuje zbiór wywołań metod niezbędnych do
rekonstrukcji obiektu z jego serializowanej postaci :
•
<object class="javax.swing.JButton" method="new">
•
<string>Ok</string>
•
</object>
lub wyrażenia
<object class="javax.swing.JButton">
W. Kasprzak: Programowanie zdarzeniowe.
11- 19
11. JavaBeans
<void method="setText">
<string>Cancel</string>
</void>
</object>
• Znaczniki dla definiowania typów prostych:
o
<boolean>
o
<byte>
o
<char>
o
<short>
o
<int>
o
<long>
o
<float>
o
<double>
•
Np. <int>5555</int>
• Znacznik <class> dla reprezentacji instancji klasy Class:
•
<class>java.swing.JFrame</class>
• Znacznik <array> dla reprezentacji tablicy:
W. Kasprzak: Programowanie zdarzeniowe.
11- 20
•
<array class="java.lang.String" length="5">
•
</array>
Przykład archiwum w XML generowanego przez komponent SimpleBean:
<?xml version="1.0" encoding="UTF-8" ?>
<java>
<object class="javax.swing.JFrame">
<void method="add">
<object class="java.awt.BorderLayout" field="CENTER"/>
<object class="SimpleBean"/>
</void>
<void property="defaultCloseOperation">
<object class="javax.swing.WindowConstants" field="DISPOSE_ON_CLOSE"/>
</void>
<void method="pack"/>
<void property="visible">
<boolean>true</boolean>
</void>
</object>
</java>
W. Kasprzak: Programowanie zdarzeniowe.
11- 21
11. JavaBeans
4) Problem serializacji
Jak zapewnić serializowalność obiektu naszej klasy?
1. Czy nasza klasa implementuje interfejs Serializable (bezpośrednio lub pośrednio) lub Externalizable?
2. Czy wszystkie pola klasy są serializowalne?
Pola typów prostych (tzw. „dla wartości”) są zawsze serializowalne.
Pozostałe pola, będące typu klas, należy sprawdzać indywidualnie. Np.
klasa Image nie jest serializowalna. Należy wtedy zdecydować, czy
należy i jak zapamiętywać takie pola – przy odtwarzaniu wczytywać
dane z osobnego pliku, stawiać je na wartości domyślne zaznaczyć pola
jako transient, itd.
3. Upewnić się, czy chcemy zapamiętać wszystkie pola, które nie są
zaznaczone jako transient lub static.
4. Czy odpowiada nam domyślny mechanizm serializacji, czy też chcemy
zapamiętać obiekt w innej strukturze, np. dokonując jego kompresji lub
szyfrowania?
5. Jak zainicjalizować pola transient i static po deserializacji obiektu?
6. Czy proces deserializacji ma być uzupełniony o walidację?
W. Kasprzak: Programowanie zdarzeniowe.
11- 22
Walidacja deserializowanego obiektu zwykle polega na sprawdzeniu jego
poprawności - zgodności z zadanym modelem dla danego typu. W tym
celu należy utworzyć i zarejestrować metodę walidacji.
Przykład. Chcemy odtworzyć obiekt klasy Date, ale ma on odpowiadać
„ostatniej” dacie a nie “pierwszej dacie”.
W tym celu możemy wprowadzić pole charakteryzowane jako transient,
które jest zerowane podczas deserializacji.
public class TreeNode implements Serializable {
Vector children;
TreeNode parent;
String name;
transient Date date; // Pole przejściowe - nietrwałe
public TreeNode(String s) {
children = new Vector(5);
name = s;
initClass();
}
private void initClass () {
W. Kasprzak: Programowanie zdarzeniowe.
11- 23
11. JavaBeans
date = new Date(); // Tu nadajemy nową wartość polu przejściowemu
}
...
private void writeObject(ObjectOutputStream s)
throws IOException {
s.defaultWriteObject();
}
private void readObject(ObjectInputStream s)
throws ClassNotFoundException, IOException {
s.defaultReadObject();
initClass();
}
}
Uwaga: metody writeObject i readObject nadpisywane są parami – w przeciwnym razie nadpisanie metody writeObject nie byłoby tu potrzebne.
W. Kasprzak: Programowanie zdarzeniowe.
11- 24
5) Odtworzenie / utworzenie ziarna metodą - Beans.instantiate
Zamiast tworzenia instancji ziarna operatorem new można też skorzystać z
metody Beans.instantiate. Jest to typowy sposób odtworzenia obiektu
ziarna po uprzedniej serializacji. W ten sposób nie musimy znać nazw
wszystkich potencjalnych klas ziaren w momencie tworzenia kodu.
Np. utworzenie obiektu klasy TextField bez korzystania z new:
Component c = (Component)Beans.instantiate(null, "java.awt.TextField"); Jest to równoważne w działaniu z kodem:
Component c = new TextField();
Aby przekonać się o różnicy utwórzmy klasę pochodną MyTextField:
public class MyTextField extends java.awt.TextField {
}
Następnie w poniższym programie dokonamy serializacji obiektu klasy
MyTextField:
import java.awt.*;
import java.io.*;
W. Kasprzak: Programowanie zdarzeniowe.
11- 25
11. JavaBeans
public class TfSaver {
public static void main(String args[]) {
MyTextField mtf = new MyTextField();
// Ustaw własności:
Font ft = new Font("Serif", Font.ITALIC, 36);
mtf.setFont(ft);
mtf.setText("Hello World");
// Serializuj:
try {
FileOutputStream f = new FileOutputStream("MyTextField.ser");
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject(mtf);
s.flush();
} catch (Exception e) {
System.out.println(e);
}
System.exit(0);
}
}
W. Kasprzak: Programowanie zdarzeniowe.
11- 26
11. JavaBeans
Aplikacja korzystająca z instancji MyTextField będzie miała już ustawione
własności font i text (na czcionkę 36-pt, italic, Serif i napis "Hello World").
W celu odtworzenia zastosujemy wywołanie Beans.instantiate o postaci:
Component c = (Component)Beans.instantiate(null, "MyTextField");
Dzięki metodzie Beans.instantiate mamy większą elastyczność tworzenia
instancji: jeśli istnieje plik o nazwie NazwaKlasy.ser (w tym przypadku MyTextField.ser) to nastąpi deserializacja obiektu na podstawie danych z
tego pliku; w przeciwnym razie obiekt ziarna zostanie zainicjalizowany
bezargumentowym konstruktorem w tej klasie.
Gdy istnieje MyTextField.ser:
Gdy nie ma pliku MyTextField.ser:
W. Kasprzak: Programowanie zdarzeniowe.
11- 27
11. JavaBeans
6) Zarządzanie wersjami klas ziaren
Ewentualne zmiany, dokonywane w definicji klasy, mogą dotyczyć
struktury danych lub metod.
Jeśli zmieniamy strukturę trwałych danych klasy ziarna to obiekty
wcześniejszych wersji tej klasy nie mogą być deserializowane.
Nawet dodanie metody do klasy ziarna czyni obiekt wcześniejszej wersji
niemożliwy do deserializacji – próba deserializacji zakończy się wyjątkiem
java.io.InvalidClassException.
Jednak dodanie lub usunięcie metody nie ma wpływu na informację o
stanie obiektu (metody wyrażają zachowanie obiektu a to nie jest
serializowane) i z tego punktu widzenia obiekty dwóch wersji klasy,
różniące się jedynie metodami, powinny być zgodne w procesie
deserializacji.
Aby zapewnić możliwość odtworzenia stanu obiektu “poprzedniej” wersji
klasy w obiekcie “nowej” wersji tej klasy należy dodać pole statyczne
zawierające wartość “Stream Unique Identifier (SUID)”:
Np.
W. Kasprzak: Programowanie zdarzeniowe.
11- 28
11. JavaBeans
private static final long serialVersionUID = -2966288784432217853L;
Wartość pola jest regenerowana podczas deserializacji i jest
porównywana z aktualną definicją klasy.
Wartości SUID są generowane przez program serialver z argumentem,
będącym nazwą klasy, w wyniku algorytmu z funkcją mieszającą („hash”).
Program serialver może być tez wywołany jawnie z opcją –show i wtedy pojawi się graficzny interfejs, który pozwoli programiście przejąć wartość
SUID, wygenerowaną dla zadanej klasy i umieścić ją w kodzie programu.
W. Kasprzak: Programowanie zdarzeniowe.
11- 29
11. JavaBeans
11.5 Introspekcja i refleksja
1) Mechanizm refleksji
Należy on do podstawowych elementów języka – jego API stanowi
pakiet java.lang.reflection. Umożliwia on m.in.:
• Określenie klasy obiektu.
Np.
TextField t = new TextField();
Class c = t.getClass(); // Klasa na podstawie obiektu
Class s = c.getSuperclass(); // Klasa bazowa
Class c = java.awt.Button.class; // Jawnie znana klasa
Class c = Class.forName(strg); // Nazwa to strg
• Informowanie
o modyfikatorach klasy, polach, metodach,
konstruktorach i klasie bazowej.
• Informowanie o przynależności stałych i metod do interfejsu.
• Utworzenie obiektu klasy nieznanej podczas programowania.
Np.
W. Kasprzak: Programowanie zdarzeniowe.
11- 30
Class cl = Class.forName(className);
obiekt = cl.newInstance();
• Pobranie
i ustawienie wartości pola, nawet gdy podczas
programowania nie jest znana nazwa tego pola.
• Wywołanie metody, nawet gdy nie jest znana jej nazwa.
• Utworzenie i modyfikację tablicy o elementach nieznanego typu.
W. Kasprzak: Programowanie zdarzeniowe.
11- 31
11. JavaBeans
2) Interfejs BeanInfo
BeanInfo pozwala rozszerzyć mechanizm refleksji w odniesieniu do ziaren.
Dla pewnej klasy programisty Beanclass można stworzyć klasę
BeanclassBeanInfo i zaimplementować we własny sposób metody BeanInfo.
Istnieje klasa SimpleBeanInfo, z której może dziedziczyć nasza klasa
BeanclassBeanInfo i wtedy wystarczy nadpisać jedynie niektóre z metod BeanInfo.
Tryb pracy ziarna: “w czasie projektowania” i “w czasie wykonania”
Podczas projektowania klasa ziarna musi udostępniać narzędziu
projektowemu informacje pozwalające na edycję (i ustawianie) własności
oraz definicje rozszerzeń własnych programisty.
Musi też udostępniać metody i zdarzenia tak, aby program projektowy
“Builder” mógł wygenerować właściwy kod metod klasy ziarna.
Metoda Beans.isDesignTime pozwala sprawdzić tryb pracy ziarna.
W. Kasprzak: Programowanie zdarzeniowe.
11- 32
Interfejs
BeanInfo
dostarcza
w
fazie
projektowej
(narzędziom
programowania wizualnego) wybranych informacji (meta-danych) o klasie
ziarna Beanclass.
Jawna
specyfikacja
klasy
BeanclassBeanInfo
jest
nadpisaniem
standardowej introspekcji i blokuje mechanizm refleksji w fazie
projektowej.
Własna implementacja interfejsu BeanInfo umożliwia:
o Specyfikację własnej ikonki dla ziarna;
o Dostarczenie dodatkowego opisu ziarna;
o Dostarczenie opisów cech (klasy pochodne od FeatureDescriptor)
BeanDescriptor
EventSetDescriptor
PropertyDescriptor
MethodDescriptor
W. Kasprzak: Programowanie zdarzeniowe.
11- 33
11. JavaBeans
3) Introspekcja
Jest to proces pozyskiwania informacji o własnościach, metodach i
zdarzeniach danego ziarna. Do tego celu służy klasa Introspector.
Innym sposobem introspekcji jest skorzystanie z mechanizmu refleksji
(“Reflection”).
Klasa Introspector odwołuje się do właściwej klasy-implementacji BeanInfo dla klasy ziarna – dzięki metodzie getBeanInfo posiadającej parametr typu
Class:
TextField tf = new TextField ();
BeanInfo bi = Introspector.getBeanInfo (tf.getClass());
Jeśli nie zdefiniowano specyficznej klasy implementującej interfejs
BeanInfo dla klasy ziarna, to Introspector korzysta z mechanizmu refleksji w celu pobrania opisów zdarzeń, własności i metod klasy ziarna.
Opis zdarzeń
Metoda getEventSetDescriptors podaje wszystkie zdarzenia zgłaszane
przez
zadaną
klasę
ziarna.
Dla
każdej
pary
metod
W. Kasprzak: Programowanie zdarzeniowe.
11- 34
add/remove ListenerTypeListener (zwracających void) powstaje jeden opis zdarzenia:
EventSetDescriptor[] esd = bi.getEventSetDescriptors();
for (int i=0;i<esd.length;i++) System.out.print (esd[i].getName() + " "); System.out.println ();
Wynik wykonania powyższego kodu dla klasy TextField :
text mouse key component action focus mouseMotion
Opis własności
Metoda getPropertyDescriptors podaje wszystkie własności klasy ziarna.
Np.
public void set PropertyName( PropertyType value);
public PropertyType get PropertyName();
public boolean is PropertyName();
PropertyDescriptor[] pd = bi.getPropertyDescriptors();
for (int i=0;i<pd.length;i++) System.out.print (pd[i].getName() + " "); System.out.println ();
Wynik wykonania powyższego kodu dla klasy TextField :
selectionStart enabled text preferredSize
W. Kasprzak: Programowanie zdarzeniowe.
11- 35
11. JavaBeans
foreground visible background selectedText
echoCharacter font columns echoChar name
caretPosition selectionEnd minimumSize editable
Opis metod
Metoda getMethodDescriptors podaje wszystkie publiczne metody klasy
ziarna. Pozwala o wywołać te metody bez znajomości ich nazwy. Dla
każdej metody pobieramy typy jej parametrów dzięki metodzie
getParameterDescriptors.
MethodDescriptor[] md = bi.getMethodDescriptors();
for (int i=0;i<md.length;i++) System.out.print (md[i].getName() + " "); System.out.println ();
Wynik wykonania powyższego kodu dla klasy TextField to wydruk nazw
155 metod, w większości dziedziczonych z klasy Component. Lista metod
obejmuje również metody add/remove ListenerTypeListener.
W. Kasprzak: Programowanie zdarzeniowe.
11- 36
Specyficzna klasa implementująca BeanInfo
Definiując własną klasę implementującą BeanInfo możemy ograniczyć
elementy klasy ziarna udostępniane edytorom projektowym.
Dla klasy ziarna o przykładowej nazwie SizedTextField właściwa nazwa klasy implementującej BeanInfo wynosi SizedTextFieldBeanInfo.
Np. gdy chcemy w trybie introspekcji udostępnić jedynie jedną własność
tej klasy - length - to zdefiniujemy następującą klasę typu BeanInfo: import java.beans.*;
public class SizedTextFieldBeanInfo extends SimpleBeanInfo {
private final static Class beanClass = SizedTextField.class; public PropertyDescriptor[] getPropertyDescriptors() {
try {
PropertyDescriptor length = new PropertyDescriptor("length", beanClass);
PropertyDescriptor [] rv = {length};
return rv;
} catch (IntrospectionException e) {
throw new Error(e.toString());
W. Kasprzak: Programowanie zdarzeniowe.
11- 37
11. JavaBeans
}
}
}
Teraz zamiast 17 własności klasy TextField, jedyną wyświetlaną
własnością stworzonej klasy pochodnej będzie nowa własność: length.
Oczywiście w klasie SizedTextField należy jeszcze zdefiniować metody
set/getLength. Jednak wszystkie własności i metody tej klasy są nadal dostępne dla programu i mogą być wywoływane w wykonywanym
programie. Również mechanizm refleksji “widzi” wszystkie publiczne
metody.
Można przekazać do edytora projektu czytelniejszą nazwę klasy. Np. dla
klasy SizedTextField czytelniejsze jest oddzielenie słów składowych: public BeanDescriptor getBeanDescriptor() {
BeanDescriptor bd = new BeanDescriptor(beanClass);
bd.setDisplayName("Sized Text Field");
return bd;
}
W. Kasprzak: Programowanie zdarzeniowe.
11- 38
Można przekazać ikonkę dla klasy ziarna, przy pomocy następującej
metody dodanej do klasy implementującej BeanInfo:
public Image getIcon (int iconKind) {
if (iconKind == BeanInfo.ICON_COLOR_16x16) {
Image img = loadImage("sized.gif");
return img;
}
return null;
}
W. Kasprzak: Programowanie zdarzeniowe.
11- 39
11. JavaBeans
11.6 Przykład klasy ziarna
Klasy ziaren nie muszą reprezentować graficznych obiektów, chociaż takie
jest ich typowe zastosowanie – w tej technologii stworzono pakiety AWT i
Swing dla środowiska Java. Dobrą regułą dla stwierdzenia, czy warto
implementować klasę jako ziarno, jest pozytywna odpowiedź na pytanie,
czy warto stosować technikę „drag-and-drop” do pochwycenia obiektu tej
klasy z zestawu komponentów i przeniesienia go do przestrzeni roboczej
projektu, podczas tworzenia aplikacji.
Klasa Hire, zrealizowana w technologii ziarna, symuluje minimalny zestaw operacji dla zatrudnienia pracownika.
package hire;
import java.util.*;
public class HireEvent extends EventObject {
private long hireDate;
public HireEvent(Object source) {
super(source);
hireDate = System.currentTimeMillis();
W. Kasprzak: Programowanie zdarzeniowe.
11- 40
}
public long getHireDate() {
return hireDate;
}
}
Obserwatorzy
zainteresowani
procesem
zatrudnienia
muszą
zaimplementować odpowiedni interfejs i zarejestrować się w celu
uzyskania powiadomień o zajściu zdarzenia:
package hire;
import hire.*;
public interface HireListener extends java.util.EventListener {
public abstract void hired(HireEvent e);
}
W klasie Hire zarządzamy listą obserwatorów:
package hire;
...
public class Hire implements Serializable {
W. Kasprzak: Programowanie zdarzeniowe.
11- 41
11. JavaBeans
...
private Vector hireListeners = new Vector();
private PropertyChangeSupport changes =
new PropertyChangeSupport(this);
public Hire() {
}
public synchronized void addHireListener(HireListener l) {
hireListeners.addElement(l);
}
public synchronized void removeHireListener(HireListener l) {
hireListeners.removeElement(l);
}
Powiadomienie obserwatorów następuje dzięki metodzie notifyHired():
protected void notifyHired() {
Vector l;
HireEvent h = new HireEvent(this);
synchronized(this) {
l = (Vector)hireListeners.clone();
W. Kasprzak: Programowanie zdarzeniowe.
11- 42
}
for (int i = 0; i < l.size(); i++) {
HireListener hl = (HireListener)l.elementAt(i);
hl.hired(h);
}
}
Własność salary w klasie Hire jest związana:
public void addPropertyChangeListener( PropertyChangeListener p) {
changes.addPropertyChangeListener(p);
}
public void removePropertyChangeListener( PropertyChangeListener p) {
changes.removePropertyChangeListener(p);
}
public void setSalary(float salary) {
float old = this.salary;
this.salary = salary;
changes.firePropertyChange("salary", new Float(old), new Float(salary));
}
public float getSalary() {
W. Kasprzak: Programowanie zdarzeniowe.
11- 43
11. JavaBeans
return salary;
}
Z klasą ziarna związana jest klasa implementująca BeanInfo – jak dotąd udostępniająca (publikująca) jedynie własność salary:
package hire;
import java.beans.*;
public class HireBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
PropertyDescriptor pd1 = new PropertyDescriptor("salary", Hire.class);
pd1.setBound(true);
return new PropertyDescriptor[] {pd1};
} catch (Exception e) {
return null;
}
}
}
W. Kasprzak: Programowanie zdarzeniowe.
11- 44
11.7 BeanContext
Pakiet
java.beans.beancontext
zawiera
interfejsy
i
klasy
przeznaczone do grupowania ziaren w hierarchię i dodawanie ziaren
do kontenera:
• Hierarchia ziaren może stanowić abstrakcyjne środowisko
(kontekst) funkcjonowania poszczególnych ziaren JavaBeans
• Elementy maszyny wirtualnej Java wspomagają dynamiczne
dodawanie dowolnych usług do środowiska JavaBeans.
• JVM udostępnia ziarnom mechanizmy poszukiwania i
wywoływania usług w środowisku JavaBeans.
BeanContext - logiczne połączenie ziaren w postać “kontekstu”, z którym poszczególne ziarna mogą się kontaktować.
Dwa rodzaje kontekstu BeanContext:
- tylko zawieranie - interfejs BeanContext ;
- zawieranie i usługi - interfejs BeanContextServices.
W. Kasprzak: Programowanie zdarzeniowe.
11- 45
11. JavaBeans
Oba interfejsy posiadają standardowe implementacje:
BeanContext BeanContextSupport
BeanContextServices BeanContextServicesSupport
Interfejs BeanContextChild umożliwia zagnieżdżoym ziarnom
JavaBeans komunikację z obejmującym je kontekstem BeanContext.
W. Kasprzak: Programowanie zdarzeniowe.
11- 46