Subsections


6 実装

1 実装環境

5章の設計に基づき,本システムを実装した. 5.2節で述べたように,実行時コード変換が比較的実現容易で あることから,実装環境としてJavaプラットフォームを選択した.

以下,実装を構成する主要な要素技術について述べる.

1 Java SE

本システムの処理系として,Java SEを選択した. Javaは,言語仕様の特性と実用性の両面において,本システムの実現に最も適し ていると言える.

Javaを用いることによる主な利点を以下に示す.

  1. Webサービス開発に最も多く用いられており,同技術に関する実装も豊富 に存在する.
  2. リフレクションやバイトコードインストゥルメンテーション,バイトコー ドのHotswapなど,実行時コード変換に有用な機能を言語仕様および仮想 マシン仕様レベルでサポートしており,それらを利用した動的コード変 換ツールも豊富に存在する.
  3. 様々なレイヤにおけるテスティングフレームワークが豊富に存在する.

以下,Java用語に基づき,変換の対象となるコードを「バイトコード」と 呼称する.

2 Javassist

Javassist[16]はバイトコード変換器である.AOPツールやDIコンテ ナの実装にも利用されており,十分な実績を持つ.

本システムにおける,実行時バイトコード変換部の基本的なフレームワークを提供する.

Javassistを用いることによる主な利点を以下に示す.

  1. 挿入されるコードをJavaソースコードの断片として記述できる.
  2. バイトコードレベルの操作が可能である.
  3. Hotswapに対応しており,実行時にクラス単位でコードを置換できる.

3 Apache Axis

Apache Axis[17](以下,Axisと略称する)はJavaで構築された代表的なWebサービスフレームワークである. SOAPメッセージングやWSDLの生成など,基本的な機能を提供する. 他,ポートタイプとなるJavaクラスからWSDLの自動生成する機能や,WSDLから リクエスタのスタブを自動生成する機能なども持つ.

4 Apache Tomcat

Apache Tomcat[18](以下,Tomcatと略称する)は代表的なServletコンテナである.AxisはServletと して実装されており,実行の際はServletコンテナが必要となる.

2 実装

本システムは,複数のモジュールからなる.システムの構成を,図6.1に示す.

Figure 6.1: 実装の構成
\resizebox{15cm}{100mm}{\includegraphics{MySystemImpl.eps}}

以下,各モジュールについて述べる.

1 Instrumentation Agentモジュール

Instrumentation Agentは,仮想マシン上に存在するすべてのクラスオブジェク トを把握し,バイトコード変換の対象となるオブジェクトモデルを構築する.

Instrumentation Agentの振る舞いを説明するためには,Javaにおけるクラスロー ディングの規則を理解する必要がある.以下,JVM階層型クラスローダについて 述べる.

Javaでは,インスタンスを生成する前に,まずクラスオブジェクトをクラスローダ にロードする.クラスローダはJVM上に複数存在し,ブートストラップローダを 起点とするツリー構造を成している. 図6.2にTomcatにおける例を示す[19].

Figure 6.2: クラスローダ階層の例 (Tomcat)
\resizebox{15cm}{100mm}{\includegraphics{TomcatClassloader.eps}}

クラスをロードする際,既に上位のクラスローダにロード済みであれば,それが 使用される.そうでなければ,アプリケーションが使用しているクラスローダに ロードされる.

親子関係にあるクラスローダ間ではクラスローディングの重複は発生し ないが,兄弟関係のサブツリー間では,互いのツリーにぶら下がっているクラス ローダが見えないので,同一のクラスがそれぞれのサブツリーにおいてロードさ れる可能性もある.

つまり,クラスローダ間の関係は,クラスオブジェクトのスコープを意味する.

Instrumentation Agentは,ブートストラップクラスローダレベルで起動し,システムクラスローダに常 駐する.そして,その後ロードされるすべてのクラスを監視し,必要に応じて処 理に割り込む.

各クラスオブジェクトへの参照は,クラス名に基づいて保持している.しかし, 先に述べた同名異スコープのクラスオブジェクトが存在すると,名前だけでは区 別が付かなくなる.そこで,クラスオブジェクトが所属するクラスローダの情報 も必要になる.

また,クラスオブジェクトを変換した場合,変換後のクラスオブジェクトのスコー プは変換前と同一であることが望ましい.従って,変換後のクラス間の関係につ いても,ツリー構造のモデルで管理する必要がある.

