DIとAOPの設定を記述する(設定ファイル)
DIやAOPに関する設定はXML形式のファイルに記述します。このファイルをs2dotnetでは「diconファイル」と呼びます。拡張子はdiconかxmlに設定します。
diconファイルの定義例は以下のようになります。
<components namespace="Logic"> <!-- データアクセス層のDiconファイルをインクルード --> <include path="S2dotnetSample/Dao/Dao.dicon.xml" /> <!-- AuthorLogicコンポーネント --> <component name="AuthorLogic" class="S2dotnetSample.Logic.Impl.AuthorLogicImpl" > <!-- プロパティインジェクション --> <property name="AuthorDao">Dao.AuthorDao</property> <!-- 自動トランザクションとログ出力機能をアスペクトにて織り込み --> <aspect>Tx.RequiredTx</aspect> <aspect>Dao.traceInterceptor</aspect> </component> </components>
<components namespace="Dao"> <!-- ローカルトランザクション設定のDiconファイルをインクルード --> <include path="S2dotnetSample/Dao/Tx.dicon.xml" /> <!-- AuthorDaoコンポーネント --> <component name="AuthorDao" class="S2dotnetSample.Dao.Impl.AuthorDaoImpl" > <!-- 例外処理とロギング機能をアスペクトにて織り込み --> <aspect>ExceptionInterceptor</aspect> <aspect>traceInterceptor</aspect> </component> <!-- ログ出力の機能を提供するInterceptor --> <component name="traceInterceptor" class="Seasar.Framework.Aop.Interceptors.TraceInterceptor" /> <!-- データアクセス層のエラーをラップする自作Interceptor --> <component name="ExceptionInterceptor" class="S2dotnetSample.Dao.Aop.ExceptionInterceptor" /> </components>
<components namespace="Tx"> ~省略~ <!-- 「RequiredTx」Interceptor--> <component name="RequiredTx" class="Seasar.Extension.Tx.TransactionInterceptor"> <arg> <component class="Seasar.Extension.Tx.Impl.LocalRequiredTxHandler" /> </arg> </component> ~省略~ </components>
これらの設定で、「AuthorLogic」というコンポーネント名を指定すると、「S2dotnetSample.Logic.Impl.AuthorLogicImp」のインスタンスが取得できます。その際、AuthorDaoプロパティには、S2dotnetSample.Dao.Impl.AuthorDaoImplのインスタンスがDIされ、自動トランザクションとログ出力がAOPによって織り込まれてます。
なお、DIやAOPの設定を行う要素は<component>になります。主要属性は下表の通りです。
| 属性名 | 説明 |
| name | コンポーネントの名前。 |
| class | コンポーネントの実装クラス。 |
| autoBinding | 自動DIをどのように行うか。「auto(デフォルト),constructor,property,none 」。 |
| instance | インスタンスをどのように生成するか。「singlton(常に1つ:デフォルト),prototype(毎回新しい),request(リクエストに1つ),session(セッションに1つ),outer(外部)」。 |
※設定の詳細についてはマニュアルを参考にすると良いでしょう。
DIの設定について
設定ファイルに基づいてインスタンスを注入するDIですが、s2dotnetでは3つのパターンが利用できます。
| パターン | 説明 | 関連するタグ |
| コンストラクタ インジェクション | コンストラクタからコンポーネントをDIする。 | <arg> |
| プロパティ インジェクション | プロパティからコンポーネントをDIする。 | <property> |
| メソッド インジェクション | メソッドからコンポーネントをDIする。 | <initMethod> |
上記のサンプルでは「プロパティ インジェクション」にてDIを行っています。(AuthorLogicクラスが公開しているAuthorDaoプロパティに<property>要素の設定に基づいてAuthorDaoクラスを注入しています。もしAuthorLogicクラスにAuthorDaoを引数とするコンストラクタを用意すれば「コンストラクタインジェクション」することも可能ですし、AuthorDaoを引数とするメソッドを用意すれば「メソッドインジェクション」することも可能です。)
component要素のname属性や「プロパティ インジェクション」の<property>要素を定義してませんが正しく動作します。これは、コンポーネント間の依存関係がコンテナによって自動的に解決されているためです。AuthorLogicクラスのAuthorDaoプロパティの型はIAuthorDaoで、このインターフェースを実装しているクラス(AuthorDaoImpl)の定義が1つだけであれば、自動的にDIされます。これを自動バインディングと呼びます。DIに慣れるまでは明確に定義を書いたほうが分かり易いかもしれませんが、開発規模が大きくなってくると設定ファイルに記述する量も増えてくるため、できるだけ自動バインディングを利用したほうが良いでしょう。
AOPの設定について
コンポーネントにAOPを織り込むには<aspect>要素を使用します。<aspect>要素にて指定できるクラスを「Interceptor(インターセプター)」と呼びます。s2dotnetは標準で以下のInterceptorを提供しています。
| Interceptorのクラス | 説明 |
| Seasar.Extension.Tx.Impl.LocalRequiredTxHandler等 | トランザクション管理を行う |
| Seasar.Framework.Aop.Interceptors.TraceInterceptor | メソッドの開始時と終了時にログ出力を行う |
| Seasar.Framework.Aop.Interceptors.MockInterceptor | Mockを使ったテストを簡単に行う |
このサンプルでは、ビジネスロジック層ではトランザクション制御(RequiredTx)とログ出力(TraceInterceptor)をAOPしています。データアクセス層では例外制御(自作Interceptor)とログ出力(TraceInterceptor)をAOPしています。
AOPの対象とするメソッドやプロパティは「<aspect pointcut="Execute.*">traceInterceptor</aspect>」のように、pointcut属性にカンマ区切りで正規表現指定します。 pointcut属性を指定しない場合は、すべてのメソッドが対象になります。
ログ出力を行う「TraceInterceptor」
「TraceInterceptor」はログを出力するIntercepterです。出力されるメッセージには、引数・戻値オブジェクトのToStringメソッドの文字列が付加されます。
自動トランザクションを管理する「RequiredTx等」
名前空間「Seasar.Extension.Tx.*」のクラスは自動トランザクション機能を織り込むIntercepterです。
自動トランザクション設定をしたクラスは、メソッド呼び出しのタイミングでトランザクションが開始され、メソッドが終わるタイミングでトランザクションが終了するようになります。トランザクション終了時に、Exceptionが発生していなければ書き込みを確定(Commit)し、Exceptionが発生していれば書き込みを取り消し(Rollback)します。
トランザクションを開始しました BEGIN S2dotnetSample.Logic.IAuthorLogic#addAuthor(Author) BEGIN S2dotnetSample.Dao.IAuthorDao#addAuthor(Author) (・・・SQLの実行・・・) END S2dotnetSample.Dao.IAuthorDao#addAuthor(Author) : END S2dotnetSample.Logic.IAuthorLogic#addAuthor(Author) : トランザクションをコミットしました
AuthorLogicに「RequiredTx」を設定すると、上記のログのようにAuthorLogicのメソッド開始と同時にトランザクションが開始し、完了と同時にCommit(Rollback)するようになります。自動トランザクションを使用する最大のメリットは、try~catchによるCommitとRollbackのプログラム制御が不要になることといえるでしょう。
自動トランザクションとして設定できる属性には、以下の種類があります。
| 属性 | Tx.diconの定義名 | 説明 |
| Required | RequiredTx | トランザクションが存在する場合は共有し、存在しない場合は新しいトランザクションを作成します。 |
| RequiresNew | RequiredNewTx | 現在のコンテキストとは関係なく、独立した新しいトランザクションを作成します。 |
| Supported | SupportedTx | トランザクションが存在する場合は共有します |
| NotSupported | NotSupportedTx | 制御するトランザクションがないコンテキストでコンポーネントを作成します。 |
通常は「RequiredTx」を使用することが多いですが、場合によって「RequiresNewTx」なども使用します(例:「書籍登録」がエラーになっても「著者登録」をコミットしたい場合には、「書籍登録」に「Required」を、「著者登録」に「RequiresNew」を設定します)。自動トランザクション機能の詳細に関しては、巻末の参考書籍『.NETエンタープライズWebアプリケーション開発技術大全 Vol5』をご覧ください。
s2dotnetの自動トランザクション管理では、ローカルトランザクションと分散トランザクション(MS-DTC)の2種類をサポートしています。
ローカルトランザクション
ローカルトランザクションとは分散トランザクションを使用しない一般的なトランザクションです。ローカルトランザクションを使用するには「Tx.dicon」をインクルードし、「Seasar.Extension.Tx.Impl.TxDataSource」からコネクションを取得してください。
分散トランザクション(MS-DTC)
分散トランザクションとはコミットやロールバック制御にWindowsが提供するMS-DTCを利用する方法です。環境構築や設計は複雑になりますが、複数サーバ間で2相コミットが必要となるようなケースでは有用です。分散トランザクションを使用するには「DtcTx.dicon」をインクルードします。
自動トランザクションの動作を確認するには、コントロールパネルの[管理ツール]から[コンポーネントサービス]を起動して、「トランザクションの統計」ページを見ると良いでしょう。正常にデータ更新された場合には「コミット」の数が更新され、問題があった場合には「中断」の数が更新されます。詳細についてはMSDNをご覧ください。
なお、分散トランザクションを使用する場合には、開発に使用するデータベースとデータプロバイダが自動トランザクション機能に対応しているかを確認する必要があります。(筆者はSQL ServerとOracleにて動作を確認しました。)
独自のInterceptorの実装方法
s2dotnetから標準で提供されていないAOP処理を行いたい場合には、「Seasar.Framework.Aop.Interceptors.AbstractInterceptor」を継承し、Invokeメソッドを実装します。invocation.Proceed()の部分が本来の処理のため、この前後にカスタマイズしたい処理を記述するようにします。
public class ExceptionInterceptor : AbstractInterceptor { public override object Invoke(IMethodInvocation invocation) { object result = null; try { result = invocation.Proceed(); } catch(Exception ex) { throw new DbException("データベースでエラーが発生しました",ex); } return result; } }
このExceptionInterceptorを織り込んだクラスにてエラーが発生した場合には、自動的にDbExceptionにてラップされるようになります。Exceptionの設計方針はプロジェクトごとに異なるものですが、粒度が細かいとExceptionを受け取る側の制御が複雑になるため、今回は1つのExceptionにてラップするようにしました。
diconファイルの管理・分割
diconファイルは、管理を容易にするために複数のファイルに分割することができます。他のdiconファイルを読み込むには<include>要素を使用します。他のdiconファイルのコンポーネントを指定する場合には<components namespace="XXXXX">で定義しているnamespace名をプレフィックスとして指定してください。ダウンロードサンプルではレイヤごとに1つのdiconファイルを作成しています。
なお、diconファイルの「ビルドアクション」は「埋め込みリソース」とします(そのため、クラスファイルを更新せずdiconファイルのみを更新した時は、「リビルド」を選択するようにしてください)。
アプリケーション構成ファイル「App.config」の記述
DIコンテナが最初に読み出すdiconファイルは「App.config」の<configPath>要素に記述します。慣例で「App.dicon」という名前をつけます。
<configSections> <section name="seasar" type="Seasar.Framework.Xml.S2SectionHandler, Seasar" /> </configSections> <seasar> <!-- SingletonS2ContainerFactory#Initで下記で指定されたdiconファイルを ルートのdiconファイルの初期値としてセットします。 --> <configPath>S2dotnetSample/App.dicon.xml</configPath> <!-- S2コンテナ作成時に以下で指定されたアセンブリをAppDomainに 読み込みます。(S2ContainerFactory#Create) --> <assemblys> <!-- <assembly>ローカルアセンブリ名</assembly> --> <!-- <assembly>グローバルアセンブリ名, Version=バージョン, Culture=カルチャ, PublicKeyToken=公開キートークン</assembly> --> </assemblys> </seasar>
またS2Container作成時にロードするアセンブリを<assembly>要素に記述することが可能です。
s2dotnetからコンポーネントを取得し利用する(プログラム)
最後に、s2dotnetからコンポーネントを生成・取得します。
//s2dotnetの初期化 SingletonS2ContainerFactory.Init(); //s2dotnetの取得 IS2Container container = SingletonS2ContainerFactory.Container; //型(Type)を指定してコンポーネントを取得 IAuthorLogic AuthorLogic1 = (IAuthorLogic) container .GetComponent(typeof(IAuthorLogic)); //名前(name)を指定してコンポーネントを取得 IAuthorLogic AuthorLogic2 = (IAuthorLogic) container .GetComponent("AuthorLogic"); //コンポーネントを利用 IList list = AuthorLogic1.getAuthorsAll();
コンポーネントを取得する前にs2dotnetを取得する必要があります。その後、s2dotnetからビジネスロジックのコンポーネントを取得します。コンポーネントは、型(Type)を指定する方法、diconファイルに指定した名前(name)を指定する方法のどちらでも取得することができますが、スペルミスのことを考えると型(Type)を指定するほうが良いでしょう。
なお、全てのコンポーネントをIS2Container#GetComponentから取得する必要はありません。最初のコンポーネントはIS2Container#GetComponentを使用して取得しますが、そのコンポーネントが依存するコンポーネントは自動的にバインディングされるため、再びGetComponentを呼び出す必要はありません。ダウンロードサンプルでも、プレゼンテーション層のFormをs2dotnetから取得した後は、他のレイヤのコンポーネントは数珠繋ぎ(プレゼンテーション層→ビジネスロジック層→データアクセス層)でDIされています。
S2HttpModuleと呼ばれるモジュールを利用するようにWeb.configに設定します。詳細についてはマニュアルを参考にすると良いでしょう。まとめ
s2dotnetの特長をまとめます。
- s2dotnetは、DIコンテナとAOPの機能を有するフレームワークです。
- DIコンテナの機能によってインターフェースと実装が分離されるため、品質・保守性が向上します。
- AOPによってソースコードがシンプルになります。「traceInterceptor」でログ出力を、「RequiredTx等」でトランザクション管理を行えます。
- DIとAOPを有効に活用することで、プログラムの量を減らし、見通しのよいシステムを構築することが可能になります。
最後に
s2dotnetは国産のオープンソースプロジェクトです。そのため英語力に関係なく情報を収集することができますし、日本語で質問や要望をあげることもできます。もし、オープンソース活動に興味がある方は、Seasar関連のイベントに参加したり、s2dotnetのメーリングリストに参加してみてはいかがでしょうか。
参考資料
- s2dotnetホーム
- s2dotnetメーリングリスト
- Seasarプロジェクト
- @IT 『次世代J2EEを目指すSeasar2はどう誕生したか』 沖林正紀 著、2005年6月
- IT Pro 『部品と部品の関係を切り離す』 2005年8月
- 『Inversion of Control コンテナと Dependency Injection パターン』 Martin Fowler 著、かくたに 訳
- @IT 『アスペクト指向の基礎とさまざまな実装』 米山学 著、2004年10月
- 『.NETエンタープライズWebアプリケーション開発技術大全 Vol5 トランザクション設計編』 赤間信幸 著、日経BPソフトプレス、2005年3月

