目次


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

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

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

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

 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のみを利用することができます

 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プロジェクトに使用例があります)

 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();