アスペクト指向プログラミングと Dependency Injection の融合 数理・計算科学専攻 千葉研究室 石川 零 指導教官:千葉 滋 スピード 、立て板に水 ゆっくり、話し方を工夫 全体の流れが置き去りになりがち 間を取る
目標:後から簡単に変更できるプログラムの開発 一度完成したら終わり、ではない バグの修正、機能の拡張 →後から簡単に変更できるよう設計することが重要 簡単に変更できるようにするには? 変更箇所を少なくする 変更箇所を1つにまとめる 修士論文発表会
コンポーネントを用いた開発 小さな部品(コンポーネント)を組み立てて、プログラムを作る 既存のプログラムの部品を“組み替える”だけで、新しいプログラムを作ることが可能 後からの変更が簡単 例 : オンライン書店のためのプログラムの開発 Amazon.com のようなもの 後からDB システムを高性能なものに変更 利用者の増加に伴い DB を操作するための機能が1つの部品になっていれば、その部品を交換するだけでよい 修士論文発表会
オブジェクト指向プログラミングを用いた設計 コンポーネントは、クラスとして設計 コンポーネントの内部に別なコンポーネントを利用するコードを記述することで、組み立てる コンストラクタ呼び出し メソッド呼び出し フィールド宣言 例:オンライン書店プログラム コンポーネントの組み替えには、この結合を変更する 注文を発注するコンポーネント DB 操作のための コンポーネント メソッド呼び出しによる結合 DB db.send(data); 修士論文発表会
問題点:組み替えが 簡単ではない 組み替えが起こりそうな箇所を ”予測して” 準備しないと、組み替えは困難 我々の考察では 組み替えが起こりそうな箇所を ”予測して” 準備しないと、組み替えは困難 変更箇所が散らばってしまう 例: DB コンポーネントの交換を簡単にするには ファクトリ、インタフェースを用いて準備 注文送信コンポーネント オブジェクト指向を用いたときでは、設計時に 起こりうる組み換えを予測して、それに備えて設計しない限り、 簡単に組み換えることができなかった 例えばDB の変更を予測して、それに備えた設計が以下になる。 このように設計時に備えておかなければならない。 それなら全ての変更を設計時に備えればいいじゃないかという話になるが、 それは困難。なぜなら「想定外」の変更がある MySQL 用DB 操作 コンポーネント DBFactory DB db = DBFactory.make(); db.send(data); Oracle 用DB 操作 コンポーネント 変更箇所はファクトリ内部のみ 修士論文発表会
組み替えを予測するのも困難 起こりうる変更例:インタフェースの変更 Oracle はDB アクセスの最適化アルゴリズムにヒントを与える機能を提供していた → 利用したい! テスト用 注文送信コンポーネント DBFactory class OrderingService { DB db = DBFactory.make(); void addShippingRate( Data data) { db.send(result); } MySQL 用 void sendHint() Oracle 用 db.sendHint(hint); 上位コンポーネントと DB インタフェース、他の DB コンポーネントの修正が必要! インタフェースやファクトリでは、 予測できない変更は対応できない 修士論文発表会
提案:glue コードを用いた設計 コンポーネントを結合するコードを、外側に分離 変更箇所を1つにまとめる GluonJ : glue コードを用いた設計を支援するシステム GluonJ による実装 これまでの実装 問題点の整理 一度組み立てたコンポーネントの組み替えは困難 組み替えを予測して準備しないと、修正箇所が散らばる 全ての組み替えの予測は困難 原因:コンポーネント内に結合のためのコードを記述する組み立て方 問題点が複雑→整理。ここをきちんと分からせる 修士論文発表会
コンポーネントの外側に分離すべき結合 結合には3種類 既存技術による分離 フィールド宣言 生成と代入 メソッド呼び出し 注文発注のためのコンポーネント class OrderingService { DB db = new MySQL(); void addShippingRate( Data data) { ... /* 配送代金の追加 */ db.send(result); } 2. 1. 既存技術による分離 ファクトリパターン、Dependency Injection 2 を分離 アスペクト指向プログラミング 1 を分離 2, 3 の分離は間接的 3. 突飛な話ではないことの証明。この話のベースとなる既存研究。ある種の背景。 詳しくは後ろではなすことを言う。 いまGluonJ は組み立て用のコードを分離するといいましたが、 そのようなコードは3種類あります。そのためそれぞれのコードを取り扱えなければいけません。 先ほどのプログラム上で説明すると、次のようになります。 これらを分離するものは過去にもあったが、3種類すべてに対応できるものはありませんでした。 そのうち設計するにあたり、大きな影響を受けた先行技術としてはDI とAOP があります。 修士論文発表会
GluonJ が可能にする結合の分離 フィールド宣言 生成と代入 メソッド呼び出し アスペクト指向プログラミングの技術を応用 Dependency Injection の技術を応用 メソッド呼び出し アスペクト指向プログラミングの技術を、結合をより直感的に記述できるよう改良 2つそれぞれでは不十分。だから組み合わせた。融合の話。 メソッド呼び出しの改良については、詳しくは後ろで話す 生成 フィールド 呼出 GluonJ ○ DI × AOP △ 修士論文発表会
GluonJ を用いて実装したオンライン書店システム 結合するためのコードは、glue コード に分離して記述 DB 型のフィールド宣言 DB コンポーネントの生成と代入 DB コンポーネントのメソッド呼び出し 注文発注コンポーネント glue コード MySQL 用 class OrderingService { void addShippingRate( Data data) { ... /*配送代金の追加*/ } Data result; MySQL db = new MySQL (); Oracle db = new Oracle (); 結合は全てglue コードに記述する 変更例1 を行うのに必要な変更箇所は、 全て glue コードの中にまとまっている 図? db.send(this.result); db.send(this.result); sendHint(); db.sendHint(hint); Oracle 用 変更が必要な箇所は、glue コードに まとまっている 修士論文発表会
予測しない変更を簡単に 起こりうる変更例:サブコンポーネントの追加 DB アクセスをともなうメソッドの冒頭でアクセス権をチェック 注文送信コンポーネント class OrderingService { DB db = DBFactory.make(); void addShippingRate( Data data) { db.send(result); } アクセス権検査 コンポーネント glue コード Checker checker = Factory.make(); 変更例1 はコンポーネントの交換 また glue コードを用いた設計では、一度結びつけたプログラムに 後からサブコンポーネントを追加することも簡単。 例えば、 GluonJ ではこんなことも可能になる。 暗にこれまではできなかったことを言う checker.check(id); 実行時に合成 修士論文発表会
結合のためのコードは glue コードに分離 言語機構 分離するコード <reference> フィールド宣言 生成と代入 <behavior> メソッド呼出 glue コード 2つの言語機構 <reference> タグ DB 型のフィールド宣言 DB コンポーネントを生成して代入するコード <behavior> タグ DB コンポーネントのメソッド呼び出し DB の生成と代入、参照はここで DB の処理の呼び出しはここで 修士論文発表会
<reference> タグの使い方(1): フィールド宣言の分離 言語機構 分離するコード <reference> フィールド宣言 生成と代入 <behavior> メソッド呼出 <reference> MySQL OrderingService.db ; </reference> タグの中にフィールドの宣言を記述 フィールドが新たに追加される 既に存在すれば、そのフィールドが使われる 例:MySQL 型のフィールドを、注文発注クラスに追加 追加するフィールド名 : db 左側の部分がフィールドの追加をあらわす OrderingService に、DB 型のフィールドを追加 フィールド名 : aspect aspect は特別なキーワード 修士論文発表会
<reference> タグの使い方(2): 生成と代入をするコードの分離 言語機構 分離するコード <reference> フィールド宣言 生成と代入 <behavior> メソッド呼出 <reference> MySQL OrderingService.db = new MySQL(); </reference> “=” の右側はコンポーネントを生成するコード MySQL コンポーネントの生成 コンポーネントの生成には Java コードを用いて記述 “=“ の左側は代入先を指定 注文発注クラスのフィールドに代入 左側の部分がフィールドの追加をあらわす OrderingService に、DB 型のフィールドを追加 フィールド名 : aspect aspect は特別なキーワード 修士論文発表会
<behavior> タグの使い方: メソッド呼び出しの分離 呼び出す箇所の指定 : <pointcut> タグ AspectJ の言語機構を利用 注文発注コンポーネントのメソッド内 呼び出すメソッドの指定 : <after> タグ Java コードで記述 db フィールドの差すDB コンポーネントの、 send() メソッドを呼び出す <before>,<around> も有り 言語機構 分離するコード <reference> フィールド宣言 生成と代入 <behavior> メソッド呼出 <behavior> <pointcut> execution(void OrderingService. addShippingRate(..)) </pointcut> <after> this.db.send(this.result); </after> </behavior> コード例を使いながら、働きの説明。 この式はPointcut 式で、コードを実行するプログラム中の箇所を指定している。 これはJava のコード、実行する処理を指定する。 修士論文発表会
GluonJ の処理系の実装 Java 言語用の処理系を実装済み JBoss 上で動くアプリケーションにも合成できる これまでの利用 既存のクラスに glue コードに記述された処理を合成 元のファイルは変更しない Javassist を利用して実装 JBoss 上で動くアプリケーションにも合成できる JBoss : アプリケーションサーバ JBoss のクラスローダの拡張機構を利用して実装 これまでの利用 J2EE アプリケーションに対するスケジューリング機能の追加 [日比野] に利用 KLAS システム [柳澤] は、処理系の実装に GluonJ の処理系のパーザ部分を利用 修士論文発表会
結合のためのコードの分離を目的とする既存技術 Dependency Injection (DI) 分離できるのは生成と代入のためのコードのみ アスペクト指向プログラミング (AOP) 分離するコンポーネントをアスペクトで書かなければいけない アスペクトを組み立て用のコードの分離のみに用いるという方法もあるが、間接的 DI は生成と代入のためのコードを分離するためのもの GluonJ は DI を元に AOP の技術を付け加えて、3種類できるようにした AOP は一見3種類の組み立て用コードを分離できるように見える しかし本質的な問題がある。 修士論文発表会
アスペクトで書くのがいけない理由 コンポーネントの開発者にとって制約 コンポーネントの再利用性を低下 AOP システムに依存した文法で記述 new できない (AspectJ など) メソッドシグネチャに制限 (JBoss-AOP など) コンポーネントの再利用性を低下 アスペクト = 分離したい処理 + 呼び出し箇所の指定 呼び出し箇所の指定が、結合するコンポーネントに依存 (アドバイス) (ポイントカット) 分離できるものもあるが、不十分 修士論文発表会
AOP を用いたもう1つの方法: アスペクトを結合の分離のみに利用 アスペクトをglue として用いる 生成と代入、フィールドの追加の分離 インタータイプ宣言を利用 メソッド呼び出しの分離 ポイントカットとアドバイスを利用 注文発注 コンポーネント glue アスペクト インタータイプ 宣言 制約があると話しました。 プログラミング上の工夫で何とかなる Glueonj とほぼ同じようにコードを書くことはできます。 アドホックだが望みの形にすることはできる。 プログラミング上の無理があります。 アスペクトを組み立ての中継として用いる GluonJ の glue コード アスペクトを glue コードとして使う GluonJ の解決法を真似ることは可能 MySQL OrderingService.db = new MySQL(); MySQL execution (addShippingRate(Data)) && this(s) ポイントカット アドバイス 修士論文発表会 s.db.send(data);
アスペクトを glue として使う問題点 (1): 代入が間接的 既存のフィールドに代入するコードを、直接分離する機能がない アドバイスを用いて記述→煩雑に 追加したフィールドへの代入を分離する方法と異なる GluonJ reference タグを用いて代入 追加したフィールドも、既存のフィールドも統一的に扱える 既存のフィールドに代入する専用の仕組みがないことを強調 AspectJ による記述 GluonJ による記述 after(OrderingService s) : (new OrderingService(..)) && this(s) { s.db = new MySQL(); } <reference> OrderingService.db = new MySQL(); </reference> 修士論文発表会
アスペクトを glue として使う問題点 (2): 実行時オーバヘッドの存在 多用しすぎると実行速度に影響 マイクロベンチマークによるオーバヘッドの測定 空のメソッドにカウンタアドバイスの呼出を挿入 1000万回の実行にかかった時間を比較 実装面での問題。 きちんと利用しても問題ない程度に抑えていることを言う オーバヘッドの測定結果 実行時間 AspectJ 188 ms GluonJ 35 ms 全てのコンポーネントを glue を使って組み立てるとなると、影響が出るのでは 修士論文発表会
まとめ glue コードを用いた開発の提案 発表済み論文 コンポーネント同士を組み立てるためのコードを分離して記述 GluonJ :glue コードを用いた開発を支援するアスペクト指向システム 発表済み論文 “Aspect-Oriented Programming beyond Dependency Injection”. 千葉, 石川. ECOOP 2005. “GluonJ によるビジネスロジックからのデータベースアクセスコードの分離 ”. 石川, 千葉. SPA 2005. “アスペクト指向プログラミングと Dependency Injection の融合” . 石川, 千葉. Tech-Report C-220 修士論文発表会