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)」に書いてあったりするといいんだけど,見あたらない感じ.無念だ.

CanCam 05 月号 エビちゃんベストセレクション 04

CanCam2005年05月号の蛯原友里ちゃん

CanCam から,お気に入りの蛯原友里ちゃんを紹介しようというこのコーナー.
今日もサマンサタバサデラックス + サマンサシルヴァ by サマンサティアラ (長っ) とのタイアップというかコラボ (?) 「エビちゃんがブランドの顔になりました!」から P124 の友里ちゃん.
このシリーズからの最後のセレクション.どれも素晴らしくカワイイ友里ちゃんで超満足♪
それにしても,このページに掲載されている友里ちゃん直筆デザイン画がスゴイです.さすがデザイン科卒...
そんなわけで (どんなわけで?),やっぱり CanCam 買うしか!!


P.S.
上に並べている小さな画像って 100 枚しか表示できないわけで,01 月号別冊付録からのひときわ美しい友里ちゃん 5 連発がもうすぐ消えちゃいます.しくしくしく...

トリプルバリア!!

アネッサの CM で最後に蛯原友里ちゃんが叫んでいるアレです.
そのセリフの着ゴエGET!! \(^o^)/
アネッサのサイトで「UV トライアスロン」を完走すると GET できます♪
http://www.shiseido.co.jp/anessa/

S2 の JTA について

S2 は Extension として JTA の実装を提供しています.
この JTA 実装では,トランザクションがコミットされる時だけでなく,ロールバックされる時も Synchronization#beforeCompletion() を呼び出し (コールバック) しています.が,ロールバック時には Synchronization#beforeCompletion() はコールバックされないのが正しいのではないかという気がします.


JTA の仕様でも CORBA OTS の仕様でもこの辺りが明記されていない気がするのですが,ほぼ同等と思われる EJB2.1 の「7.6 Stateful Session Bean State Diagram」ではトランザクションがコミットされる場合にのみ SessionSynchronization#beforeCompletion() が呼び出されることが明示されています.JDO の仕様も同様です.
他の JTA 実装としては,JOTM はロールバック時に Synchronization#beforeCompletion() を呼び出していません.コードは見てないけどぐぐったら JBoss でも同様っぽい感じ?
Spring FrameworkJTA の実装を提供しませんが,独自の PlatformTransactionManagerJTASynchronization によく似た TransactionSynchronization を定義しています.こいつの場合はロールバック時にも beforeCompletion() が呼び出されることが明示されています.かわりに (?) beforeCommit() というメソッドも定義されていて,こいつはコミットする場合にのみ呼び出されます.まぁ,あんまり参考にならない感じ.


解釈としては,トランザクションがコミットまたはロールバックされた後に呼び出される Synchronization#afterCompletion(int status) にはトランザクションのステータスが引数で渡されるのに対して,Synchronization#beforeCompletion() にはステータスが渡されません.つまり必要ないという事で,その理由はトランザクションがコミットされる場合にしか呼び出されないからステータスに応じた処理をする必要がないという事ではないかなぁと思います.


そんなわけで (どんなわけで?),
S2 の JTA 実装でもロールバック時には Synchronization#beforeCompletion を呼び出さない
というように変更したいと考えています.
Synchronization を使うことはそれほど多くないと考えていますが,もし Synchronization をお使いになっている方で,この変更が望ましくないという場合にはコメントください.
あるいは,JTA 仕様の解釈に関するご意見,WebSpheare や WebLogic 等における JTA 実装の挙動に関してご存じの方もコメント頂けると幸いです.
よろしくお願いします.


22:44 追記
Apache Geronimoロールバックの時にも Synchronization#beforeCompletion() を呼び出してるなぁ...


23:23 追記
Transaction#setRollbackOnly() を呼び出した後に Transaction#registerSynchronization(Synchronization) しようとすると RollbackException が飛んでくることになっています.つまり,ロールバックされるトランザクションSynchronization を登録してもしょうがないということで,これもロールバックされるトランザクションでは Synchronization#beforeCompletion() を呼び出すべきでないことを暗に示しているような気のせいが.


00:00 追記
CORBA OTS 1.4 (03-09-02) の「2.9 Synchronization Interface」に次の記述が.

If the transaction is instructed to roll back rather than be committed, the object will only be notified after rollback completes.

これで (だいたい) 安心.
少なくとも CORBA OTS (つまり JTS) では Synchronization#before_completion() が呼び出されるのはトランザクションがコミットされる場合だけで,ロールバックされる際には呼び出されないという事ですね.
そして JTAJTS の上位に構築できることを意図していて,JTASynchronizationJTSSynchronization とメソッドの命名規則に基づく違いがあるだけという事から,JTASynchronization#beforeCompletion()ロールバック時には呼び出されないのが妥当といえるのではないかと.


00:30 追記
Oracle9iAS はロールバック時にも Synchronization#beforeCompletion() を呼び出しているらしい.
しかし (9iAS にバンドルされている) TopLink はそれを想定していなくて例外が吹っ飛んだりしていたらしい.でもどうせロールバックするんだから例外が吹っ飛んでもどうという事はなかったと思われますが.
9iAS の 9.0.3.3 くらいまでの話.その後なんらかの対応がなされたらしいけれど,どういう対応 (9iAS が Synchronization#beforeCompletion() を呼び出さないようにしたのか TopLink で善処したのか) をしたのかは不明.