Java Beans について

オブジェクト指向プログラミングでは、ビジュアル環境(IDE:Integrated Development Environmentsなど)で部品を配置する感覚でプログラミングが行えます(全てではないが...)。JavaBeansは、以下のようなプログラミングを実現できるような部品となる Javaオブジェクトクラスを指します。

Java Beansプログラミングというと、「beans を利用してプログラムを作成する」「beans 自身を作成する」の二通りに分類できます。beans を利用してプログラムを作成する場合、アプリケーションを作成する一般的なプログラミングです。しかし、 beans 自身を作成する場合、アプリケーションのプログラミングに必要な機能の実現だけでなく、再利用可能となるような設計、動作をテストする機能、セキュリティの問題を意識しなくてはなりません。さらに、beans はデバッグされているものとして利用され、利用する側ではデバッグを行わないため、高性能/高品質でなくてはなりません。

1. 概要

Beansは、比較的小さなコンポーネントを目指したもので、単純なプログラミングで基本動作でカバーできるものを想定しています。 Beansの APIは、各プラットフォームで何らかの形でサポートされていなければなりません。つまり、Beansの APIに従って Beanコンポーネントを開発すれば、いろいろなプラットフォームで何らかの形(表示方法とは違えど)で動作することを保障します。

全ての Beanの動作を決める親クラスを定義するのではなく、各コンポーネント自身が標準的な動作をサポートすることを推奨します。しかし、標準的な動作をしないからといってこれを排除するものではありません。

Beans を実現するための基本的なフレームワークは以下のようなものです。

  1. 引数のないコンストラクタを作成
  2. Serializableインターフェースを実装
  3. プロパティ値格納用のインスタンス変数を作成
  4. プロパティ値格納用以外のインスタンス変数に「transient」キーワードを付ける
  5. プロパティのアクセスメソッド(getXXX(),setXXX())を作成
  6. 必要なら、paintメソッドと getPreferredSizeメソッドを作成(GUI部品の場合)
  7. 適当なイベントオブジェクトクラスを作成
  8. 7)に対応したイベントリスナーインターフェースを作成
  9. (このBeanがイベントリスナーの場合は)イベントリスナーインターフェースを実装
  10. (このBeanがイベントソースの場合は)イベントリスナーの登録/削除メソッドを作成
  11. その他アクセス可能なpublicメソッドを作成
  12. クラスファイルがBeanであることを示すフラグを立ててJARファイル化する

Bean を作成するにあたって意識しなくてはならないこと

2. イベント

Beans は、あるコンポーネントが発生するイベントや、あるコンポーネントが受け取るイベントが何かを知っている必要があります。JDK では、動的にイベントの送受信が可能なように「イベントリスナー」オブジェクトというフレームワークが導入されています。これは、イベントを受信して処理するオブジェクトに対し、イベントオブジェクトを引数とするメソッド(イベントハンドラ)を用意してイベントソースオブジェクトからこのメソッドを呼び出してもらうことにより、イベントの交換をしようというものです。 イベントソースオブジェクトは、 イベントリスナーを登録するメソッド(add <イベント名> Listener()) と削除するメソッド(remove <イベント名> Listener())を用意し、 ある処理が実行されると適当なイベントオブジェクトを作成した上で登録されたイベントリスナーのメソッドを呼び出します。

《イベントリスナー》

《イベントアダプタ》

イベントのクラス名、リスナーのクラス名、および 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のみで処理すると、本当に値に反映したかどうかの確認ができません。

《メソッド》

4. イントロスペクション

メソッド名などからでは入手不可能な Bean のプロパティ等を調べるプロセスをイントロスペクション(Introspection) と呼びます。これは、あるBeansクラス(MyObjectとする)に対して、BeanInfoクラス(名前は「MyObjectBeanInfo」)を作成し、 そのBeanInfoクラスの中で不足している情報を提供するコードを記述するという方法です。

また、リフレクション(Reflection) と呼ばれる技術によって、任意のオブジェクトクラスが、どのような名前/引数/返り値のメソッドを持っているのかを リストアップすることがでます。 ビジュアル開発ツール(IDE) は、このリフレクションを使用してインストールされた各Beansクラスがどのようなメソッドを持つのかをチェックし、 イベントのメニューに表示したり、 プロパティシートにプロパティの名前を表示したりしています。

開発ツールは、イントロスペクションとリフレクションを利用することで、どのようなイベントを発生させたりどのようなイベントを受信したりするのか、 ターゲットのBeanクラスがどのようなプロパティを持っているのか等を読み取り、メニューへの表示やコードの自動生成などを行います。

5. カスタマイザ

6. Serialize

開発ツール上でプロパティ値を変更した Beanオブジェクトは、ファイルに保存することができます。 これはSerialization機能を利用して実現されています。 保存されているBeanオブジェクトを読み出して利用する際も、Serialization機能を利用するが、ObjectInputStreamを直接介して 保存されているBeanオブジェクトを読み出すのではなく、 次のようなメソッドを利用します。
MyObject obj = (MyObject)Beans.instantiate(null,"e;MyObject");
このメソッドを呼び出すと、 次のどちらかの方法でオブジェクトをインスタンス化します。
  1. MyObject.classファイルを読み込み、引数の無いコンストラクタを呼び出す。
  2. MyObject.serファイルを読み込み、ObjectInputStreamを通してDeserializeする。

このメソッドにより、インスタンス化しようとするオブジェクトが通常のオブジェクトクラスなのか、開発ツールによってプロパティ値をデザインされたオブジェクトクラスなのかをプログラム側で区別する必要がなくなります。 ※)このinstantiate()メソッドの仕様上、 すべてのBeanクラスは引数の無いコンストラクタを用意する必要があります。

7. JARファイルの作成

一つの Beanは、通常複数のクラスファイルから構成されています。 開発ツールでデザインする時と、 その結果出来上がったプログラムを実行する時で、 そのすべてのクラスファイルが必要となります。実行時に、それらのクラスファイルが JARファイル化されている必要はないが、 デザイン時にはクラスファイルは1個の JARファイルにまとめられている必要があります。

例えば、次のようなクラスファイルから構成されるBeanがあるとします。

開発ツールで、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クラスをパッケージ化することができます。