LiQ Container は、Dependency Injection の考え方に基づいて、 任意のクラスのインスタンスを生成することができる、汎用の ファクトリ・フレームワークです。 複雑に絡み合ったオブジェクトを組み立てる煩雑な処理を、 シンプルなコードで実現することができます。
LiQ Container を使用するには、最低限以下のインポート文が必要です。
import jp.liq.container.Container; import static jp.liq.container.ContainerVocabulary.*;
LiQ Container を使うための最小のコードはたったこれだけです。
Container container = new Container(); sentinel.configure(container);
sentinel はインタフェース ContainerVocabulary に定義された static フィールドです。 LiQ Container では、このようにインタフェースに定義された static フィールドを起点にして Container の設定を行います。
ContainerVocabulary には他にも Container を設定するための語彙となる static フィールドが いくつか定義されています。これらの static フィールドを参照するには、static インポートを 使用するか、使用したいクラスで ContainerVocabulary を実装します。
この設定によって、Container はコンストラクタを一つ持つ任意の具象クラスを インスタンス化することができるようになります。
では実際に、以下に示すクラス A のインスタンスを取得してみましょう。
public class A { private B b; public A(B b) { this.b = b; } public B getB() { return b; } } public class B { }
このクラス A のインスタンスを取得するコードは以下のようになります。
A a = container.get(A.class); assertNotNull(a); assertNotNull(a.getB());
この例において、クラス A のコンストラクタにはクラス Bのインスタンスを渡す 必要があります。 Container は A をインスタンス化する前に、 まず B をインスタンス化し、Bをコンストラクタの引数に渡して A を インスタンス化する、という処理を行います。
このように、Container はあるクラスのインスタンスを生成する際に必要となる他のクラスの インスタンスを再帰的に取得します。
あるクラスのインスタンスを生成する処理を Container に任せないで、 自分で生成したインスタンスを使用することができます。 サンプルコードは以下のようになります。
Container container = new Container(); B b = new B(); //1. components.configure(container) .define(component.instance(B.class, b)); //2. sentinel.configure(container);
2. の設定によって、この Container から 以下のコードを実行した際、 A のコンストラクタには、新しく Container がインスタンス化したB のインスタンスではなく、 1. のインスタンス b が渡されます。
A a = container.get(A.class); assertSame(b, a.getB());
Container がクラスのインスタンスを取得するルールは、先に設定されたものの方が優先されるため、 あらかじめ登録されたBのインスタンスを使用する、以下の設定が、
components.configure(container) .define(component.instance(B.class, b));
新たに B のインスタンスを生成する、以下の設定よりも優先されます。
sentinel.configure(container);
インスタンスを登録する機能は、javax.servlet.http.HttpServletRequest や java.sql.Connection など、Container が自分でインスタンス化することが出来ないオブジェクトを Inject したいときに有効です。
コンストラクタが複数ある場合や、コンストラクタに String や Integer など、Container が自分で 取得できないオブジェクトを渡したい場合、ある条件によって、要求されたクラスの異なる サブクラスを生成したい場合等、sentinel があるクラスのインスタンスを素直に生成できない ケースも多々存在します。そのような場合は、ファクトリメソッドを使用してあるクラスのインスタンスを 生成するように設定することができます。
例えば、以下のようなクラスCは、複数のコンストラクタを持つため、sentinelによってインスタンス 化することは出来ません。インスタンス化するときに、どのコンストラクタを使ってよいのか、 判断できないからです。
public class C { private A a; public C() { } public C(A a) { this.a = a; } public A getA() { return a; } }
この時、Cのインスタンスを生成するファクトリメソッド createC を以下のように定義することができます。
public class CFactory { @Factory public C createC(A a) { return new C(a); } }
ここで、Cのインスタンスをメソッド createC によって、生成するように設定するコードは、 以下のようになります。
Container container = new Container(); B b = new B(); components.configure(container) .define(component.factory(new CFactory())) //1. .define(component.instance(B.class, b)); sentinel.configure(container); C c = container.get(C.class); assertSame(b, c.getA().getB());
1. の設定によって、CFactory のインスタンスメソッドのうち、アノテーション jp.liq.container.vocabulary.Factory が付与されたものが、そのメソッドの戻り値として指定されたクラスを生成するために使用されるようになります。
メソッドの引数は、Container がそのクラスのインスタンスを自動的に取得して渡します。
なお、1. の設定の代わりに、以下のようにCFactory のクラスオブジェクトを指定することもできます。
components.configure(container) .define(component.factory(CFactory.class)) .define(component.instance(B.class, b));
この場合、まず Container は 他のクラスと同様に、CFactory のインスタンスを生成してから、 createC メソッドを呼び出します。
Container に対し、あるクラスが要求されたとき、 そのクラスのサブクラスのインスタンスを返すように設定することが出来ます。
例えば、以下のようなBのサブクラスExtendsBのインスタンスを、
public static class ExtendsB extends B { }
Bが要求された時に生成して返すようにするには、以下のようにします。
Container container = new Container(); components.configure(container) .define(component.mapping(B.class, ExtendsB.class)); //1. sentinel.configure(container);
1. の設定によって、ExtendsB が B にマッピングされます。 このとき、A を取得すると、以下のように A のコンストラクタには ExtendsB のインスタンスが渡されます。
A a = container.get(A.class); assertTrue(a.getB() instanceof ExtendsB);
Container が 生成したインスタンスに対して、フィールドインジェクションまたは、 メソッドインジェクションを行うように設定することができます。
sentinel が生成したインスタンスに対して Field Injection を行うには、 以下のようにします。
Container container = new Container(); sentinel.configure(container).with(injector.toFields());
Inject したいフィールドには、以下のように アノテーション import jp.liq.container.vocabulary.Inject を付与します。
public class D { @Inject public A a; }
これによって、以下のようにDのインスタンスを取得したときに、フィールド a に A のインスタンスが自動的に設定されます。
D d = container.get(D.class); assertNotNull(d.a);
sentinel が生成したインスタンスに対して Method Injection を行うには、 以下のようにします。
Container container = new Container(); sentinel.configure(container).with(injector.toMethods());
Inject したいメソッドには、以下のように アノテーション import jp.liq.container.vocabulary.Inject を付与します。
public class E { private A a; public A getA() { return a; } @Inject public void setA(A a) { this.a = a; } }
これによって、以下のようにDのインスタンスを取得したときに、メソッド setAの引数に に A のインスタンスが自動的に渡されます。
E e = container.get(E.class); assertNotNull(e.getA());
Container に他の Container の設定を取り込むことができます。
例えば、以下の例において、parent に対する設定が container に取り込まれているため、container から C のインスタンスを 取得することが可能になっています。
Container parent = new Container(); components.configure(parent) .define(component.factory(new CFactory())); Container container = new Container(); container.include(parent); sentinel.configure(container); C c = container.get(C.class); assertNotNull(c);