Seasarプロジェクト ＞ Seasar.NETプロジェクト ＞ S2Container.NET ＞ Quillで簡単DI+AOP

# 目次

上へ

Quillを使うことにより簡単にDIやAOPを行うことができます。 Quillでは属性を用いてDIやAOPの設定を行います。 S2Container.NET 1.3.0から追加された機能です。

ステートレスな業務ロジックを構築するために作成された為、S2Container.NETに比べると機能が制限されています。 ただしS2Container.NETと連携してS2Container.NETに登録されているコンポーネントを利用する機能があります。

- セットアップ
- DIを利用する
  - Quillでインスタンスを管理するオブジェクトをDIする
  - S2Containerでインスタンスを管理するオブジェクトをDIする
  - 注意点
- AOPを利用する
  - Quillでインスタンスを管理するInterceptorを使用する
  - S2Containerでインスタンスを管理するInterceptorを使用する
  - Interceptorの作成方法
  - Interceptorが呼び出される順番
  - 注意点
- diconファイルを書かずにSeasarの機能を利用する
  - diconファイルを使わないことによる利点
  - データソースの設定
  - アセンブリの設定
  - S2Daoを利用してDBアクセス
  - トランザクションをかける
  - 「quill」セクションの使い方
  - 注意点
- Windowsアプリケーションから簡単にQuillを利用する
- アンマネージ リソースを解放する
- Quill が保持するオブジェクトの参照を破棄する
- テスト時に Mock を利用する

# セットアップ

上へ

Quillのセットアップについては、以下のドキュメントを参照してください。

セットアップ - Quillのセットアップ

# DIを利用する

上へ

DIを行う為にはSeasar.Quill.QuillInjectorクラスのInjectメソッドに、DIを行わせたいオブジェクトを渡します。

QuillInjectorはInjectメソッドに渡されたオブジェクトのフィールドから自動的に判断して、 必要なオブジェクトをフィールドにバインドします。その際にあらかじめ設定ファイルでコンポーネントを登録しておく必要はありません。

QuillではQuillでインスタンスを管理するオブジェクトとS2Containerでインスタンスを管理するオブジェクトをDIすることができます。

## Quillでインスタンスを管理するオブジェクトをDIする

Quillでインスタンスを管理するオブジェクトをDIする場合は、 フィールドの型にSeasar.Quill.Attrs.ImplementationAttributeクラスを属性として設定します。 Quillは設定された属性からバインドすべきと判断して自動的にバインドを行います。

初めてバインドされるオブジェクトは新しくインスタンス化されますが、 一度バインドされたオブジェクトは以後同じインスタンスがバインドされることになります。 つまりQuillでバインドするためにインスタンス化されるオブジェクトは全てsingletonになります。 これはQuillがステートレスな業務ロジックを構築することを目的に作られたからです。

下記の例ではIEmployeeLogicインターフェースの実装クラスであるEmployeeLogicクラスを Form1のフィールドにバインドします。もしEmployeeLogicクラスがフィールドを持ち、 そのフィールドの型にImplementationAttributeクラスが属性として設定されていれば連鎖的にバインディングが行われていきます。

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Form1 form = new Form1();

            QuillInjector injector = QuillInjector.GetInstance();
            injector.Inject(form);
            ・・(略)・・
        }
    }

    public partial class Form1 : Form
    {
        // IEmployeeLogicの実装クラスであるEmployeeLogicのインスタンスがバインドされる
        protected IEmployeeLogic employeeLogic;

        public Form1()
        {
                InitializeComponent();
        }
        ・・(略)・・
    }

    [Implementation(typeof(EmpployeeLogic))]
    public interface IEmployeeLogic
    {
        ・・(略)・・
    }

    public class EmployeeLogic : IEmployeeLogic
    {
        ・・(略)・・
    }

上記のようなインターフェースを用意せずに直接クラスのインスタンスをバインディングする場合は、 下記のようにImplementationAttributeクラスに引数を指定しません。

    public partial class Form1 : Form
    {
        // EmployeeLogicのインスタンスがバインドされる
        protected EmployeeLogic employeeLogic;

        public Form1()
        {
                InitializeComponent();
        }
        ・・(略)・・
    }

    [Implementation]
    public class EmployeeLogic
    {
        ・・(略)・・
    }

また下記のように引数無しのImplementationAttributeクラスをインターフェースに指定して、 Aspectで実装を持たせることもできます。

    public partial class Form1 : Form
    {
        // Aspectによって拡張された型のインスタンスがバインドされる
        protected IEmployeeLogic employeeLogic;

        public Form1()
        {
                InitializeComponent();
        }
        ・・(略)・・
    }

    [Implementation]
    [Aspect(typeof(HogeInterceptor))]
    public interface IEmployeeLogic
    {
        ・・(略)・・
    }

## S2Containerでインスタンスを管理するオブジェクトをDIする

フィールドにSeasar.Quill.Attrs.BindingAttributeクラスが属性として指定されている場合は、 S2Containerでインスタンスを管理するオブジェクトをバインディングします。 BindingAttributeクラスの引数にはS2Containerのコンポーネント名を指定します。

下記の例ではS2Containerにコンポーネント名"empLogic"として定義されているコンポーネントのインスタンスを Form1のフィールドにバインドします。

    static class Program
    {
        [STAThread]
        static void Main()
        {

            // 定義(dicon)ファイルをセットする
            SingletonS2ContainerFactory.ConfigPath = "App.dicon";

            // S2Containerを初期化する
            SingletonS2ContainerFactory.Init();

            Form1 form = new Form1();

            QuillInjector injector = QuillInjector.GetInstance();
            injector.Inject(form);
            ・・(略)・・
        }
    }

    public partial class Form1 : Form
    {
        [Binding("empLogic")]
        protected IEmployeeLogic employeeLogic;

        public Form1()
        {
                InitializeComponent();
        }
        ・・(略)・・
    }

    public interface IEmployeeLogic
    {
        ・・(略)・・
    }

    public class EmployeeLogic : IEmployeeLogic
    {
        ・・(略)・・
    }

App.dicon

    <?xml version="1.0" encoding="utf-8" ?>
    <!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components21.dtd">

    <components>

        <component name="empLogic"
            class="HogeNamespace.EmployeeLogic" />

    </components>

## 注意点

- バインディングさせるために用意するフィールドのアクセス修飾子はprotectedかpublicにします （Aspectを利用しない場合はprivateでも可）
- Quillでインスタンスを管理するオブジェクトのインスタンスはsingletonになります
- Quillでインスタンスを管理するオブジェクトはインターフェース1つに実装クラス2つという場合に、 動的に実装クラスを切り替えるといったことはできません （ImplementationAttributeの引数を変更して静的に変更することは可能です） そのような場合はS2Containerで実装クラスを切り替える仕組みを作成する必要があります
- QuillからS2Containerを利用する場合、 SingletonS2ContainerFactoryから取得できるS2Containerのみを利用することができます

# AOPを利用する

上へ

QuillでAOPを利用する為にはSeasar.Quill.Attrs.AspectAttributeクラスを属性として設定します。 AspectAttributeクラスはクラス・インターフェース、メソッドに設定することができます。 ただしQuillによってインスタンス化されたオブジェクトにのみ設定が有効となります。

AspectAttributeクラスの第1引数にはInterceptorを指定します。 Quillでインスタンスを管理するInterceptorを指定する場合はInterceptorのTypeクラスを指定します。 S2Containerでインスタンスを管理するInterceptorを指定する場合はInterceptorのコンポーネント名を指定します。

クラス・インターフェースにAspectAttributeクラスを設定した場合は、 クラス・インターフェースに含まれるメソッドにAspectが適用されます。 ただしインターフェースに定義されたメソッド、virtualキーワードが指定されたメソッドが対象となります。

メソッドにAspectAttributeクラスを設定することでメソッド毎にAspectを適用することができます。 ただしインターフェースに定義されたメソッド、virtualキーワードが指定されたメソッドにしか設定することはできません。

## Quillでインスタンスを管理するInterceptorを使用する

Quillでインスタンスを管理するInterceptorを使用する場合は、 Seasar.Quill.Attrs.AspectAttributeクラスを属性として、 第1引数にInterceptorのTypeクラスを指定します。

Interceptorのインスタンスは必ずsingletonになります。

下記の例ではEmployeeLogicのGetEmployeeByEmpNoにAspectを適用します。 InterceptorのインスタンスはQuillで管理されます。

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Form1 form = new Form1();

            QuillInjector injector = QuillInjector.GetInstance();
            injector.Inject(form);
            ・・(略)・・
        }
    }

    public partial class Form1 : Form
    {
        // EmployeeLogicのインスタンスがバインドされる
        protected EmployeeLogic employeeLogic;

        public Form1()
        {
                InitializeComponent();
        }
        ・・(略)・・
    }

    [Implementation]
    public class EmployeeLogic
    {
            [Aspect(typeof(ConsoleWriteInterceptor))]
            public virtual Employee GetEmployeeByEmpNo(int empNo)
            {
                Console.WriteLine("メソッドが呼び出されました");
            }
        ・・(略)・・
    }

    public class ConsoleWriteInterceptor : IMethodInterceptor
    {
        #region IMethodInterceptor メンバ

        public object Invoke(IMethodInvocation invocation)
        {
            MethodBase method = invocation.Method;
            Console.WriteLine("メソッドを開始します");

            // 実際の処理を実行する
            object ret = invocation.Proceed();

            Console.WriteLine("メソッドを終了します");

            return ret;
        }

        #endregion
    }

EmployeeLogicのGetEmployeeByEmpNoをForm1から実行すると下記のようなメッセージがコンソールに出力されます。

    メソッドを開始します
    メソッドが呼び出されました
    メソッドを終了します

## S2Containerでインスタンスを管理するInterceptorを使用する

S2Containerでインスタンスを管理するInterceptorを使用する場合は、 Seasar.Quill.Attrs.AspectAttributeクラスを属性として、 第1引数にInterceptorのコンポーネント名を指定します。

下記の例ではEmployeeLogicのGetEmployeeByEmpNoにAspectを適用します。 InterceptorのインスタンスはS2Containerで管理されます。

    static class Program
    {
        [STAThread]
        static void Main()
        {
            // 定義(dicon)ファイルをセットする
            SingletonS2ContainerFactory.ConfigPath = "App.dicon";

            // S2Containerを初期化する
            SingletonS2ContainerFactory.Init();

            Form1 form = new Form1();

            QuillInjector injector = QuillInjector.GetInstance();
            injector.Inject(form);
            ・・(略)・・
        }
    }

    public partial class Form1 : Form
    {
        // EmployeeLogicのインスタンスがバインドされる
        protected EmployeeLogic employeeLogic;

        public Form1()
        {
                InitializeComponent();
        }
        ・・(略)・・
    }

    [Implementation]
    public class EmployeeLogic
    {
            [Aspect("consoleInterceptor")]
            public virtual Employee GetEmployeeByEmpNo(int empNo)
            {
                Console.WriteLine("メソッドが呼び出されました");
            }
        ・・(略)・・
    }

    public class ConsoleWriteInterceptor : IMethodInterceptor
    {
        #region IMethodInterceptor メンバ

        public object Invoke(IMethodInvocation invocation)
        {
            MethodBase method = invocation.Method;
            Console.WriteLine("メソッドを開始します");

            // 実際の処理を実行する
            object ret = invocation.Proceed();

            Console.WriteLine("メソッドを終了します");

            return ret;
        }

        #endregion
    }

App.dicon

    <?xml version="1.0" encoding="utf-8" ?>
    <!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components21.dtd">

    <components>

        <component name="consoleInterceptor"
            class="HogeNamespace.ConsoleWriteInterceptor" />

    </components>

EmployeeLogicのGetEmployeeByEmpNoをForm1から実行すると下記のようなメッセージがコンソールに出力されます。

    メソッドを開始します
    メソッドが呼び出されました
    メソッドを終了します

## Interceptorの作成方法

Interceptorの作成方法はS2AOP.NETと同じです。下記のドキュメントを参照して下さい。

AOP - (3) 独自実装によるInterceptor

## Interceptorが呼び出される順番

