4連休
明日から4連休です.(^^;
16日(金)と19日(月),いずれも19時から予定があるからなのですが,そのために仕事を休むとはやる気満々というのかやる気null
というのか(苦笑).
- 16日(金) OO Enkai
明日の夜は,かつてニフティサーブのプログラマーズフォーラム(プロ)「OOについて議論する」会議室でいろいろと勉強させていただいた常連の方々と宴会です.それがOO Enkai(笑).当時はOO OFFと呼ばれていたものが,パワーアップして復活しました.どうパワーアップしたかは謎です.
ニフティのフォーラムや会議室が寂れてしまい,自分も休会して久しいこともあって,数年ぶりにお会いする人が何人も.とても楽しみです.
ちなみに,私がはてなをはじめたのは,その常連だったはぶさんやまこたんを見かけた影響です.いろいろ刺激をもらって感謝してます.
- 19日(月) 第11回日本XPユーザー会 Martin Fowler夫妻とGregor Hohpe氏を囲む会
なんでも,一緒に食事をしながらお話をするとか.しかし英語が... 無念だ.
ということで,会場で話し相手になってくれる人募集します.(^^;
募集というか,こちらが応募させていただきたいです.よろしくお願いします.>参加される方
Inter 0 - 1 Marseille
(;_;)
これで後は来シーズンのCL出場権を目指すだけになってしもうた.
昨シーズンと違って楽しみのないこと... 無念だ.
お仕事スタイル
- 黒のジップアップブルゾン(VIKTOR & ROLF Monsieur 03-04AW)
- 黒とグレイのボーダーニット(BURBERRY Prosum 03SS)
- 黒のベルベットパンツ(JIL SANDER 03-04AW)
- 黒のショートブーツ(JIL SANDER 03-04AW)
こう天気や気温が不安定だと何着ていいのか困っちゃいます.
JTA/JTS
現実逃避シリーズ第2弾(苦笑).
とめどもさんが日記でJTAとJTSについて混乱されているようなので,ちょっと書いてみるてすと(またしても弱気,ほとんどうろ覚えなので).
Java以前の分散トランザクション
正確には非Java環境での分散トランザクションですが,重要な仕様が二つあります.
この仕様は,80年代後半に作業が始まって,90年前後に最初の仕様がリリースされた(と記憶している)もので,簡単に言ってしまうとTUXEDO*2やEncina*3といったTPモニタのための仕様です(超乱暴).
初期のDTPのモデルはとても簡単(これが大事)で,主な構成要素は次の3つです.
-
- Application(AP)
- Transaction Manager(TM)
- Resource Manager(RM)
このうちTMがTPモニタで,RMがDBMSとかMQSeries*4のようなMOM(Messaging Oriented Middleware)です.このような構成により,複数のRMに対してTMが2フェーズ・コミット(2PC)を司ってくれることになっています.
そして,上記の要素間で使用する以下のAPIが定められています.
これらのAPIはいずれもC言語で規定されています.というか,XAに至っては関数ポインタの入った構造体なんていうしろものです.(^^;
DTPは後に拡張されて,cRM(Communication Resource Manager)という要素が追加され,(クライアント)APがcRMを通じて(サーバ)APを呼び出すためのAPI*5が定められたり,例えばTUXEDO上のAPからEncina上のAPを呼び出したい場合に,cRMを通じて複数のTMにまたがって2PCを行うためのXA+というAPI*6が定められたりしました.
さて,このDTPですが,TPモニタがそれほど広く普及したわけではないため,今となってはそれほど重要なものではないと個人的には思います.もっとも,XAだけは今も重要な存在です.
- OMG CORBA OTS(Object Transaction Service)
X/Open DTPはオブジェクト指向以前の分散トランザクション仕様です.それに対して,分散オブジェクト環境においても分散トランザクションが必要だということで,CORBAのCommon Object Serviceの一部として策定された仕様がOTSです.ということで,当然ながらCORBA上に構築された仕様です.
X/Open DTPはよく言えばシンプル,悪く言えば大雑把なモデルをベースにした仕様ですが,OTSはオブジェクト指向の専門家とトランザクションの専門化により練り上げられた,緻密なモデルをベースとしています.悪く言えば複雑ってことなんですが.
どれくらい複雑かを味わっていただくために(笑),その構成要素が持つべき主なinterface
を列挙すると...
-
TransactionFactory
Control
Terminator
Resource
SubtransactionAwareResource
Synchronization
Cordinator
RecoveryCodinator
このうち,始めの3つがTXに,その後の2つがXAに相当するって感じでしょうか.
トランザクションを開始するのと,それを終了(commit/rollback)するのに別々のinterface
が用意されていたりして,結構煩雑です.そのため,高水準のinterface
として,
-
Current
が用意されています.これはCORBAでいうところの擬似オブジェクト(Pseudo Object)で,CORBAで定められているもののCORBAオブジェクトではない(ObjectReferenceを持たない),手軽にサービスを使うためのオブジェクトです.
CORBA OTSは,X/Open DTPの経験をベースにしているだけに,よくできた仕様だと思います.しかし,前提となるCORBAが普及しなかったこともあり,メジャーな存在にはなれませんでした.無念だ.
特に,上記interface
中のResource
がXAに相当するものなのですが,これを実装しているDBMS等がほとんどありません.私が知る限りでは,O2というフランス製のODBMSくらいです.考えてみれば,OracleやMQなんかがCORBAをサポートして分散トランザクションに参加するというのも,ちょっとなんだかなーって思いますよね.おっと,Oracleは中にVisiBroker持っているんでしたっけ.油断大敵?
そのため,OTSを実装した製品を使う場合でも,たいていはResource
とXAのアダプタを使ってRDBMSなどを使用することになります.
Java環境での分散トランザクション
ここからようやくJavaについて.
Javaにおいても,分散トランザクションが必要ということで,その仕様としてJTSとJTAの2つが定められています.
自分の記憶によれば,最初に定められたのはJTSです.これは,CORBA OTSを単純にJavaにマッピングしたものです.CORBAというのはプログラミング言語独立の仕様であり,前述のinterface
はCORBA IDLで記述されています.乱暴に言うと,これをidl2javaしたものがJTSです.よって,膨大なJ2EE関連の仕様の中にありながら,JTS仕様書の本文はわずか10ページ程度しかありません.詳細はCORBA OTSを見ろってことなのです.
なお,とめどもさんが多少混乱されているようですが,JTSは実装ではなくて仕様です.CORBA IDLによるinterface
をidl2javaして作られるのは,Javaのinterface
です*7.CORBAでは実装を規定していませんから,それをJavaにマッピングしても実装は得られないのです.
そのJTSですが,当然ながらCORBA OTSと同じ問題を抱えています.それは,CORBAが前提だということです.いかにJ2SEの一部としてJava IDLというCORBA実装が提供されているとはいえ,CORBAが広く使われているわけではありませんし,そもそもJavaとして,CORBAに依存した分散トランザクションしか用意されていないのはいかにもまずいでしょう.
ということでJTAです.JTSがCORBA OTSのJavaマッピングなら,JTAはX/Open DTPのJavaによる焼き直しという感じで,そっくりのモデルとAPIを持っています.その主なinterface
.
UserTransaction
- TXに相当する
interface
. XAResource
- XAに相当する
interface
.
ちなみにXAResource
ですが,名前もそこで定義されているメソッドもそのままXAな感じなのですが,これはあくまでJavaのinterface
です.その実装がJNI
でXAを呼び出しても構いませんが,そうしなければならないわけではありません.Pure JavaなRMはXAをサポートせずにXAResource
を提供するでしょうし,Type4のJDBCドライバが提供するXAResource
にしてもXAを使ったりはしないでしょう.XAはCで定義されているため,対応するCランタイムライブラリのバージョンの組み合わせなどがシビアだったりする場合もあり,XAResource
ネイティブなRMが増えることに期待大です.
JTAは,CORBAのようなプラットフォームからは独立した仕様です.そのJTAを,JTS上の薄いラッパーとして実装することもできます.そのような実装の例として,SunのJ2EE Reference Implementationや,IBMのWebSphere Application Serverがあります.これは,たまたまそういう風にJTAを実装したということで,JTAをネイティブに実装しても何の問題もありません.S2のJTAはネイティブな実装ですね.
ということで,JavaにはJTAとJTSという二つの分散トランザクション仕様があるわけですが,その主要な違いはCORBAに依存するか,しないかです.まれに,JTSは低水準でJTAは高水準という説明を見かけますが(っていうかJTAの仕様書にそういう図があるし),JTSにもCurrent
というUserTransaction
と同等の高水準interface
があるので,そのような説明は妥当ではないと個人的には思います.現在の位置付けとしては,アプリケーションが使用するのはJTAで,JTSはその水面下で使われることを想定しているっていうことはよく分かるのですけど.
そういえば「Seasarのからさわぎ」で,とめどもさんでしたっけ? JTSを実装をしたい!という方がいらっしゃいましたが,CORBAを前提とすることがほとんど考えられない現状では,その必要性は限りなく0に近いと考えてよいと思います.もちろん,趣味でということなら何の問題もありません.ぜひ実装してください!(^^;
JTAを使おう
JTAを使うと分散トランザクションを実現できるわけですが,分散トランザクションを使わない場合でもJTAを使う価値はあると思います.たいていのJTA実装は,トランザクションに参加するXAResource
が一つだけなら2フェーズコミットを行いません.S2のJTAもそうなっていますね.また,JDBCのcommit()
/rollback()
を直接呼び出すようなJTA実装もありでしょう.
ですから,たとえDB1つしか使わないという場合でも,JTAを使ってもいいのです.重要なのは,トランザクションはリソース(JDBCとかJMSとかJCAとかEJBとか)とは独立したものだということです.アプリケーションでJDBCのcommit()
/rollback()
を直接呼び出すようなことは,もうやめましょう.S2のようなコンテナを使うと,何も気にしなくてもJTAを使うことができます.というかJTAを使っていると意識することすらありませんね.素晴らしい.
2PCは慎重に
逆に,JTAが使えるからといって2PCを気軽に使うのは控えたほうがいいでしょう.2PCを使うと微妙な問題につきまとわれます.
- パフォーマンス
2PCではコミットの際に,TMはそれぞれのRMと2回ずつやり取り(たいていは通信を伴う)を行います.アプリの処理に比べたら軽いものかもしれませんが,パフォーマンスの検証をしてから慎重に考えた方がいいでしょう.
- コミットの順序
たまに悩ませてくれるコミット順.例えばOracleとMQで分散トランザクションを行っている場合.両方に対するPrepareを終えると,TMは一つ目のRMをコミットします.それがMQの方だとして,メッセージのPUTがコミットされたとしましょう.その時点で,他のアプリケーションはMQからメッセージをGETできます.そこでメッセージ中の項目をキーにOracleにアクセスすると,悲しいかなOracleはまだ先の2PCのコミットが終わっていなくて,あるべきデータがない,なんていうタイミングが発生する場合があります.どっちを先にコミットすべきかはアプリ次第だったりするので,なかなか悩ましいですー.
- TMの障害
たいていの場合,DBMSなどのRMはトランザクションを一方的にロールバックできます.セッションが切れたとかタイムアウトしたとかで.2PCを使っている場合でも,Prepareの前までは同じです.しかし,RMがPrepareに対してコミットできるとTMに答えた場合,その後はTMからcommit/rollbackを指示されるまで,RMはそのトランザクションを自分の意志でどうこうすることはできなくなります.当然,そのトランザクションが持っているロックもかけっぱなしです.ところがその時,TMが吹っ飛んでいたりしたら? TMがトランザクションのログを残してくれていて,それにアクセスできる場合などはすみやかにリカバリできますが,TMのトランザクションログにアクセスできない障害の場合,トランザクションをコミットするかロールバックするかを誰かが判断して手作業で実施する必要があったりします.こういう事態に備えた運用をするのは結構たいへんじゃないでしょうか? いやその,2PC使っていてもそういう事態にちゃんと備えた運用手順を用意しているとは限らないのであうあう.心より恥じる.
あー,すっきりした.そろそろ現実に戻らねば... 無念だ.
Spring Framework 入門記 AOPその15 TargetSource
今回は「5.10. Using TargetSources」に進みます.なんか,いつのまにか5章もあと少しみたい? 確かに,Spring AOPの基本的なことは大体分かってきた感じがします.便利なautoproxyも使えるようになってきたし.よし,この調子でがんばるべし!
で,今回のTargetSource
ですが,なんかちょっと分かりにくい存在です.これは,Aspectを適用するターゲットオブジェクトをAOP Proxyに提供することを役割としています.DataSource
のSource
と同じ意味なんでしょうね.
AOP Proxyは,メソッドが呼び出されるたびに,Adviceをかましながら最後はターゲットのメソッドを呼び出すわけですが,その都度ターゲットをTargetSource
から取得するということを,実はこれまでも水面下で行っていたようです.ふー,Springって本当に複雑だなぁ.
TargetSource
が存在することにより,例えばJDBCのConnection
にAspectをWeavingしたい,なんて場合に重宝するのかもしれません.コネクションプールを使う場合,JDBCのConnection
にAspectを適用するのはなかなか難しいように感じます.普通に考えると,コンテナが扱うのはDataSource
であってConnection
ではありません.やるとしたら,Connection
のラッパー(Proxy)を作って,それにAspectをWeavingするとか.ラッパーはプロパティとしてDataSource
を持って,必要に応じて本物のConnection
を取得する,みたいな感じでしょうか.
それを一般化したものが,今回のTargetSource
なのかな? と解釈してみました.つまり,TargetSource
のメリットを平たく言うと,AspectのWeavingを本物のターゲットの取得前に行える! ということではないかと.たぶん.
さて,そのTargetSource
ですが,次のメソッドを持っています.
Object getTarget()
void releaseTarget(Object target)
Class getTargetClass()
boolean isStatic()
AOP Proxyは,getTarget()
でターゲットオブジェクトを取得してそのメソッドを呼び出し,その後releaseTarget(Object)
でターゲットオブジェクトをTargetSource
に戻します.
もし,TargetSource
が常に同じターゲットオブジェクトを返す場合は,isStatic()
でtrue
を返します.その場合,AOP Proxyは毎回getTarget()
を呼び出さないように最適化するかもしれません(Springが現時点で提供するAOP Proxyはやっていないようです).
そして,Springが標準で提供するTargetSource
の実装としては,次のものがあります.
SingletonTargetSource
- デフォルトで使用される
TargetSource
で,常に同じインスタンスを返します. HotSwappableTargetSource
- ターゲットを取り替えられる
TargetSource
です.ターゲットを取り替えるには,swap(Object)
メソッドを使用します. CommonsPoolTargetSource
- Jakarta CommonsのPoolを使用する
TargetSource
です. PrototypeTargetSource
- 毎回コンテナからBeanを取得して返す
TargetSource
です.Beanが(singletonではなく)prototypeである場合に使う価値がありますが,AOP Proxyのメソッド呼び出しのたびにターゲットのインスタンス生成 & Dependency Injection が行われるため,良い子は使わないほうがよさげです.
これまで使ってきたProxyFactoryBean
やAutoProxyCreator
を使った場合,デフォルトではSingletonTargetSource
が使われます.これを変更するには,ProxyFactoryBean#setTargetSource(TargetSource)
やXxxAutoProxyCreator#setCustomTargetSourceCreator(List)
で設定します.
それでは実験を始めましょう.
こういう場合は,すでにあるものを使うより,自分で作ったほうがいいに決まってます.いやその,決してCommons Poolを学習するのが面倒だからではありません. あうあう.心より恥じる.
ということで,独自のTargetSource
を作ります.こういう場合は乱数です.何をするか考えるのが面倒になったら乱数なんです.乱数を返すようなTargetSource
,その名もRandomTargetSource
を作ってみませう.
問題は,乱数をどのように扱うかです.CGLIBを使ったAOP Proxyではfinal
なクラスは扱えないし,java.lang.reflect.Proxy
を使ったAOP Proxyではinterface
が必要なので,final
でかつinterface
をimplements
していないInteger
とかは扱えません.Number
がinterface
なら... 無念だ.
しょうがないのでjava.util.Date
を使いましょう.
package study; import java.util.Date; import java.util.Random; import org.springframework.aop.TargetSource; public class RandomTargetSource implements TargetSource { private Random randomGenerator; public RandomTargetSource() { randomGenerator = new Random(System.currentTimeMillis()); } public Object getTarget() throws Exception { return new Date(randomGenerator.nextLong()); } public void releaseTarget(Object target) throws Exception { } public Class getTargetClass() { return Date.class; } public boolean isStatic() { return false; } }
ちょろいもんです.えっへん.
今回はターゲットがDate
なので,おなじみのFoo
は出番なしです.無念だ.
次に定義ファイル.<bean>
要素以下のみ.
<bean name="interceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="date" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetSource"> <bean class="study.RandomTargetSource"/> </property> <property name="interceptorNames"> <value>interceptor</value> </property> </bean>
今回はautoproxyではなく,ProxyFactoryBean
を使いました.そのtargetSource
プロパティにRandomTargetSource
を設定しています.Aspect(Interceptor)は,例によってDebugInterceptor
です.心より恥じる.
で,実行用のクラス.今回の中心部はこんな感じ.
Date date = (Date) context.getBean("date"); System.out.println(date.getTime()); System.out.println(date.getTime()); System.out.println(date.getTime());
このように,同じインスタンスのgetTime()
を呼び出してプリントしています.本来Date
は不変オブジェクトなのですが...
実行!!!
Debug interceptor: count=1 invocation=[Invocation: method=[public long java.util.Date.getTime()] args=[Ljava.lang.Object;@f593af] target is of class java.util.Date] Debug interceptor: next returned 8860736471533486537 Debug interceptor: count=2 invocation=[Invocation: method=[public long java.util.Date.getTime()] args=[Ljava.lang.Object;@f593af] target is of class java.util.Date] Debug interceptor: next returned 5782795183581819694 Debug interceptor: count=3 invocation=[Invocation: method=[public long java.util.Date.getTime()] args=[Ljava.lang.Object;@f593af] target is of class java.util.Date] Debug interceptor: next returned 9017716241053679149
今回はターゲットのメソッドの中でプリントしているわけではないので少々見にくいですが,getTime()
の値が毎回異なっていることが確認できます.
それにしても,いくらお遊びとはいえDate
の不変性をいぢるってのは... 心より恥じる.