以上の観点から,バイトコード変換用に別のクラスローダツリーを構築すること とした.図6.3にモデルを示す.

Figure 6.3: バイトコード変換用クラスローダツリー
\resizebox{15cm}{100mm}{\includegraphics{MyLoader.eps}}

すなわち,元のクラスローダツリー(オリジナルツリー)の階層関係を完全に保ったまま,バイトコード変 換に適した形でツリーの写像(コピーツリー)を構築する.

バイトコード変換はコピーツリーのクラスオブジェクトに対して実行され,必要に応 じてオリジナルツリーのクラスオブジェクトと交換される.このとき,後で元に戻せるよ うに,コピーツリー側でオリジナルコードを保持する.

コピーツリーの実装は,JavassistのオブジェクトモデルおよびAPIを用いて実装 されている.バイトコード変換を行う際は,Instrumentaion Agentが提供するコ ピーツリーへのインタフェースを使用してクラスオブジェクトを取得し, Javassistを用いて変換するという手順を踏む.

2 Containerモジュール

Containerは,コンテナを抽象化したモジュールである.コンテナとは,Webアプ リケーションフレームワークの実行環境である.本システムではTomcatが相当す る.

一般に,設定ファイルやクラスファイルなどのコンテナによって参照されるリソー スの位置およびフォーマットは,アプリケーションが配備される環境,コンテナ の種類,および実行コンテキストによって異なる. Containerモジュールは,このような不定な位置におかれたリソースをアプリケー ションから参照するためのインタフェースを提供する.

本システムではポートタイプを特定するための識別情報の一部として,Webアプ リケーション名とWebアプリケーションが配備されているファイルシステム上の 位置を使用する.これは,同一のバイトコードであっても,所属するコンテキス トが異なればクラスローダも異なり,すなわち別個のクラスオブジェクトと見な されるという,Javaのクラスローディングのルールに対応するためである.

Containerモジュールは,リクエスタから渡されたWebアプリケーション名を元に, Webアプリケーションが配備されているコンテキスト情報を探索する.以下, その手順について述べる.

まず,使用中のコンテナを特定する.コンテナの種類と配備されている位置は, Java仮想マシンのシステムプロパティやファイルシステム上の位置などの手がか りから総合的に自動判別する.次に,特定した情報からコンテナの設定リソース を探索し,内容を解析する.解析結果から得られる情報はコンテナの種類に依存 するが,少なくともコンテキスト情報のマッピングは特定できる.

コンテキスト情報は,Webアプリケーションごとに一つずつ存在し,主にWebアプ リケーションのベースディレクトリ(ドキュメントベース)を調べるために用いら れる.

Containerモジュールは抽象クラスとして実装されており,使用するコンテナに 対応したコンクリートクラスを用意しなければならない.コンクリートクラスで は,コンテナの仕様に依存するメソッドと自動判別ルーチンを実装する必要があ る.現状では,Tomcat 5.5系にのみ対応している.

3 WSRuntimeモジュール

WSRuntimeは,Webサービスフレームワークを抽象化したモジュールである.Web サービスフレームワークはWebアプリケーションとして振る舞うので,実行の際 はコンテナが必要となる.本システムではAxisが相当する.

ポートタイプとクラスオブジェクトのマッピング情報は,Webサービスフレー ムワークが管理している.しかし,一般にその設定リソースの位置やフォーマッ トは,Webサービスフレームワークの種類によって異なる. WSRuntimeモジュールは,このような不定な位置におかれたリソースをアプリケー ションから参照するためのインタフェースを提供する.

WSRuntimeモジュールは,リクエスタから渡されたWSDLのポートタイプ定義を元 に,マッピングされているクラスオブジェクトを探索する.以下,その手順 について述べる.

まず,ポートタイプの実装に用いられているWebサービスフレームワークを特定 する.Webサービスフレームワークの種類は,Webアプリケーション全体の設定リ ソースの内容などから総合的に自動判別する.次に,特定した情報からWebサー ビスフレームワークの設定リソースを探索し,内容を解析する.解析結果から得 られる最も重要な情報は,ポートタイプとクラスオブジェクトのマッピング 情報である.

マッピング情報は,主にクラスオブジェクトの完全修飾名 (Full Qualified Domain Name, FQDN) を調べるために用いられる.

