AccessibleObject#setAccessible(boolean)
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)」に書いてあったりするといいんだけど,見あたらない感じ.無念だ.
出演予定 TV 番組
情報なしですぅ...
CanCam 05 月号 エビちゃんベストセレクション 04
CanCam から,お気に入りの蛯原友里ちゃんを紹介しようというこのコーナー.
今日もサマンサタバサデラックス + サマンサシルヴァ by サマンサティアラ (長っ) とのタイアップというかコラボ (?) 「エビちゃんがブランドの顔になりました!」から P124 の友里ちゃん.
このシリーズからの最後のセレクション.どれも素晴らしくカワイイ友里ちゃんで超満足♪
それにしても,このページに掲載されている友里ちゃん直筆デザイン画がスゴイです.さすがデザイン科卒...
そんなわけで (どんなわけで?),やっぱり CanCam 買うしか!!
P.S.
上に並べている小さな画像って 100 枚しか表示できないわけで,01 月号別冊付録からのひときわ美しい友里ちゃん 5 連発がもうすぐ消えちゃいます.しくしくしく...
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 Framework は JTA の実装を提供しませんが,独自の PlatformTransactionManager
で JTA の Synchronization
によく似た 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()
が呼び出されるのはトランザクションがコミットされる場合だけで,ロールバックされる際には呼び出されないという事ですね.
そして JTA は JTS の上位に構築できることを意図していて,JTA の Synchronization
が JTS の Synchronization
とメソッドの命名規則に基づく違いがあるだけという事から,JTA の Synchronization#beforeCompletion()
もロールバック時には呼び出されないのが妥当といえるのではないかと.
00:30 追記
Oracle9iAS はロールバック時にも Synchronization#beforeCompletion()
を呼び出しているらしい.
しかし (9iAS にバンドルされている) TopLink はそれを想定していなくて例外が吹っ飛んだりしていたらしい.でもどうせロールバックするんだから例外が吹っ飛んでもどうという事はなかったと思われますが.
9iAS の 9.0.3.3 くらいまでの話.その後なんらかの対応がなされたらしいけれど,どういう対応 (9iAS が Synchronization#beforeCompletion()
を呼び出さないようにしたのか TopLink で善処したのか) をしたのかは不明.