作成 2002/1/12
なんとなくリフレクションについて簡単にまとめてみようと思います。リフレクション(Reflection)は、 Javaクラスからフィールドやメソッドなどの情報を取得する APIです。 取得したメソッドを実行することもできます。
APIはjava.lang.reflectパッケージにあり、 クラスも少なく、ちょっと使う分には、それほど難しくありません。
使われる場面は、主にフレームワーク、開発ツール だと思われますが、ちょっとしたユーティリティークラスでも 使うと便利な場合もあります。
使うと便利なんですが、コードが複雑になる (しかもコンパイラやリファクタリングツールの チェック機能外となる) のと、パフォーマンスはイマイチなので、 必要な部分のみ使うのがよいと思います。
簡単なリフレクションの例を見てみましょう。
例えばintを返すplayというメソッドをもったGalというクラスがあります。
public class Gal { public int play(){ //... } //... }
このクラスのplayメソッドの実行は普通にやるとこうでしょう。
Gal gal = new Gal(); int count = gal.play();
これと同じことをリフレクションを使ってやるとこうなります。
//インスタンスを作成します Object instance = Gal.class.newInstance(); //メソッドを取得します Method method = Gal.class.getMethod("play", new Class[0]); //メソッドを実行します Integer count = (Integer)method.invoke(instance, new Object[0]);
"play"といういう、文字列でメソッドを取得して、 実行できてしまうんです。面白いでしょ?
ちなみにGal.classというのはjava.lang.Class型のオブジェクトを 表す書き方です。
上記のソースはコレです。
次は、いくつかリフレクションを使った例を見ていきましょう。
よくオブジェクトの状態を出力する toString()メソッドを書きますよね。 上のGalクラスで書くと、こんな感じになるでしょう。
public class Gal { private String favorite; private int count; public String toString(){ return super.toString() + ",favorite=" + favorite + ",count=" + count ; } //... }このtoString()を毎度、実装するというのは、 デバッグ時にとても強力な方法なんですが、 フィールドがたくさんあると、 大変な上に単調な作業ですよね。 しかも、フィールド名が変更になると、 ついこの部分の変更を忘れがちです。
ここでリフレクションを使うとこんな感じで書けます。
スーパークラスのフィールド、配列フィールドは このままじゃうまく文字列にできないけど、 こんなのもいいでしょ。 toStringの中でtoString(this)しても可。
setAccessible()は、 Java2セキュリティーのパーミッションが厳しく設定されてる場面では 許可されないのでご注意を。
このように、クラスのメタデータ的なものを取得するときに リフレクションは重宝します。
クラスのフィールド、メソッド一覧を表示するツリー形式のクラスビューア
とか、比較的簡単に作れそうでしょ?
例2(FormatUtil)
次の例は、日付や10進数を指定したパターンの文字列に変換するユーティリティーメソッドです。
public static String format(Object data, String pattern)
java.text.Formatを利用するんですが、 それが、SimpleDateFormatなのか、DecimalFormatなのかは、 dataの型から判断されます。 ソースはコレです。
どんな場合に使うのかと言えば、 以下のようなJSPカスタムタグから使います。
<hoge:write name="order" format="yyyy/M/d"/>
このように、あるインターフェイスに対して、 動的にインスタンス、実行メソッドを決定したい場合に リフレクションは重宝します。
デザインパターンで言うと(特に)Factory Method、Commandパターン向きの機能かなぁ
と思われます。
DynamicProxy
今更ですが。。。
DynamicProxy(ダイナミックプロキシ)は、名前のとおり、プロキシを動的に作る仕組みです。java.lang.reflect.Proxyクラスおよびjava.lang.reflect.InvocationHandlerを利用します。JDKのドキュメントに簡単な説明があります。
次のようなクラスとインターフェイスがある場合、
Foo.java
IFoo.java
doSomething()メソッドを実行するコードはこうなります。
IFoo foo = new Foo(); foo.doSomething("hello!");
で、このFooに何らかのプロキシを通したいとします。プロキシクラスは次のようになるでしょう。
実行するコードは次のようになるでしょう。
IFoo foo = new FooProxy(new Foo()); foo.doSomething("hello!");
プロキシとは、インターフェイスを通して、オブジェクトを透過的に扱うクラスのことを言います。クライアントから見て、下の2つを同じように扱えるわけです。
ダイナミックプロキシを使うと、このようなProxyクラスを動的に作成できます。次のクラスはダイナミッククラスの例です。
このDebugProxyを使うと、前のコードはこうなります。
IFoo foo = (IFoo)DebugProxy.newInstance(new Foo()); foo.doSomething("hello!");
ちなみに出力結果はこんな感じ。
before method hello! before method
で、だから何だという話ですが、いろいろと使いようがあります。プロキシを動的に作成できるという機能を利用して、EJBやRMIなどのスタブを動的に生成することが可能です(ただし、メソッドインターフェイスを工夫する必要はある)。また、共通の前後処理を行うプロキシクラスを作成することができます。例えば、DBトランザクションの開始と終了などを記述できます(ただし、Connectionオブジェクトを渡す方法を工夫する必要はある)。このあたりは、最近だとAspectJでやれば?ってな感じになるのかもしれませんが。
欠点としては、必ずインターフェイスが必要なこと(インターフェイスを作る手間)。また、1回目のプロキシ作成時はコンパイル同等の処理が入るので、若干の処理が必要なこと(その後の呼び出しはリフレクションコールの微小なパフォーマンスの差異くらい)。
ていうわけで、まあ、古くて新しいダイナミックプロキシです。便利なのにあんまりみんな使ってないような気もするなぁ。