以下,実装を構成する主要な要素技術について述べる.
Javaを用いることによる主な利点を以下に示す.
以下,Java用語に基づき,変換の対象となるコードを「バイトコード」と 呼称する.
本システムにおける,実行時バイトコード変換部の基本的なフレームワークを提供する.
Javassistを用いることによる主な利点を以下に示す.
以下,各モジュールについて述べる.
Instrumentation Agentの振る舞いを説明するためには,Javaにおけるクラスロー ディングの規則を理解する必要がある.以下,JVM階層型クラスローダについて 述べる.
Javaでは,インスタンスを生成する前に,まずクラスオブジェクトをクラスローダ にロードする.クラスローダはJVM上に複数存在し,ブートストラップローダを 起点とするツリー構造を成している. 図6.2にTomcatにおける例を示す[19].
クラスをロードする際,既に上位のクラスローダにロード済みであれば,それが 使用される.そうでなければ,アプリケーションが使用しているクラスローダに ロードされる.
親子関係にあるクラスローダ間ではクラスローディングの重複は発生し ないが,兄弟関係のサブツリー間では,互いのツリーにぶら下がっているクラス ローダが見えないので,同一のクラスがそれぞれのサブツリーにおいてロードさ れる可能性もある.
つまり,クラスローダ間の関係は,クラスオブジェクトのスコープを意味する.
Instrumentation Agentは,ブートストラップクラスローダレベルで起動し,システムクラスローダに常 駐する.そして,その後ロードされるすべてのクラスを監視し,必要に応じて処 理に割り込む.
各クラスオブジェクトへの参照は,クラス名に基づいて保持している.しかし, 先に述べた同名異スコープのクラスオブジェクトが存在すると,名前だけでは区 別が付かなくなる.そこで,クラスオブジェクトが所属するクラスローダの情報 も必要になる.
また,クラスオブジェクトを変換した場合,変換後のクラスオブジェクトのスコー プは変換前と同一であることが望ましい.従って,変換後のクラス間の関係につ いても,ツリー構造のモデルで管理する必要がある.
以上の観点から,バイトコード変換用に別のクラスローダツリーを構築すること とした.図6.3にモデルを示す.
すなわち,元のクラスローダツリー(オリジナルツリー)の階層関係を完全に保ったまま,バイトコード変 換に適した形でツリーの写像(コピーツリー)を構築する.
バイトコード変換はコピーツリーのクラスオブジェクトに対して実行され,必要に応 じてオリジナルツリーのクラスオブジェクトと交換される.このとき,後で元に戻せるよ うに,コピーツリー側でオリジナルコードを保持する.
コピーツリーの実装は,JavassistのオブジェクトモデルおよびAPIを用いて実装 されている.バイトコード変換を行う際は,Instrumentaion Agentが提供するコ ピーツリーへのインタフェースを使用してクラスオブジェクトを取得し, Javassistを用いて変換するという手順を踏む.
一般に,設定ファイルやクラスファイルなどのコンテナによって参照されるリソー スの位置およびフォーマットは,アプリケーションが配備される環境,コンテナ の種類,および実行コンテキストによって異なる. Containerモジュールは,このような不定な位置におかれたリソースをアプリケー ションから参照するためのインタフェースを提供する.
本システムではポートタイプを特定するための識別情報の一部として,Webアプ リケーション名とWebアプリケーションが配備されているファイルシステム上の 位置を使用する.これは,同一のバイトコードであっても,所属するコンテキス トが異なればクラスローダも異なり,すなわち別個のクラスオブジェクトと見な されるという,Javaのクラスローディングのルールに対応するためである.
Containerモジュールは,リクエスタから渡されたWebアプリケーション名を元に, Webアプリケーションが配備されているコンテキスト情報を探索する.以下, その手順について述べる.
まず,使用中のコンテナを特定する.コンテナの種類と配備されている位置は, Java仮想マシンのシステムプロパティやファイルシステム上の位置などの手がか りから総合的に自動判別する.次に,特定した情報からコンテナの設定リソース を探索し,内容を解析する.解析結果から得られる情報はコンテナの種類に依存 するが,少なくともコンテキスト情報のマッピングは特定できる.
コンテキスト情報は,Webアプリケーションごとに一つずつ存在し,主にWebアプ リケーションのベースディレクトリ(ドキュメントベース)を調べるために用いら れる.
Containerモジュールは抽象クラスとして実装されており,使用するコンテナに 対応したコンクリートクラスを用意しなければならない.コンクリートクラスで は,コンテナの仕様に依存するメソッドと自動判別ルーチンを実装する必要があ る.現状では,Tomcat 5.5系にのみ対応している.
ポートタイプとクラスオブジェクトのマッピング情報は,Webサービスフレー ムワークが管理している.しかし,一般にその設定リソースの位置やフォーマッ トは,Webサービスフレームワークの種類によって異なる. WSRuntimeモジュールは,このような不定な位置におかれたリソースをアプリケー ションから参照するためのインタフェースを提供する.
WSRuntimeモジュールは,リクエスタから渡されたWSDLのポートタイプ定義を元 に,マッピングされているクラスオブジェクトを探索する.以下,その手順 について述べる.
まず,ポートタイプの実装に用いられているWebサービスフレームワークを特定 する.Webサービスフレームワークの種類は,Webアプリケーション全体の設定リ ソースの内容などから総合的に自動判別する.次に,特定した情報からWebサー ビスフレームワークの設定リソースを探索し,内容を解析する.解析結果から得 られる最も重要な情報は,ポートタイプとクラスオブジェクトのマッピング 情報である.
マッピング情報は,主にクラスオブジェクトの完全修飾名 (Full Qualified Domain Name, FQDN) を調べるために用いられる.
WSRuntimeモジュールは抽象クラスとして実装されており,使用するWebサービス フレームワークに対応したコンクリートクラスを用意しなければならない.コン クリートクラスでは,Webサービスフレームワークの仕様に依存するメソッドと 自動判別ルーチンを実装する必要がある.現状では,Axis 1系にのみ対応している.
ContainerモジュールとWSRuntimeモジュールは,それぞれが抽象化した対象のリ ソースへのアクセス手段を提供する.これらを用いて,実際にポートタイプとク ラスオブジェクトのバインディング管理を行うのがProviderモジュールである. 以下,その手順について述べる.
まず,リクエスタから渡されたWSDLのURLを元に,Containerモジュールと WSRuntimeモジュールを取得する.そして,あるポートタイプに対するバインド 要求があると,ContainerモジュールとWSRuntimeモジュールによる解析結果に基 づき,対応するクラスオブジェクトを探索してバインドする.このとき成立した バインディング情報は,Providerモジュール内で記憶される.これは,バインディ ングの重複を防ぐために用いられる.バインディングが成立すると,ポートタイ プをキーとするクラスオブジェクトのルックアップが可能になる.テスト終了時 など,バインディングを解除したい場合は,アンバインド操作を適切に行ってバ インド情報を削除する.
ルックアップによって得られるクラスオブジェクトは,主にInstrumentation Agentモジュールのコピーツリーから対応するクラスオブジェクトを取得するた めのキーとして用いられる.
例えば,標準出力を用いたデバッグプリントはプロバイダが稼働するホスト のTTYに出力されてしまうので,リクエスタからメッセージを取得できない. また,テスト対象のメソッドが例外をスローした場合も,例外が伝搬する過程の どこかで意図しないキャッチが行われるか,またはプロバイダが稼働するホスト のTTYにスタックトレースが出力されるかのいずれかとなり,リクエスタにエラー の発生を通知できない.
そこで,テスト用コードによるメッセージをバッファリングし, リクエスタに転送するための機構が必要となる.これが,Message Dispatcherモ ジュールである.
Message Dispatcherモジュールは,以下の機能を提供する.
以下,Message Dispatcherモジュールを用いたテスト用コードの書き方について 概要を述べる.
テスト用コードでメッセージを出力したい場合は,所属するプロバイダの Message Dispatcherモジュールを取得して,メッセージバッファへの書き込みメ ソッドを呼び出す.
例外をディスパッチしたい場合は,catch
した例外をMessage Dispatcher
モジュールの例外ディスパッチメソッドに渡す.
また,例外ディスパッチ時に,ユーザ定義ハンドラを作成して特定の処理をディ
スパッチャに実行させることも可能である.以下に例を示す.なお,$e
はスローされた例外を表すJavassist構文である.
ExceptionHandler eh = new ExceptionHandler(); MessageDispatcher md = Controller.getMessageDispatcher("WebappName"); md.dispatch($e, eh);
このとき,ExceptionHandler
オブジェクトはMessage Dispatcherモジュー
ルによって呼び出し可能なメソッドを実装していなければな
らない.
本システムは,Message Dispatcherモジュールによって処理可能な例外クラスを提 供している.テスト用コードが任意の例外処理を含む場合は,Message Dispatcherモジュール対応の例外オブジェクトでラッピングすればよい.
以下,Controllerモジュールが提供する機能と,その内部的な振る舞いについて 述べる.
WSDLのURLは,ControllerモジュールおよびWebサービスフレームワークに関係す るモジュール (WSRuntime,Providerなど)の初期化に用いられる.
また,初期化時にMessage Dispatcherモジュールを生成する.Message Dispatcherモジュールは一つのプロバイダに対して一つだけ生成され,静的オブ ジェクトとして参照される.
同時に,ポートタイプをキー,ポートタイプを構成するメンバの集合(コンスト ラクタ,メソッド)を値とするマップを生成する.以後,コンストラクタ とメソッドを総称して「ビヘイビア」,前述のマップを「ビヘイビアマップ」と 呼称する.ビヘイビアマップは,次に述べるコード挿入操作によって変更された ビヘイビアを記憶し,変更の重複を防止するために用いられる.
まず,ポートタイプ名をキーに,WSRuntimeモジュールからクラスオブジェク トのFQDNを取得する.それをキーに,Instrumentation Agentモジュールのコピー ツリーから対応するコピーオブジェクトを取得し,Javassistを用いてクラス構 造を解析する.解析結果として,クラスオブジェクトのメタデータ集合 (ビヘイ ビアのシグニチャなど) が得られる.
メタデータ集合は,ClassInfoオブジェクトとしてリクエスタに返信される. ClassInfoオブジェクトは,メタデータ集合を本システムとリクエスタで扱いや すい形式に変換し,適切なアクセサを付加したものである.
変更されたビヘイビアはビヘイビアマップに登録され,除去操作が行われるまで 他の挿入操作を受け付けなくなる.未挿入のコードポイントへの挿入および例外 ディスパッチャの有効化は可能である.
catch
ブロック内で対処が完結する例外は対象に含
まれない.
主な用途は,Message Dispatcherモジュールの例外ディスパッチへ例外処理を委 譲することである.詳細はMessage Dispatcherモジュールの説明を参照のこと.
変更されたビヘイビアはビヘイビアマップに登録され,除去操作が行われるまで 他の挿入操作を受け付けなくなる.未挿入のコードポイントへの挿入は可能である.
オリジナルツリーのコードからクラス全体を再生成するため,リクエスタによる すべての改変が無効となる.
除去操作後,当該ビヘイビアはビヘイビアマップからも除去される.
挿入操作を行った段階では,コピーツリーのクラスオブジェクトが変更されただ けで,まだテスト用コードは有効でない.本操作を行うことによって,実際にコ ピーツリーのクラスオブジェクトとオリジナルツリーのそれが入れ換わり,テス ト用コードが有効になる.すなわち,この時点で実行時バイトコード変換が成立 する.
クラスオブジェクトの入れ換えは,Java仮想マシンのHotswap機能によって実現 している.
有効化操作の逆を行い,オリジナルコードに基づいてクラスオブジェクトを復元 する.ロード済みのテスト用コード付きクラスオブジェクトは破棄されるが,コ ピーツリーには残っている.
テスト用コードの実行にあたって外部ライブラリの追加を要する場合は,テスト 用コードを挿入する前に,本機能を使用してあらかじめライブラリをセットして おかなればならない.もしライブラリが不足していた場合,実行時例外が発生す る.
Controllerモジュール自身はメッセージバッファの管理に関与しないので,メッ セージバッファが更新されたことをメソッド利用者に通知できない. したがって,リアルタイム出力を実現するためには,後述するMessengerモジュー ルか,またはリクエスタにおいて,ポーリングしながらメッセージバッファを読み 出すなどの工夫が必要となる.
Controllerモジュールは本システムにおける高レイヤのインタフェースを提供す るが,それらは特定のクライアントに依存しないように設計された汎用的な仕様 となっている.したがって実際に利用する際は,クライアントの差異を吸収する フロントエンドが必要となる.
Messengerモジュールは,Controllerモジュールのフロントエンドとして機能し, 以下の処理を行う.
本システムにおける外部要求は,基本的にSOAPメッセージとして処理される. したがって,SOAPメッセージングに対応したMessengerモジュールをフロントエ ンドとし,Webサービスゲートウェイとして動作させることになる. Messengerモジュールは,WSDLを公開しWebサービスとしてテスト要求を待 ち受ける.WSDLの内容はMessengerモジュールのpublicメソッド集合であり, Controllerモジュールのインタフェースと似ている.
特に,本システムではWebサービスフレームワークとしてAxisを利用しているので, WSDLの生成やSOAPメッセージの処理はAxisに代替させることができる. 実際,Messengerモジュールの内容は,Controllerモジュールを生成して処理を委譲してい るのみである (ライブラリのデシリアライズやメッセージストリームのポーリン グを除く).
もしWebサービス以外のプロトコルで本システムを利用したい場合も,そのプロ トコルに対応したMessengerモジュールを作成し,Controllerモジュールに処理を委譲すれ ばよい.その際,複雑なデコードやプロトコル変換などの処理は,Messengerモ ジュールに隠蔽すべきである.
プロバイダ管理者が本システムを配備するために要するコストは,起動前の設定 と起動パラメタの変更のみである.
Javaエージェントはpremain
というmain
の前に実行され
る特殊なメソッドを持ち,クラスローディングに独自の処理を付加する
ことができる.
JavaエージェントとしてInstrumentation Agentモジュールを有効にする ためには,本システム一式を含めたJARアーカイブ (ex. wsinstrumentation.jar) を作成し,コンテナの起動コマンドに以下の ようなオプションを付加する.
-javaagent"C:\libdir\wsinstrumentation.jar" \ -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9999
-javaagent
オプションにはInstrumentaion Agentモジュールを含
むJARアーカイブを指定する.なお,具体的なJavaエージェントのクラス
を指定するマニフェストファイルを,JARアーカイブに含める必要がある.
-agentlib
はHotswapに必要な設定で,Javassistが使用する.