11. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

}

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

11. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

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. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

•

<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

11. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

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

11. JavaBeans

}

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

11. JavaBeans

}

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. JavaBeans

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