WSRuntimeモジュールは抽象クラスとして実装されており,使用するWebサービス フレームワークに対応したコンクリートクラスを用意しなければならない.コン クリートクラスでは,Webサービスフレームワークの仕様に依存するメソッドと 自動判別ルーチンを実装する必要がある.現状では,Axis 1系にのみ対応している.

4 Providerモジュール

Providerは,バイトコード変換の対象となるポートタイプのクラスオブジェクト を,本システムのバイトコード変換ルーチンから参照するためのモジュールであ る.

ContainerモジュールとWSRuntimeモジュールは,それぞれが抽象化した対象のリ ソースへのアクセス手段を提供する.これらを用いて,実際にポートタイプとク ラスオブジェクトのバインディング管理を行うのがProviderモジュールである. 以下,その手順について述べる.

まず,リクエスタから渡されたWSDLのURLを元に,Containerモジュールと WSRuntimeモジュールを取得する.そして,あるポートタイプに対するバインド 要求があると,ContainerモジュールとWSRuntimeモジュールによる解析結果に基 づき,対応するクラスオブジェクトを探索してバインドする.このとき成立した バインディング情報は,Providerモジュール内で記憶される.これは,バインディ ングの重複を防ぐために用いられる.バインディングが成立すると,ポートタイ プをキーとするクラスオブジェクトのルックアップが可能になる.テスト終了時 など,バインディングを解除したい場合は,アンバインド操作を適切に行ってバ インド情報を削除する.

ルックアップによって得られるクラスオブジェクトは,主にInstrumentation Agentモジュールのコピーツリーから対応するクラスオブジェクトを取得するた めのキーとして用いられる.

5 Message Dispatcherモジュール

Message Dispatcherモジュールは,挿入されたテスト用コードによって出力されたメッ セージをハンドリングするモジュールである.

例えば,標準出力を用いたデバッグプリントはプロバイダが稼働するホスト のTTYに出力されてしまうので,リクエスタからメッセージを取得できない. また,テスト対象のメソッドが例外をスローした場合も,例外が伝搬する過程の どこかで意図しないキャッチが行われるか,またはプロバイダが稼働するホスト のTTYにスタックトレースが出力されるかのいずれかとなり,リクエスタにエラー の発生を通知できない.

そこで,テスト用コードによるメッセージをバッファリングし, リクエスタに転送するための機構が必要となる.これが,Message Dispatcherモ ジュールである.

Message Dispatcherモジュールは,以下の機能を提供する.

  1. メッセージバッファ
  2. 例外ディスパッチ

以下,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モジュール対応の例外オブジェクトでラッピングすればよい.

6 Controllerモジュール

Controllerは,本システムにおける論理的な処理を定義するモジュールである. 本システムに対する制御要求はすべてControllerモジュールに集約される.

以下,Controllerモジュールが提供する機能と,その内部的な振る舞いについて 述べる.

1 Controllerモジュールの生成

リクエスタから,テスト対象となるプロバイダが公開しているWSDLのURLを受け 取る.このWSDL,すなわちプロバイダに対して,一つのControllerモジュールが 生成される.

WSDLのURLは,ControllerモジュールおよびWebサービスフレームワークに関係す るモジュール (WSRuntime,Providerなど)の初期化に用いられる.

また,初期化時にMessage Dispatcherモジュールを生成する.Message Dispatcherモジュールは一つのプロバイダに対して一つだけ生成され,静的オブ ジェクトとして参照される.

2 テスト対象となるポートタイプのバインド

リクエスタから指定されたポートタイプを,Providerモジュールを用いてバイン ドする.

同時に,ポートタイプをキー,ポートタイプを構成するメンバの集合(コンスト ラクタ,メソッド)を値とするマップを生成する.以後,コンストラクタ とメソッドを総称して「ビヘイビア」,前述のマップを「ビヘイビアマップ」と 呼称する.ビヘイビアマップは,次に述べるコード挿入操作によって変更された ビヘイビアを記憶し,変更の重複を防止するために用いられる.

3 ポートタイプに対応するクラスオブジェクトの解析

ポートタイプ名に対応するクラスオブジェクトを取得し,そのメタデータを解析 する.

まず,ポートタイプ名をキーに,WSRuntimeモジュールからクラスオブジェク トのFQDNを取得する.それをキーに,Instrumentation Agentモジュールのコピー ツリーから対応するコピーオブジェクトを取得し,Javassistを用いてクラス構 造を解析する.解析結果として,クラスオブジェクトのメタデータ集合 (ビヘイ ビアのシグニチャなど) が得られる.

メタデータ集合は,ClassInfoオブジェクトとしてリクエスタに返信される. ClassInfoオブジェクトは,メタデータ集合を本システムとリクエスタで扱いや すい形式に変換し,適切なアクセサを付加したものである.

4 テスト用コードの挿入

任意のテスト用コードを,Javassistを用いてビヘイビアに挿入する.挿入可能 なコードポイントは,ビヘイビア呼び出しの前後いずれかである. テスト用コードは,Javaのソースコードとして記述する.

変更されたビヘイビアはビヘイビアマップに登録され,除去操作が行われるまで 他の挿入操作を受け付けなくなる.未挿入のコードポイントへの挿入および例外 ディスパッチャの有効化は可能である.

5 例外ディスパッチャの有効化

あるメソッド内で発生する例外をトラップし,呼び出し元に伝搬する前に処理を 追加できる.ただし,catchブロック内で対処が完結する例外は対象に含 まれない.

主な用途は,Message Dispatcherモジュールの例外ディスパッチへ例外処理を委 譲することである.詳細はMessage Dispatcherモジュールの説明を参照のこと.

変更されたビヘイビアはビヘイビアマップに登録され,除去操作が行われるまで 他の挿入操作を受け付けなくなる.未挿入のコードポイントへの挿入は可能である.

6 テスト用コードの除去

既に挿入されているテスト用コードを,Javassistを用いてビヘイビアから除去 する.例外ディスパッチャも無効化される.

オリジナルツリーのコードからクラス全体を再生成するため,リクエスタによる すべての改変が無効となる.

除去操作後,当該ビヘイビアはビヘイビアマップからも除去される.

7 テスト用コードの有効化

挿入操作によってフックされたテスト用コードを有効にする.

挿入操作を行った段階では,コピーツリーのクラスオブジェクトが変更されただ けで,まだテスト用コードは有効でない.本操作を行うことによって,実際にコ ピーツリーのクラスオブジェクトとオリジナルツリーのそれが入れ換わり,テス ト用コードが有効になる.すなわち,この時点で実行時バイトコード変換が成立 する.

クラスオブジェクトの入れ換えは,Java仮想マシンのHotswap機能によって実現 している.

8 テスト用コードの無効化

有効なテスト用コードを無効化する.

有効化操作の逆を行い,オリジナルコードに基づいてクラスオブジェクトを復元 する.ロード済みのテスト用コード付きクラスオブジェクトは破棄されるが,コ ピーツリーには残っている.

9 ライブラリのロード

シリアライズされたライブラリを復元し,コンテナにロードする. ライブラリは,JARフォーマットのバイト配列と見なす.

テスト用コードの実行にあたって外部ライブラリの追加を要する場合は,テスト 用コードを挿入する前に,本機能を使用してあらかじめライブラリをセットして おかなればならない.もしライブラリが不足していた場合,実行時例外が発生す る.

10 メッセージストリームの取得

Message Dispatcherモジュールが保持しているメッセージバッファを読み出すた めの,出力ストリームを提供する.

Controllerモジュール自身はメッセージバッファの管理に関与しないので,メッ セージバッファが更新されたことをメソッド利用者に通知できない. したがって,リアルタイム出力を実現するためには,後述するMessengerモジュー ルか,またはリクエスタにおいて,ポーリングしながらメッセージバッファを読み 出すなどの工夫が必要となる.

7 Messengerモジュール

Messengerは,外部要求に対するインタフェースを提供する.

Controllerモジュールは本システムにおける高レイヤのインタフェースを提供す るが,それらは特定のクライアントに依存しないように設計された汎用的な仕様 となっている.したがって実際に利用する際は,クライアントの差異を吸収する フロントエンドが必要となる.

Messengerモジュールは,Controllerモジュールのフロントエンドとして機能し, 以下の処理を行う.

  1. 外部要求に対する応答
  2. リクエスタとの通信に使用するプロトコルの変換
  3. Controllerモジュールの生成および初期化と,外部要求の転送

本システムにおける外部要求は,基本的にSOAPメッセージとして処理される. したがって,SOAPメッセージングに対応したMessengerモジュールをフロントエ ンドとし,Webサービスゲートウェイとして動作させることになる. Messengerモジュールは,WSDLを公開しWebサービスとしてテスト要求を待 ち受ける.WSDLの内容はMessengerモジュールのpublicメソッド集合であり, Controllerモジュールのインタフェースと似ている.