クラス・インタフェースに設定されたInterceptorは、メソッドに設定されたInterceptorより先に呼び出されます。 クラス・インターフェースに複数のAspectAttributeクラスが設定された場合、 もしくはメソッドに複数のAspectAttributeクラスが設定された場合は、 AspectAttributeクラスの第2引数に並び順を指定して呼び出される順番を制御することができます。 数字の小さい方が先に呼び出されます。第2引数を省略した場合は0が設定されたと判断します。 ただしメソッドに指定したInterceptorをクラス･インターフェースに設定されたInterceptorより先に呼び出すことはできません。

    [Implementation]
    public class EmployeeLogic
    {
            [Aspect("consoleInterceptor", 1)]
            [Aspect(typeof(TraceInterceptor), 2)]
            public virtual Employee GetEmployeeByEmpNo(int empNo)
            {
                ・・(略)・・
            }
        ・・(略)・・
    }

## 注意点

- Aspectを適用する為にはインターフェースに定義されたメソッド、virtualキーワードが指定されたメソッドである必要があります
- Aspectを適用するクラスとメソッドはpublicである必要があります
- staticキーワードが指定されたクラス・メソッドにはAspectを適用できません
- Interceptorのインスタンスはsingletonです
- S2Dao.NETやトランザクションを利用する場合はS2Containerに登録したInterceptorを使うようにしてください （配布物のSeasarソリューションに含まれるSeasar.Quill.Examplesプロジェクトに使用例があります）

# diconファイルを書かずにSeasarの機能を利用する

上へ

## diconファイルを使わないことによる利点

QuillではS2Daoやトランザクションの設定も含めてdiconファイルを書かずに
属性を使用することでDI+AOPを利用することができます。

その利点は大まかに以下のものが挙げられます。

- ＜タイプセーフ＞
  ・文字列で定義していた項目をコンパイル対象となる属性で定義しているため
- ＜diconファイルへの「埋め込まれたリソース」の設定を意識しなくてもよくなる＞
- ＜遅延初期化で起動が速い＞
  DIコンテナの初期化をアプリケーション起動時よりも遅らせることが
  できるため、初回起動時のコストが低くなる。

## データソースの設定

設定ファイル(App.config)の「quill」セクション内に下記の設定を記述することで
データソースを設定することができます。
各データソースの定義は「dataSource」タグに記述し、
複数データソースを定義する場合は「dataSources」タグ内に
各データソースの設定を書いていきます。

dataSourcesで定義されたデータソースは
Seasar.Quill.Database.DataSource.Impl.SelectableDataSourceProxyWithDictionaryという
クラスのインスタンスでQuillコンテナ内に保持し、DBアクセス時に使用します。

dataSourceタグ内で設定する内容は以下の通りです。

### ・データソース名

データソースをアプリケーション中で一意に識別するための名前です。

### ・プロバイダクラス名

ADO.NET上で使うクラス名を保持するSeasar.Extension.ADO.DataProvider継承クラス名です。
独自のプロバイダクラスを使う場合は名前空間付きで指定します。
Quill内にあらかじめ定義されているプロバイダクラスを使用する場合は
クラス名のみの指定で使用可能です。

以下のプロバイダがQuill内に定義されています。

プロバイダ | 説明
--- | ---
SqlServer | SQLServer用プロバイダクラス
ODP | ODP.NET(Oracle)用プロバイダクラス
Oracle | Oracle用プロバイダクラス
DB2 | DB2用プロバイダクラス
MySQL | MySQL用プロバイダクラス
PostgreSQL | PostgreSQL用プロバイダクラス
Firebird | Firebird用プロバイダクラス)
OleDb | OleDb用プロバイダクラス)

### ・接続文字列

クラスを指定する方法と文字列を直接書く方法があります。

クラスを指定する場合は
Seasar.Quill.Database.DataSource.Connection.IConnectionStringインターフェースを
実装し、「connectionString」タグにクラス名を名前空間付きで指定して下さい。

文字列で直接書く場合は接続文字列を「"」で囲って指定して下さい。

データソースは以下の優先順序でQuill内に保持されます。

- １．App.config内「quill」セクションのdataSources、dataSourceの設定
- ２．App.config内ConnectionStringsセクションの設定
- ３．XXX.dicon内に記述された設定

### ・データソースクラス名

Seasar.Extension.ADO.Impl.DataSourceImplや
Seasar.Extension.Tx.Impl.TxDataSourcelなどの
Seasar.Extension.ADO.IDataSource実装クラス名を
名前空間付きで指定します。

※使用可能なデータソースはS2Container.NETと同じです。

※トランザクションを利用する場合は必ず
Seasar.Extension.Tx.Impl.TxDataSourcel.TxDataSource、
またはその継承クラスを使用して下さい。

