SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

S2Container.NETのDIとAOPを活用し生産性の高いシステムを構築する

.NET環境でDIコンテナとAOPの機能を提供するフレームワークを利用する


DIとAOPの設定を記述する(設定ファイル)

 DIやAOPに関する設定はXML形式のファイルに記述します。このファイルをs2dotnetでは「diconファイル」と呼びます。拡張子はdiconxmlに設定します。

 diconファイルの定義例は以下のようになります。

「Logic.dicon.xml」
<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>
Logic.diconからインクルードされる「Dao.dicon.xml」
<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>
Dao.diconからインクルードされる「Tx.dicon.xml」
<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>になります。主要属性は下表の通りです。

componetsタグの主要属性
属性名説明
nameコンポーネントの名前。
classコンポーネントの実装クラス。
autoBinding自動DIをどのように行うか。「auto(デフォルト),constructor,property,none 」。
instanceインスタンスをどのように生成するか。「singlton(常に1つ:デフォルト),prototype(毎回新しい),request(リクエストに1つ),session(セッションに1つ),outer(外部)」。

 ※設定の詳細についてはマニュアルを参考にすると良いでしょう。

DIの設定について

 設定ファイルに基づいてインスタンスを注入するDIですが、s2dotnetでは3つのパターンが利用できます。

DIの注入パターン
パターン説明関連するタグ
コンストラクタ インジェクションコンストラクタからコンポーネントを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を提供しています。

s2dotnet標準のInterceptor
Interceptorのクラス説明
Seasar.Extension.Tx.Impl.LocalRequiredTxHandler等トランザクション管理を行う
Seasar.Framework.Aop.Interceptors.TraceInterceptorメソッドの開始時と終了時にログ出力を行う
Seasar.Framework.Aop.Interceptors.MockInterceptorMockを使ったテストを簡単に行う

 このサンプルでは、ビジネスロジック層ではトランザクション制御(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のプログラム制御が不要になることといえるでしょう。

 自動トランザクションとして設定できる属性には、以下の種類があります。

自動トランザクションの属性について(Seasar.Extension.Tx.*)
属性Tx.diconの定義名説明
RequiredRequiredTxトランザクションが存在する場合は共有し、存在しない場合は新しいトランザクションを作成します。
RequiresNewRequiredNewTx現在のコンテキストとは関係なく、独立した新しいトランザクションを作成します。
SupportedSupportedTxトランザクションが存在する場合は共有します
NotSupportedNotSupportedTx制御するトランザクションがないコンテキストでコンポーネントを作成します。

 通常は「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()の部分が本来の処理のため、この前後にカスタマイズしたい処理を記述するようにします。

ExceptionInterceptor
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」という名前をつけます。

「app.config」
<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コンテナの初期化とコンポーネントの取得
//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されています。

参考:ASP.NETでの利用
 s2dotnetはASP.NETアプリケーションからも利用することができます。その場合は、S2HttpModuleと呼ばれるモジュールを利用するようにWeb.configに設定します。詳細についてはマニュアルを参考にすると良いでしょう。

まとめ

 s2dotnetの特長をまとめます。

  • s2dotnetは、DIコンテナとAOPの機能を有するフレームワークです。
  • DIコンテナの機能によってインターフェースと実装が分離されるため、品質・保守性が向上します。
  • AOPによってソースコードがシンプルになります。「traceInterceptor」でログ出力を、「RequiredTx等」でトランザクション管理を行えます。
  • DIとAOPを有効に活用することで、プログラムの量を減らし、見通しのよいシステムを構築することが可能になります。

最後に

 s2dotnetは国産のオープンソースプロジェクトです。そのため英語力に関係なく情報を収集することができますし、日本語で質問や要望をあげることもできます。もし、オープンソース活動に興味がある方は、Seasar関連のイベントに参加したり、s2dotnetのメーリングリストに参加してみてはいかがでしょうか。

参考資料

  1. s2dotnetホーム
  2. s2dotnetメーリングリスト
  3. Seasarプロジェクト
  4. @IT 『次世代J2EEを目指すSeasar2はどう誕生したか』 沖林正紀 著、2005年6月
  5. IT Pro 『部品と部品の関係を切り離す』 2005年8月
  6. 『Inversion of Control コンテナと Dependency Injection パターン』 Martin Fowler 著、かくたに 訳
  7. @IT 『アスペクト指向の基礎とさまざまな実装』 米山学 著、2004年10月
  8. 『.NETエンタープライズWebアプリケーション開発技術大全 Vol5 トランザクション設計編』 赤間信幸 著、日経BPソフトプレス、2005年3月
修正履歴

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 青木 淳夫(アオキ アツオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/180 2006/12/22 18:44

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー