Java Beans について
オブジェクト指向プログラミングでは、ビジュアル環境(IDE:Integrated Development Environmentsなど)で部品を配置する感覚でプログラミングが行えます(全てではないが...)。JavaBeansは、以下のようなプログラミングを実現できるような部品となる Javaオブジェクトクラスを指します。
デザインシート上に、スタンプ付きのお絵描きソフトの感覚でクラスを張り付けることができる。
クラスのプロパティ(数値、文字、サイズなど)を、個々のプロパティエディタ上で変更/保存ができる。
2つのクラス間で、特定のイベントの交換を指定することで連携させることができる。
デザインシートごとに保存して、実行可能なJavaプログラムを作成することができる。
Java Beansプログラミングというと、「beans を利用してプログラムを作成する」「beans 自身を作成する」の二通りに分類できます。beans を利用してプログラムを作成する場合、アプリケーションを作成する一般的なプログラミングです。しかし、 beans 自身を作成する場合、アプリケーションのプログラミングに必要な機能の実現だけでなく、再利用可能となるような設計、動作をテストする機能、セキュリティの問題を意識しなくてはなりません。さらに、beans はデバッグされているものとして利用され、利用する側ではデバッグを行わないため、高性能/高品質でなくてはなりません。
1. 概要
Beansは、比較的小さなコンポーネントを目指したもので、単純なプログラミングで基本動作でカバーできるものを想定しています。 Beansの APIは、各プラットフォームで何らかの形でサポートされていなければなりません。つまり、Beansの APIに従って Beanコンポーネントを開発すれば、いろいろなプラットフォームで何らかの形(表示方法とは違えど)で動作することを保障します。
全ての Beanの動作を決める親クラスを定義するのではなく、各コンポーネント自身が標準的な動作をサポートすることを推奨します。しかし、標準的な動作をしないからといってこれを排除するものではありません。
Beans を実現するための基本的なフレームワークは以下のようなものです。
引数のないコンストラクタを作成
Serializableインターフェースを実装
プロパティ値格納用のインスタンス変数を作成
プロパティ値格納用以外のインスタンス変数に「transient」キーワードを付ける
プロパティのアクセスメソッド(getXXX(),setXXX())を作成
必要なら、paintメソッドと getPreferredSizeメソッドを作成(GUI部品の場合)
適当なイベントオブジェクトクラスを作成
7)に対応したイベントリスナーインターフェースを作成
(このBeanがイベントリスナーの場合は)イベントリスナーインターフェースを実装
(このBeanがイベントソースの場合は)イベントリスナーの登録/削除メソッドを作成
その他アクセス可能なpublicメソッドを作成
クラスファイルがBeanであることを示すフラグを立ててJARファイル化する
Bean を作成するにあたって意識しなくてはならないこと
セキュリティ問題を意識する - publicメソッドのみしか許されない場合がある。自分自身が保存される Serialize先をコントロールできない。
動作環境に応じて見えたり(visible)見なかったりする Beanもありうる
マルチスレッドを意識する - メソッドの同時呼び出し/変数の同時更新/普通はsynchronizedで逃げる
各種表示文字列と短い説明については多国語化可能
キャストには注意する - ある Beanをいろんな物として見せるインターフェースがあるので
Beans.getInstanceOf()
を使って Beanオブジェクトを取り出す必要がある開発ツールに期待する機能 - プロパティの変更/Bean同士の接続+Hookupソースの生成
他の Beanやイベントリスナーの変数は transientにする
Bean同士の接続を管理するのはコンテナの仕事
ランタイムに直接 Beanのプロパティを変えるコードもありうるため、 デザイン時のみアクセス可能なプロパティかどうかを BeanInfo経由で伝える必要がある
2. イベント
Beans は、あるコンポーネントが発生するイベントや、あるコンポーネントが受け取るイベントが何かを知っている必要があります。JDK では、動的にイベントの送受信が可能なように「イベントリスナー」オブジェクトというフレームワークが導入されています。これは、イベントを受信して処理するオブジェクトに対し、イベントオブジェクトを引数とするメソッド(イベントハンドラ)を用意してイベントソースオブジェクトからこのメソッドを呼び出してもらうことにより、イベントの交換をしようというものです。 イベントソースオブジェクトは、 イベントリスナーを登録するメソッド(add <イベント名> Listener()) と削除するメソッド(remove <イベント名> Listener())を用意し、 ある処理が実行されると適当なイベントオブジェクトを作成した上で登録されたイベントリスナーのメソッドを呼び出します。
《イベントリスナー》
EventListenerインターフェースを実装することによって他のクラスと区別します。
イベントによる状態はすべて EventObject内に格納します。
リスナー登録メソッドを定義することによりイベントソースを区別します。
直接イベントの受け渡しをするのが適当でないときはアダプタを途中に挿入します。 同じメソッド呼び出しでも、イベントの内容が違うときに、ターゲットのメソッドの切り替えをアダプタで行います。
あるイベントによる Beanの状態の変更は1つのメソッドで閉じる。
イベントリスナーの登録順は実装まかせで、イベントのディスパッチ部分は
Vector.close()
などを使ってマルチスレッドを意識する必要があります。イベントリスナーの数の制限は TooManyListenersExceptionで行います。その他に IllegalArgumentExceptionやNullPointerException もあります。(Null リスナーは引数に渡してはいけない)
リスナー全てにイベントを投げるかどうかは実装次第です。リスナーが Exceptionを発生したときに処理を続けるかどうかも実装次第です。
checkedExceptionをリスナーは発生すべきではありません(発生する場合はバグとみなす)。ソースのほうでは、このExceptionを認識すべきです。Exceptionをどう扱うかは実装依存です。
デッドロックを防ぐために、リスナーの保持が synchronized でもイベントのディスパッチ(リスナーのメソッドの呼び出し)は unsynchronized で行うべきです。
《イベントアダプタ》
イベントキューの実現
フィルタの実現
イベントの多重化
接続マネージャの役割
イベントのクラス名、リスナーのクラス名、および add, removeメソッドの名前が対応していることに注意しなければなりません。 これは、Beans を読み込む開発ツールが、これらの名前を参照することでどのイベントソースと、どのイベントリスナーがイベント交換可能かどうかを判断するのに必要になります。
public class XXX extends EventObject; interface XXXListener extends EventListener { void xxxXxxxXxxx(XXX ev); void xxxXxxxXxxx(XXX ev); void xxxXxxxXxxx(XXX ev); } class Xxxx implements XXXListener; public void addXXXListener(XXXListener l); public void removeXXXListener(XXXListener l);
サンプルコード:
【イベントオブジェクト】 class MyEvent extends AWTEvent { Object source; public MyEvent(Object obj, int code) { super(obj, iCode); // 必要であればイベントソースオブジェクトを記憶しておく source = obj; } } 【リスナーのインターフェース】 public interface MyEventListener { public void handleMyEvent(MyEvent evt); } 【イベントソース】 class MySource { MyEventListener listener; public void addMyEventListener(MyEventListener lis) { listener = lis; } public void removeMyEventListener(MyEventListener lis) { if( listener == lis ) { listener = null; } } public static void main(String args[]) { . . // イベントオブジェクトを生成(または何らかの方法で)して // イベントリスナーのイベントハンドラを呼ぶ MyEvent event = new MyEvent(this, i); listener.handleMyEvent(event); . . } } 【イベントリスナー】 class MyListener implements MyEventListener { // MyEventListenerのインターフェースに従って handleMyEvent()メソッドを用意する public void handleMyEvent(MyEvent event) { // 送ったイベントのオブジェクトを得る: event.getSource() // イベントの ID を得る: event.getID() // などなど } }
3. プロパティ
プロパティの値は、常に専用のメソッド(アクセスメソッド)を通して Beansオブジェクトの利用者(開発ツールなど)からアクセスされます。
Indexed property
二種類のメソッドが用意されています。
プロパティ値の参照は、「getXXXX()
」という名前のメソッドを呼び出すことで行われ、 「XXXX」の部分がそのプロパティ名となるように記述します。
プロパティ値の変更は、「setXXXX()
」という名前のメソッドを呼び出すことで行われ、 「XXXX」の部分がそのプロパティ名となるように記述します。
getXXXX()
や setXXXX()
を用意したBeanを、デザインシートに張り付けると
「XXXX」という名前のプロパティがプロパティシートに表示されます。
void setXXXX(int index, XXXX val); XXXX getXXXX(int index); void setXXXX(XXXX val[]); // 配列のサイズを変更したい場合、これですべてセットし直す XXXX[] getXXXX();
Bound property(あるBeanのプロパティの更新を別のBeanに通知する機能)
public void addPropertyChangeListener(PropertyChangeEvent ev);
public void removePropertyChangeListener(PropertyChangeEvent
ev);
PropertyChangeEvent内には Localeに依存しないプロパティ名を指示し、自分の内部状態を更新してからこのイベントを投げる。
PropertyChangeSupportクラスを使うことで楽にこのイベントを処理でます。
Constrained property(あるBeanのプロパティの更新を別のBeanが拒否できる機能)
もし、このプロパティが変更されたら、Beanは VetoableChangeListener.veto
ableChnage()
メソッドを呼び出します。このとき引数にPropertyChangeEventを渡します。この結果、変更して欲しくないものの場合は
PropertyVetoExceptionが発生します。1つ以上のリスナーが拒否した場合は、元の値に戻し、かつ戻したことを知らせる PropertyChangeEventを発生させる必要があります。
vetoableChange()
メソッドの呼び出しは値の変更前に実行しなければなりません。VetoableChangeSupportクラスを使うと便利です。また、同時に
PropertyChangeイベントも発生させるべきです。あるプロパティの変更に対し、2回イベントを発生させる必要があります。VetoableChangeのみで処理すると、本当に値に反映したかどうかの確認ができません。
《メソッド》
もし、
setXXXX()
で渡された値が同じものなら何もしない(イベントを発生しない)のが良い。プロパティ名つきの
addXXXListener()
もありadd <propertyname> Listener()
やremove <propertyname> Listener()
というメソッドも OK
4. イントロスペクション
メソッド名などからでは入手不可能な Bean のプロパティ等を調べるプロセスをイントロスペクション(Introspection) と呼びます。これは、あるBeansクラス(MyObjectとする)に対して、BeanInfoクラス(名前は「MyObjectBeanInfo」)を作成し、 そのBeanInfoクラスの中で不足している情報を提供するコードを記述するという方法です。
また、リフレクション(Reflection) と呼ばれる技術によって、任意のオブジェクトクラスが、どのような名前/引数/返り値のメソッドを持っているのかを リストアップすることがでます。 ビジュアル開発ツール(IDE) は、このリフレクションを使用してインストールされた各Beansクラスがどのようなメソッドを持つのかをチェックし、 イベントのメニューに表示したり、 プロパティシートにプロパティの名前を表示したりしています。
開発ツールは、イントロスペクションとリフレクションを利用することで、どのようなイベントを発生させたりどのようなイベントを受信したりするのか、 ターゲットのBeanクラスがどのようなプロパティを持っているのか等を読み取り、メニューへの表示やコードの自動生成などを行います。
5. カスタマイザ
AWTで構成されたCustomizerクラスを作ることで、より複雑なカスタマイズが可能になります。
PropertyEditorManagerクラスでプロパティの型とエディタの対応を管理します。
明示的に登録されたエディタがあればそれを使用する。
型名(XXXX)に「Editor」を付けたクラスを探す。(XXXXEditor)
java.beans.editorsパッケージの中でXXXXEditorクラスを探す。
PropertyEditor自身もPropertyChangeイベントを投げて変更を通知します。
最初に渡されたプロパティ自身を変更すべきではありません。
自分で同じクラスのオブジェクトを作りそれを返すようにする。
PropertyEditor以外の入力で変更された値の更新表示は開発ツールの責任で行う。
変更されたプロパティをSerializeせずに、初期化するコードを生成するための呼び出し文字列を返す
getJavaInitializationString()
を実装しなければなりません。 このメソッドは現在のプロパティの値に合わせて、再初期化時のコードを返します。(setXXXX()
の引数部分のみ)
6. Serialize
開発ツール上でプロパティ値を変更した Beanオブジェクトは、ファイルに保存することができます。 これはSerialization機能を利用して実現されています。 保存されているBeanオブジェクトを読み出して利用する際も、Serialization機能を利用するが、ObjectInputStreamを直接介して 保存されているBeanオブジェクトを読み出すのではなく、 次のようなメソッドを利用します。MyObject obj = (MyObject)Beans.instantiate(null,"e;MyObject");このメソッドを呼び出すと、 次のどちらかの方法でオブジェクトをインスタンス化します。
MyObject.classファイルを読み込み、引数の無いコンストラクタを呼び出す。
MyObject.serファイルを読み込み、ObjectInputStreamを通してDeserializeする。
このメソッドにより、インスタンス化しようとするオブジェクトが通常のオブジェクトクラスなのか、開発ツールによってプロパティ値をデザインされたオブジェクトクラスなのかをプログラム側で区別する必要がなくなります。
※)このinstantiate()
メソッドの仕様上、 すべてのBeanクラスは引数の無いコンストラクタを用意する必要があります。
7. JARファイルの作成
一つの Beanは、通常複数のクラスファイルから構成されています。 開発ツールでデザインする時と、 その結果出来上がったプログラムを実行する時で、 そのすべてのクラスファイルが必要となります。実行時に、それらのクラスファイルが JARファイル化されている必要はないが、 デザイン時にはクラスファイルは1個の JARファイルにまとめられている必要があります。例えば、次のようなクラスファイルから構成されるBeanがあるとします。
MyObject.class (Beanそのもの)
MyEvent.class (イベントクラス)
MyEventListener.class (イベントリスナーのインターフェース)
開発ツールで、Beanを利用しようとする場合は、このクラスがまとめられた JARファイルを決められた手順に従ってインストールしなければなりません。
開発ツールは、インストールされている各JARファイルをスキャンして、 順番にBeanクラスを取り出します。このとき、1個のJARファイル中のどのクラスファイルが Beanクラスなのかを示すフラグを立てておく必要があります。そのフラグはManifestファイル(MANIFEST.MF) というテキストファイルに記述します。Manifestファイルは、JARファイルに含まれた全ファイルの情報(名前など)をテキストファイルとして格納したものです。
Manifestファイル(MANIFEST.MF)に追加する情報:
# cat MyBean.MF Manifest-Version: 1.0 Name: MyObject.class Java-Bean: True
JARファイルを作成:
# ls MyObject.class MyEvent.class MyEventListener.class MyBean.MF # jar cfm MyBean.jar MyBean.MF *.class
jarコマンドのオプションに "m" を追加し、Manifestファイル"MyBean.MF"を指定すると、 Beanとして JARファイルが作成されます。一つのJARファイル中の複数のクラスファイルに「Java-Bean: True」 のフラグを立てることで、複数のBeanクラスをパッケージ化することができます。