下記は設定ファイル(App.config)の設定例です。

    <quill>
        <!-- (他設定は省略) -->
        <!-- データソース設定 -->
        <dataSources>
            <dataSource name="ds">
                <provider>SqlServer</provider>
                <connectionString>"Server=localhost(途中略)"</connectionString>
                <class>Seasar.Extension.Tx.Impl.TxDataSource</class>
            </dataSource>
        </dataSources>
    </quill>

## アセンブリの設定

SQLServer以外のデータベースを使用する（上記データソースの設定でSqlServer以外の
プロバイダを利用する）場合はApp.configで「quill」セクションの「assemblys」、「assembly」を
使って参照するアセンブリを指定する必要があります。

アセンブリの設定例（Oracle Data Providerを使用する場合）

    <quill>
        <!-- （省略） -->
        <!-- ロードするアセンブリ名 -->
        <assemblys>
          <assembly>Seasar.Dao</assembly>
          <!-- Oracle Data Provider for .NET を使用する場合に必要です。 -->
          <assembly>Oracle.DataAccess, version=10.2.0.100, Culture=neutral,

              PublicKeyToken=89b483f429c47342</assembly>
        </assemblys>
        <!-- （省略） -->
    </quill>

## S2Daoを利用してDBアクセス

S2Dao.NETの機能を利用して検索、更新などのDBアクセスを行いたい場合は
S2Dao属性を使用します。

### ・適用範囲

S2Dao属性はクラス、インターフェース、メソッドに対して付けることができます。 それぞれのS2Dao(S2DaoInterceptor)の適用範囲は以下の通りです。

指定箇所 | 適用範囲
--- | ---
クラス、インターフェース | クラス、インターフェース内の全てのメソッド(public以外も含む)
メソッド | 指定したメソッド(public以外も含む)

    // クラス、インターフェースに対して設定する場合
    [S2Dao]
    [Bean(typeof(Hoge))]
    public interface IHogeDao {
        Employee GetHoge(int hogeNo); // S2DaoInterceptorが適用される
        void Update(Hoge entity);     // S2DaoInterceptorが適用される
    }

    // メソッドに対して設定する場合
    [Bean(typeof(Hoge))]
    public interface IHogeDao {
        [S2Dao]
        Employee GetHoge(int hogeNo); // S2DaoInterceptorが適用される

        void DoSomething();  // このメソッドにはS2DaoInterceptorは適用されない
    }

### ・設定

#### ・標準の設定を使う

S2Dao属性を引数なしで設定し、App.configにも設定の記述がない場合
S2Daoの標準的な設定が使われます。 具体的には以下のクラスのインスタンスがQuill内で生成され使用されます。

- Seasar.Extension.ADO.Impl.BasicCommandFactory
- Seasar.Extension.ADO.Impl.BasicDataReaderFactory
- Seasar.Dao.Impl.FieldAnnotationReaderFactory
- Seasar.Dao.Interceptor.S2DaoInterceptor

#### ・カスタムした設定を使う

独自に拡張した設定を使用する場合、
Seasar.Quill.Dao.IDaoSetting実装クラスを作成し
後述の方法でカスタム設定の適用範囲を定義します。

IDaoSettingは以下のプロパティ、メソッドをもっています。

プロパティ名 | 説明 | 戻り値Null
--- | --- | ---
DaoMetaDataFactory | IDaoMetaDataFactory実装クラス | ×不可
DaoInterceptor | IMethodInterceptor実装クラス | ×不可
DataSourceName | 使用するデータソース名 （App.configのdataSourceタグで設定したもの） | ○可能

メソッド名 | 説明
--- | ---
Setup | S2DaoInterceptorなどの設定などを行う
IsNeedSetup | Setupメソッドを呼ぶかどうかを返す

※Seasar.Quill.Dao.Impl.AbstractDaoSetting（IDaoSettingを実装する抽象クラス）を使うと
プロパティの定義とIsNeedSetupの実装を省略できます。

