java.lang.reflect
パッケージに AccessibleObject
というクラスがあります.こいつは同パッケージの Field
や Method
,Constructor
のスーパークラスです.
これらに対して setAccessible(true)
してあげると,private
なフィールドにアクセスしたり,private
なメソッドやコンストラクタを呼び出したりできます.final
なフィールドを更新したりもできるらしいけれどやったことはありません.
こいつらの使い方がちょっと悩ましいので書いてみるテスト.
まず,Method
なんかのインスタンスを取得するには Class
のインスタンスが必要です.あるクラスを表現する Class
のインスタンスは,クラスローダごとに一つだけです.
そして,あるクラスのあるメソッドを表現する Method
のインスタンスも (クラスローダごとに) 一つだけみたいです.同じメソッドを表す Method
を取得するために何回も getMethod()
しても同じインスタンスが返ってきますから.
<追記>Java5 以降では異なったインスタンスが返ってくるので以下は該当しません.</追記>
つ・ま・り
Field
や Method
のインスタンスは様々なところであるいはスレッドで共有されるオブジェクトということです.
で,setAccessible(boolean)
メソッドなんですが.
こいつの使い方ってこんな感じですよね.
Method method = clazz.getDeclaredMethod(...); method.setAccessible(true); method.invoke(...);
あるいはこんな書き方を見ることも.
Method method = clazz.getDeclaredMethod(...); try { method.setAccessible(true); method.invoke(...); } finally { method.setAccessible(false); }
どっちもイマイチっぽい.
最初の書き方の場合,Method
オブジェクトの状態を変更したままなので,別のコードで同じメソッドをリフレクションで呼び出そうとすると,setAccessible(true)
を呼び出していなくても private
なメソッドを呼び出すことができてしまいます.単に呼び出すメソッドを実行時に決めたいだけで,アクセス制御を無効にしたいわけではなかったとしても.
二番目の書き方の場合にしても,マルチスレッドで実行されるとやばそう.あるスレッドが setAccessible(true)
したところでコンテキストスイッチが起きて,別のスレッドで setAccessible(false)
されてしまったら,元のスレッドで invoke()
したところで IllegalAccessException
が吹っ飛んでくるかもしれません.無念だ.
そんなわけで (どんなわけで?),次のような事態も.
例えば CGLIB では ClassLoader#defineMethod()
という protected
なメソッドを呼び出すために static
イニシャライザで Method
のインスタンスを取得して setAccessible(true)
したものを static
フィールドに保持しています.クラスを生成する場合はこの static
フィールドに保持した Method
オブジェクトを invoke()
するわけですが,その時はいちいち setAccessible(true)
しません.
一方 Javassist では,クラスを生成する都度,ClassLoader#defineMethod()
の Method
を取ってきて setAccessible(true)
〜 invoke()
〜 setAccessible(false)
しています.
つ・ま・り
CGLIB と Javassist を一緒に使うと,CGLIB がちゃんと動かないなんてことも??
実際には,CGLIB と Javassist では引数の数が異なる ClassLoader#defineMethod()
を使っているため問題はないと思いますが,もし全く同じシグネチャのメソッドを使っていたらやばかったわけです.
結局,アクセス制御を無視してフィールドにアクセスしたりメソッドを呼び出すっていうことが共有されるオブジェクトの状態で表現されるのがイマイチなわけで,今のリフレクション API だったら Field
や Method
のインスタンスは Class
から取得するたびに新しいインスタンスが生成されないとおかしいんではないかと.
っていうより,アクセス制御を無視するかどうかは引数で指定するとかってほうがよかったんじゃないかという気のせいが.
でまぁ,とりあえずの結論としては,Method#invoke()
なんかを呼び出した後に setAccessible(false)
しない方がいいだろうってことですねぇ?
その方が他のコードに与える影響が少ないんじゃないかなぁってことで.
うーみゅ...
っていうことが「Java Reflection in Action (isbn:1932394184)」に書いてあったりするといいんだけど,見あたらない感じ.無念だ.