まず。この物語はフィクションであり このエントリ上のコードはあくまでもチュートリアル用のコードであり、実用性に関しては(ry
という訳で、id:happy_ryoのリクエストにお応えして、id:daisuke-mが書いたDIのお話のパクリです。Guiceを使ってみます。
ポリモーフィズムの例をもうちっと実用的に書いてみた。 - 都元ダイスケ IT-PRESSの続きとして書かれた、「DIのお話を書いてみる」をGuiceで書いたものです。必ずしもネタ元と同じ展開になるとは限りません...というか、Seasar2とGuiceでは機能が違うから同じ展開は難しいのです。が、「DIとはなんぞや?」を説明する、という目的とサンプルアプリケーションは同じです。
まずはいきなりMain...ではなくBusinessLogicを見てみますか。
本家のdaisuke-mの展開だとまずMainになっているが、こちらはBusinessLogicから。
import com.google.inject.Inject; public class BusinessLogic { private Converter converter; @Inject public BusinessLogic(Converter converter) { this.converter = converter; } public void doBusiness(Table table) { String sql = converter.convert(table); System.out.println(sql); } }
元々のBusinessLogic.javaとの差分は2行だけ。
- まず1行目の「import com.google.inject.Inject;」は、GuiceのAnnotation. 単なるimport。
- で、本題はコンストラクタを修飾している「@Inject」。これのために先ほどのimportがある。今回は「DI」つまり「Dependency(依存) Injection(注入)」の話であって、その「Inject」を行うための修飾となる。
BusinessLogicが「依存」しているConverterのインスタンスを「うまい具合に設定してね」という事をGuiceに伝えるための表現。
ではMain見てみますか。
import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; public class Main { public static void main(String[] args) { Table table = new Table("T_HOGE"); table.columns.add(new Column("ID", "integer")); table.columns.add(new Column("CONTENTS", "string")); // Guiceの設定 Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(Converter.class).to(MySQLConverter.class); } }); BusinessLogic business = injector.getInstance(BusinessLogic.class); business.doBusiness(table); } }
main()メソッド内のtableがらみの3行はネタ元のソースだが「Guiceの設定」から下がこのサイトオリジナルのもの。
- 「Guice.createInjector()」で「Injector」のインスタンスを生成している。こいつこそが「DIContainerの本体」みたいなものとなる。
- 「Injector」のインスタンスを生成する際に、AbstractModuleを継承した匿名Classを引数に渡している。これが「DIContainerへの設定情報」となる。GuiceはXmlとか使わず、Javaで設定するのだ。
- その設定方法として、「bind().to()」という表現方法で「Converter」という型には「MySQLConverter」というclassを割り当ててね、という設定をしている。めっさ簡単に単純化して言うと「Converter.classをKeyにMySQLConverter.classを設定」しているカンジ。Mapみたいなイメージでも構わない。
- BusinessLogicのインスタンスを作成する時は、自分でnewせずにInjectorのインスタンスに対して「getInstance()」でBusinessLogicのインスタンスを要求してる。すると、BusinessLogicを生成しようとしたGuiceが、先ほどの「@Inject」という修飾を見つけて「Converter」のインスタンスをInjectしなきゃ!となり、その際には「ConverterにはMySQLConverter」という設定を見て「MySQLConverter」のインスタンスをBusinessLogicにInjectしてくれるのだ。
ちょっと話がそれるけど…BusinessLogicを少し修正
少しBusinessLogicを修正してみる。こちらの方が「Inject」されてる!と感じやすいかも?しれない。
import com.google.inject.Inject; public class BusinessLogic { @Inject private Converter converter; public void doBusiness(Table table) { String sql = converter.convert(table); System.out.println(sql); } }
Converterを引数にしたコンストラクタを削除して、private で宣言しているfieldに対して「@Inject」で修飾してみた。これでも、Mainはそのままでも動作する。privateとかおかまいなしにGuiceがInjectしてくれるためだ。
なお、この修正を行った後で、Mainクラス内のBusinessLogicのインスタンス生成をGuiceのInjectorに依頼するのではなく、自分自身でnewしてみよう。つまり「BusinessLogic business = injector.getInstance(BusinessLogic.class);」を「BusinessLogic business = new BusinessLogic();」とする。この場合は以下のようなExceptionが発生する。
この無理矢理っぽい動作を見ると「誰かが不可視なはずのfieldに何かを注入しやがっている!」という事を実感しやすいかもしれん、と思っただけです。
いわゆる、ぬるぽですな。そりゃそうです、BusinessLogic#Converterはコンストラクタでも受け取らないし、setterすら無い、「設定できないはずのfield」となっているわけですから。Exception in thread "main" java.lang.NullPointerException at BusinessLogic.doBusiness(BusinessLogic.java:8) at Main.main(Main.java:19)
この無理矢理っぽい動作を見ると「誰かが不可視なはずのfieldに何かを注入しやがっている!」という事を実感しやすいかもしれん、と思っただけです。
あれ?ひとつのInterfaceに対してひとつの実装の型を指定できるだけ?
ネタ元では、「mysql」「postgresql」という名前でConverterの実装を切り分けているようですね。では、Guiceでも同じ事をやってみよう。Guiceの設定部分を次のように修正する。
// Guiceの設定 Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(Converter.class).annotatedWith(Names.named("mysql")).to( MySQLConverter.class); bind(Converter.class).annotatedWith(Names.named("postgresql")).to( PostgreSQLConverter.class); } });
bind().annotatedWith().to()、という流れ。「"mysql"という修飾があるConverterの実装としてMySQLConverter、"postgresql"という修飾があるConverterの実装としてPostgreSQLConverter」という設定をしている。見たまんまだ。で、これを使う側のBusinessLogicのソースは以下のようになる。
import com.google.inject.Inject; import com.google.inject.name.Named; public class BusinessLogic { @Inject @Named("postgresql") private Converter converter; public void doBusiness(Table table) { String sql = converter.convert(table); System.out.println(sql); } }
...あれ?これだと"mysql","postgresql"を選択するときに毎回ソースを触らないといけないな…。
ま、今回はこれでいいとしよう(他の方法がわかんないしw)。GuiceでのDIの簡単な説明についてはこんなところで。
0 件のコメント:
コメントを投稿