AbstractDaoSettingを継承した実装例

    ///
    /// 一般的なDaoSetting実装クラス
    ///
    public class TypicalDaoSetting : AbstractDaoSetting
    {
        protected override void SetupDao(IDataSource dataSource)
        {
            BasicCommandFactory commandFacoty = new BasicCommandFactory();
            BasicDataReaderFactory dataReaderFactory =
                new BasicDataReaderFactory(commandFacoty);
            FieldAnnotationReaderFactory annotationReaderFactory =
                new FieldAnnotationReaderFactory();
            _daoMetaDataFactory = new DaoMetaDataFactoryImpl(
                dataSource, commandFacoty, annotationReaderFactory, dataReaderFactory);
            _daoInterceptor = new S2DaoInterceptor(_daoMetaDataFactory);
        }
    }

##### カスタム設定を全てのS2Dao属性に一括して適用する

App.configの「quill」セクション内に「daoSetting」タグを
記述し、その中に作成した設定クラスを名前空間付で書きます(「"」は不要）。

    <quill>
        <!-- S2Dao設定 -->
        <daoSetting>Hoge.CustomDaoSetting</daoSetting>
        <!-- (他設定は省略) -->
    </quill>

##### カスタム設定をS2Dao属性ごとに適用する

S2Dao属性の引数にtypeof(カスタム設定クラス)の戻り値を渡します。

    [Bean(typeof(Hoge))]
    public interface IHogeDao
    {
        [S2Dao(typeof(CustomDaoSetting))]
        Hoge GetHoge(int hogeNo);
    }

※S2Dao属性に渡した型のインスタンスはQuillコンテナの中に一つしか作られませんが
SetupメソッドはIsNeedSetupの戻り値がtrueであれば設定されたS2Dao属性の数だけ呼ばれます。

S2Daoの設定は以下の優先順で適用されます。

- １．S2Dao属性の引数で指定されたS2Dao設定クラス
- ２．App.configのdaoSettingタグで定義されたS2Dao設定クラス
- ３．標準設定

## トランザクションをかける

トランザクションをかける箇所を指定したい場合は
Transaction属性を使用します。

### ・適用範囲

Transaction属性はクラス、インターフェース、メソッドに対して付けることができます。 それぞれのトランザクション(TransactionInterceptor)の適用範囲は以下の通りです。

指定箇所 | 適用範囲
--- | ---
クラス、インターフェース | クラス、インターフェース内の全てのメソッド(public以外も含む)
メソッド | 指定したメソッド(public以外も含む)

    // クラス、インターフェースに対して設定する場合
    [Transaction]
    public interface IHogeBhv {
        Employee GetHoge(int hogeNo); // トランザクションが適用される
        void Update(Hoge entity);     // トランザクションが適用される
    }

    // メソッドに対して設定する場合
    public interface IHogeBhv {
        [Transaction]
        void Update(Hoge entity);     // トランザクションが適用される

        void DoSomething();  // このメソッドにはトランザクションは適用されない
    }

### ・設定

#### ・標準の設定を使う

Transaction属性を引数なしで設定し、App.configにも設定の記述がない場合
S2Daoにおけるトランザクションの標準的な設定が使われます。 具体的には以下のクラスのインスタンスがQuill内で生成され使用されます。

- Seasar.Extension.Tx.Impl.TransactionContext
- Seasar.Extension.Tx.Impl.LocalRequiredTxHandler
- Seasar.Extension.Tx.TransactionInterceptor

#### ・カスタムした設定を使う

独自に拡張した設定を使用する場合、
Seasar.Quill.Database.Tx.ITransactionSetting実装クラスを作成し
後述の方法でカスタム設定の適用範囲を定義します。

ITransactionSettingは以下のプロパティ、メソッドをもっています。

プロパティ名 | 説明 | 戻り値Null
--- | --- | ---
TransactionContext | ITransactionContext実装クラス | ×不可
TransactionInterceptor | IMethodInterceptor実装クラス | ×不可

メソッド名 | 説明
--- | ---
Setup | TransactionInterceptorなどの設定などを行う
IsNeedSetup | Setupメソッドを呼ぶかどうかを返す

※Seasar.Quill.Database.Tx.Impl.AbstractTransactionSetting
（ITransactionSettingを実装する抽象クラス）を使うと
プロパティの定義とIsNeedSetupの実装を省略できます。

AbstractTransactionSettingを継承した実装例

    ///
    /// 一般的なトランザクションの設定クラス
    ///
    public class TypicalTransactionSetting : AbstractTransactionSetting
    {
        protected override void SetupTransaction(IDataSource dataSource)
        {
            //  TransactionContext
            _transactionContext = new TransactionContext();
            TransactionContext txContext = (TransactionContext)_transactionContext;
            txContext.DataSouce = dataSource;
            txContext.IsolationLevel = IsolationLevel.ReadCommitted;

            //  TransactionContextを使用するデータソースにも設定
            Type dataSourceType = dataSource.GetType();
            if (typeof(SelectableDataSourceProxyWithDictionary).IsAssignableFrom(
                dataSourceType))
            {
                ((SelectableDataSourceProxyWithDictionary)dataSource)
                    .SetTransactionContext(txContext);
            }
            else if (typeof(TxDataSource).IsAssignableFrom(dataSourceType))
            {
                ((TxDataSource)dataSource).Context = txContext;
            }

            //  TransactionInterceptor
            LocalRequiredTxHandler handler = new LocalRequiredTxHandler();
            handler.Context = txContext;
            _transactionInterceptor = new TransactionInterceptor(handler);
            ((TransactionInterceptor)_transactionInterceptor).TransactionStateHandler
                = txContext;
        }
    }

##### カスタム設定を全てのTransaction属性に一括して適用する

App.configの「quill」セクション内に「transactionSetting」タグを
記述し、その中に作成した設定クラスを名前空間付で書きます(「"」は不要）。

    <quill>
        <!-- Transaction設定 -->
        <transactionSetting>Hoge.CustomTransactionSetting</transactionSetting>
        <!-- (他設定は省略) -->
    </quill>

##### カスタム設定をTransaction属性ごとに適用する

Transaction属性の引数にtypeof(カスタム設定クラス)の戻り値を渡します。

    public interface IHogeBhv
    {
        [Transaction(typeof(CustomTransactionSetting))]
        void Update(Hoge entity);
    }

※Transaction属性に渡した型のインスタンスはQuillコンテナの中に一つしか作られませんが
SetupメソッドはIsNeedSetupの戻り値がtrueであれば設定されたTransaction属性の数だけ呼ばれます。

Transactionの設定は以下の優先順で適用されます。

- １．Transaction属性の引数で指定されたTransaction設定クラス
- ２．App.configのtransactionSettingタグで定義されたTransaction設定クラス
- ３．標準設定

## 「quill」セクションの使い方

アプリケーション構成ファイル（App.config）で「quill」セクションを使うためには、以下のようにconfigSections/section要素を追加します。

App.config

    <configuration>
      <configSections>
        <section name="quill"
            type="Seasar.Quill.Xml.QuillSectionHandler, Seasar.Quill" />
      </configSections>
        ・・・
    <configuration>

「quill」セクションを使用して、データソースの設定やアセンブリの設定を行うことができます。以下は設定例です。

App.config

    <configuration>
      <configSections>
        <section name="quill"
            type="Seasar.Quill.Xml.QuillSectionHandler, Seasar.Quill" />
      </configSections>
      <quill>
        <dataSources>
          <dataSource name="ds">
            <provider>Firebird</provider>
            <connectionString>
              "ServerType=1;User=ユーザー;Password=パスワード;
                Database=C:\Firebird\データベース.FDB"
            </connectionString>
            <class>Seasar.Extension.Tx.Impl.TxDataSource</class>
          </dataSource>
        </dataSources>
        <assemblys>
          <assembly>
           FirebirdSql.Data.FirebirdClient, Version=2.1.0.0,
             Culture=neutral, PublicKeyToken=3750abcc3150b00c
          </assembly>
        </assemblys>
      </quill>
    <configuration>

## 注意点

- データソースにTxDataSourceを使う場合は必ずTransaction属性を
  どこかで使用していることが必要になります。
  （TxDataSourceを使用=トランザクションが使われることが前提のため）
- S2Dao属性、Transaction属性が設定されている箇所には
  Interceptor(Aspect)が適用されます。そのため「AOP利用の注意点」と
  同様の制約があります。

# Windowsアプリケーションから簡単にQuillを利用する

上へ

Seasar.Quill.QuillInjectorクラスのInjectメソッドを用いてDIを行ってきましたが、 ツールボックスからコントロール（Seasar.Quill.QuillControl）をFormに貼り付けるだけで簡単にDIを利用することもできます。

Seasar.Quill.QuillControlを使う場合はツールボックスに登録する必要があります。 「ツールボックスアイテムの選択」からSeasar.Quill.dllを選択してください。

下記はForm1のフィールドにCulcLogicをバインドする例です。

    Public partial class Form1 : Form
    {
        protected CulcLogic culcLogic = null;

        private void button1_Click(object sender, EventArgs s)
        {
            int ret = culcLogic.Plus(1, 2);
            Console.WriteLine(“戻り値：” + ret);
        }
    }

    [Implementation]
    Public class CulcLogic
    {
        [Aspect(typeof(ConsoleWriteInterceptor))]
        public virtual int Plus(int x, int y)
        {
            Console.WriteLine(“Plusが呼ばれました”);
            return x + y;
        }
    }

次にデザイナでQuillControlをForm1に貼り付けます。

これでフィールドのculcLogicにAspectが適用されたインスタンスがDIされるようになります。 実行して「Plusを呼ぶ」ボタンを押すとコンソールにメッセージが出力されます。

    メソッドを開始します
    Plusが呼ばれました
    メソッドを終了します
    戻り値：3

# アンマネージ リソースを解放する

上へ

バインドされるために Quill によってインスタンス化されたオブジェクトのインスタンスは、 singleton として保持されます。 バインドされるためにインスタンス化されたオブジェクトが System.IDisposable インターフェースを実装している場合は Quill から Dispose メソッドを呼び出すことができます。

以下のように QuillInjector の Dispose メソッドを呼び出して、アンマネージ リソースを解放します。

    QuillInjector.GetInstance().Dispose();

# Quill が保持するオブジェクトの参照を破棄する

上へ

バインドされるために Quill によってインスタンス化されたオブジェクトのインスタンスは、 singleton として保持されます。 しかし Quill が保持するオブジェクトの参照を破棄することができます。

以下のように QuillInjector の Destroy メソッドを呼び出して、Quill が保持するオブジェクトの参照を破棄します。 破棄するオブジェクトが System.IDisposable を実装している場合は参照を破棄する前に Dispose メソッドが呼び出されます。

    QuillInjector.GetInstance().Destroy();

# テスト時に Mock を利用する

上へ

テストコードでリモートの処理を呼び出す部分等を Mock に置き換えたい場合があります。 そんな場合には QuillInjector の代わりに Seasar.Quill.Unit.MockInjector を使います。

通常はインターフェースに Implementation 属性で実装クラスを指定しますが、 同じように Mock 属性(Seasar.Quill.Attrs.MockAttribute)で Mock クラスを指定することができます。 MockInjector を使用すると Mock 属性で Mock クラスが指定されている場合に、 Implementation 属性より優先して Mock クラスを Inject します。 Mock 属性が指定されていない場合は QuillInjector と同じ動作をします。

以下はサンプルです。

    [Implementation(typeof(RemoteLogic))]
    [Mock(typeof(MockRemoteLogic))]
    public interface IRemoteLogic
    {
        string ExecuteRemote();
    }

    public class MockRemoteLogic : IRemoteLogic
    {
        public string ExecuteRemote()
        {
            return "Mock です。";
        }
    }

    public class CallRemoteLogic
    {
        protected IRemoteLogic remoteLogic;

        public string CallRemote()
        {
            return remoteLogic.ExecuteRemote();
        }
    }

以下はテストクラスです。MockInjector を使っている為、Mock が使われます。

    // CallRemoteLogic のテストクラス
    [TestFixture]
    public class CallRemoteLogicTest
    {
        // CallRemoteメソッドのテスト
        [Test]
        public void TestCallRemote()
        {
            CallRemoteLogic logic = new CallRemoteLogic();
            MockInjector.GetInstance().Inject(logic);

            string ret = logic.ExecuteRemote();

            Assert.AreEqual("Mock です。", ret);
        }
    }

MockInjector はもっともシンプルな QuillInjector のカスタマイズ例でもあります。 Quill(QuillInjector) はシンプルで機能が限定的です。 もし QuillInjector をカスタマイズしたいと考えた場合は MockInjector の実装を参考にしてください。

© Copyright The Seasar Project and the others 2004-2009, all rights reserved.