特に,本システムではWebサービスフレームワークとしてAxisを利用しているので, WSDLの生成やSOAPメッセージの処理はAxisに代替させることができる. 実際,Messengerモジュールの内容は,Controllerモジュールを生成して処理を委譲してい るのみである (ライブラリのデシリアライズやメッセージストリームのポーリン グを除く).

もしWebサービス以外のプロトコルで本システムを利用したい場合も,そのプロ トコルに対応したMessengerモジュールを作成し,Controllerモジュールに処理を委譲すれ ばよい.その際,複雑なデコードやプロトコル変換などの処理は,Messengerモ ジュールに隠蔽すべきである.

3 使用方法

本節では,本システムの使用方法について述べる.

プロバイダ管理者が本システムを配備するために要するコストは,起動前の設定 と起動パラメタの変更のみである.

1 プロバイダ

本システムは独立したJavaアプリケーションではなく,コンテナに付随するサブ システムである.以下に,起動前の設定とスタートアップのシーケンスを示す.
  1. 本システムのInstrumentation Agentモジュールは,Javaエージェントとして実装さ れている.Javaエージェントとは,Javaのブートストラップクラスロー ダにおいて実行される特殊なモジュールである.

    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が使用する.
  2. Messengerモジュールを,テストを許可するポートタイプと同じコンテキ ストに配備して,テスト用ゲートウェイを開設する.Webサービスフレー ムワークにAxisを使用している場合は,Axisに付属しているAdminClient を使用すると簡単に配備できる.
  3. コンテナを起動する.同時にシステム内部でInstrumentation Agentが起 動し,すべてのクラスローディングを解析してコピーツリーを生成する.
  4. コンテナの起動が完了し,本システムが利用可能になる.

2 リクエスタ

テスト用ゲートウェイに対する要求は,すべてSOAPメッセージングで行われる. 実用の際には,Axisに付属しているWSDL2Javaなどのコードジェネレータを用い て,テスト用ゲートウェイとの通信に用いるクライアントプログラムを作成する などの事前準備が必要になる.以下に,本システムを用いたテストのシーケンス を示す.
  1. テスト用ゲートウェイのメソッドを呼び出し,テストしたいプ ロバイダのWSDLをURL指定でセットする.すなわち,テスト用ゲートウェ イの定義が含まれるWSDLを指定すればよい.
  2. テスト用ゲートウェイのメソッドを呼び出し,引数にテストしたいポートタ イプ名をセットする.本システムによって,ポートタイプのクラスオブジェ クトが解析される.
  3. テスト用ゲートウェイのメソッドを呼び出し,先の操作で作成されたポー トタイプのメタデータ情報セットを取得する.
  4. メタデータの情報を元に,ビヘイビアに挿入するコード断片を作成する.
  5. テスト用ゲートウェイのメソッドを呼び出し,コード断片が使用するラ イブラリをシリアライズして送信する.
  6. テスト用ゲートウェイのメソッドを呼び出し,任意のビヘイビアにコー ド断片を挿入する.挿入可能なコードポイントは,ビヘイビアの先頭お よび最後である.また,例外ディスパッチも,この段階で有効化する. これらの作業を,テストしたいビヘイビアすべてに対して行う.
  7. 前述2から6の操作を,テストしたいポートタイプすべてに対して実行する.
  8. テスト用ゲートウェイのメソッドを呼び出し,変更したコードをすべて 有効化する.
  9. テスト用ゲートウェイのメソッドを呼び出し,メッセージストリームを 開く.
  10. 実際にテスト対象となるポートタイプにサービスを要求して,テストを 開始する.
  11. テストを中断する場合は,テスト用ゲートウェイの無効化メソッドを呼 び出す.この操作は,挿入されたコード断片の除去等は行わない.
  12. 特定のビヘイビアに対する変更をすべて除去したい場合は,テスト用ゲー トウェイの除去メソッドを呼び出す.
  13. 特定のポートタイプに対するテストを止めたい場合は,テスト用ゲート ウェイのメソッドを呼び出してポートタイプを解放する.
  14. テスト用ゲートウェイのメソッドを呼び出し,テストを完了する.この とき,未解放のポートタイプはすべて解放する.
MITSUBAYASHI Shin 2007-03-14