ネット上で、Java への dis として「なんで Java のプロパティアクセッサはわざわざ get/set で始まる名前のメソッドにしないといけないんだ!」というのをよく見かけます。ですが実は JavaBeans の仕様としてはいわゆる getter/setter でないメソッドもプロパティのアクセッサにすることができます。余りにも JavaBeans のことがボロクソに言われるのでかっとなって書きましたw
まずは以下の Java クラスを見てください。
/** * getter/setterを使わないJavaBeansの例. */ public class Test { private String わーい; public String すごーい() { return わーい; } public void たーのしー(String myProperty) { this.わーい = myProperty; } @Override public String toString() { return "Test{" + "わーい='" + わーい + '\'' + '}'; } }
いわゆる getter/setter がありませんね。このクラスを次のように Introspector
に掛けてプロパティを抽出し、実行してみます。
import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; /** * Beanの利用例. */ public class JavaBeansSampleApp { public static void main(String[] args) { try { Test test = new Test(); BeanInfo testBeanInfo = Introspector.getBeanInfo(test.getClass()); PropertyDescriptor[] propertyDescriptors = testBeanInfo.getPropertyDescriptors(); for (PropertyDescriptor pd : propertyDescriptors) { if ("わーい".equals(pd.getName())) { System.out.println("わーいプロパティのsetter: " + pd.getWriteMethod()); System.out.println("わーいプロパティのgetter: " + pd.getReadMethod()); pd.getWriteMethod().invoke(test, "なにこれー?"); } } System.out.println(test); } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) { e.printStackTrace(); } } }
結果は次のようになります。
わーいプロパティのsetter: public void aoetk.sample.Test.たーのしー(java.lang.String) わーいプロパティのgetter: public java.lang.String aoetk.sample.Test.すごーい() Test{わーい='なにこれー?'}
set で始まっていない たーのしー()
メソッドが setter として認識され、get で始まっていない すごーい()
メソッドが getter として認識されています。プロパティの writeMethod を実行するとちゃんと たーのしー()
メソッドが実行されて値が設定されています。
からくりについて解説しましょう。次のように Bean に対応した BeanInfo
実装クラスを用意し、Bean と同じパッケージに置きます。以下の例では BeanInfo
インターフェースのデフォルトメソッドを実装した SimpleBeanInfo
クラスを継承し、必要なメソッドだけ実装しています。
/** * {@link Test}のJavaBeans情報. */ public class TestBeanInfo extends SimpleBeanInfo { @Override public PropertyDescriptor[] getPropertyDescriptors() { try { return new PropertyDescriptor[]{ new PropertyDescriptor("わーい", Test.class, "すごーい", "たーのしー") }; } catch (IntrospectionException e) { return null; } } }
ここでは getPropertyDescriptors()
メソッドをオーバーライドし、 PropertyDescriptor
のインスタンスを返しています。もうお分かりですね。ここでこの JavaBean のプロパティの情報 (プロパティ名、アクセッサメソッド) を返しているわけです。
Introspector の API ドキュメント には次のような解説が記載されています。
Fooクラスについては、情報の問い合わせ時にnull以外の値を提供するFooBeanInfoクラスがあれば、明示的な情報を取得できます。まず、ターゲットのBeanクラスの完全指定されたパッケージ名に「BeanInfo」を付加して新規のクラス名とし、BeanInfoクラスを検索します。これに失敗した場合は、この完全指定されたパッケージ名の最後のクラス名にあたる部分を使って、BeanInfoパッケージ検索パスに指定されたパッケージごとに該当クラスを検索します。
(中略)
クラスの明示的なBeanInfoが見つからない場合は、低レベルのリフレクションを使ってクラスのメソッドを調べ、標準設計パターンを適用して、プロパティのアクセス用メソッド、イベント・ソース・メソッド、publicメソッドを識別します。
(以下略)
つまり、JavaBeans を作るとき、本来は対になる BeanInfo 実装クラスを用意し、Bean の情報を提供する必要があるのです。ですが、毎回それをやっては面倒なので、便宜的にあの命名規則が用意されているわけです。CoC の先駆けだったわけですね (ちなみに JavaBeans は 20 世紀に誕生した仕様です) 。
この BeanInfo ですが次のような情報を提供することができます。
- プロパティの情報
- イベントの情報
- メソッドの情報
- アイコン
アイコンとかイベントって何?と思いましたか。JavaBeans は本来可搬性のあるソフトウェアコンポーネント、特に GUI コンポーネントを開発するために作られた規格です。つまり、IDE の「ポトペタツール」に追加して使えるソフトウェア部品を作るためのものなのです。BeanInfo ではそのツールに対して提供する情報を定義します。
こうして考えると、単なる DTO として JavaBeans を使うのはオーバースペックであることも分かりますね。なぜこんなにあちこちで JavaBeans が使われるようになったのかは歴史的な経緯が色々あるのですが、まあそれはまたの機会で。
なお、このエントリでやったいたずらは業務のコードではやらないでくださいね。いたずらに混乱を招くだけです。酒の席でのネタにとどめてくださいw
全体のコードは gist にアップしてあります。
https://gist.github.com/aoetk/e5a09f67f2ebbc206d0770b21116b69e