Quillを使うことにより簡単にDIやAOPを行うことができます。 Quillでは属性を用いてDIやAOPの設定を行います。 S2Container.NET 1.3.0から追加された機能です。
ステートレスな業務ロジックを構築するために作成された為、S2Container.NETに比べると機能が制限されています。 ただしS2Container.NETと連携してS2Container.NETに登録されているコンポーネントを利用する機能があります。
Quillを使うことにより簡単にDIやAOPを行うことができます。 Quillでは属性を用いてDIやAOPの設定を行います。 S2Container.NET 1.3.0から追加された機能です。
ステートレスな業務ロジックを構築するために作成された為、S2Container.NETに比べると機能が制限されています。 ただしS2Container.NETと連携してS2Container.NETに登録されているコンポーネントを利用する機能があります。
DIを行う為にはSeasar.Quill.QuillInjectorクラスのInjectメソッドに、DIを行わせたいオブジェクトを渡します。
QuillInjectorはInjectメソッドに渡されたオブジェクトのフィールドから自動的に判断して、 必要なオブジェクトをフィールドにバインドします。その際にあらかじめ設定ファイルでコンポーネントを登録しておく必要はありません。
QuillではQuillでインスタンスを管理するオブジェクトとS2Containerでインスタンスを管理するオブジェクトを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 { ・・(略)・・ }
フィールドに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>
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を使用する場合は、 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を使用する場合は、 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の作成方法はS2AOP.NETと同じです。下記のドキュメントを参照して下さい。
クラス・インタフェースに設定された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) { ・・(略)・・ } ・・(略)・・ }
設定ファイル(App.config)の「quill」タグ内に下記の設定を記述することでデータソースを使うことができます。
各データソースの定義は「dataSource」タグに記述し、
複数データソースを定義する場合は「dataSources」タグ内に
各データソースの設定を書いていきます。
データソースをアプリケーション中で一意に識別するための名前です。
ADO.NET上で使うクラス名を保持する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.TxDataSourceなどのIDataSource実装クラス名を
名前空間付きで指定します。
使用可能なデータソースはS2Container.NETと同じです。
トランザクションを利用する場合は必ずTxDataSource、またはその継承クラスを
使用して下さい。
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 によってインスタンス化されたオブジェクトのインスタンスは、 singleton として保持されます。 しかし Quill が保持するオブジェクトの参照を破棄することができます。
以下のように QuillInjector の Destroy メソッドを呼び出して、Quill が保持するオブジェクトの参照を破棄します。 破棄するオブジェクトが System.IDisposable を実装している場合は参照を破棄する前に Dispose メソッドが呼び出されます。
QuillInjector.GetInstance().Destroy();
テストコードでリモートの処理を呼び出す部分等を 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 の実装を参考にしてください。