AccessibleObject#setAccessible(boolean)

java.lang.reflect パッケージに AccessibleObject というクラスがあります.こいつは同パッケージの FieldMethodConstructorスーパークラスです.
これらに対して setAccessible(true) してあげると,private なフィールドにアクセスしたり,private なメソッドやコンストラクタを呼び出したりできます.final なフィールドを更新したりもできるらしいけれどやったことはありません.
こいつらの使い方がちょっと悩ましいので書いてみるテスト.


まず,Method なんかのインスタンスを取得するには Classインスタンスが必要です.あるクラスを表現する Classインスタンスは,クラスローダごとに一つだけです.
そして,あるクラスのあるメソッドを表現する Methodインスタンスも (クラスローダごとに) 一つだけみたいです.同じメソッドを表す Method を取得するために何回も getMethod() しても同じインスタンスが返ってきますから.
<追記>Java5 以降では異なったインスタンスが返ってくるので以下は該当しません.</追記>
つ・ま・り
FieldMethodインスタンスは様々なところであるいはスレッドで共有されるオブジェクトということです.


で,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 だったら FieldMethodインスタンスClass から取得するたびに新しいインスタンスが生成されないとおかしいんではないかと.
っていうより,アクセス制御を無視するかどうかは引数で指定するとかってほうがよかったんじゃないかという気のせいが.


でまぁ,とりあえずの結論としては,Method#invoke() なんかを呼び出した後に setAccessible(false) しない方がいいだろうってことですねぇ?
その方が他のコードに与える影響が少ないんじゃないかなぁってことで.
うーみゅ...


っていうことが「Java Reflection in Action (isbn:1932394184)」に書いてあったりするといいんだけど,見あたらない感じ.無念だ.