Guiceを使ってみる

Guice(ジュースと読む)は、googleが作成したJava製のDIコンテナです。Guiceオープンソースであり、無償で利用可能です。
以下のURLでGuice本体やドキュメント(PDF)などがダウンロードできます。

Guice
http://code.google.com/p/google-guice/

面白そうなので、ちょっとさわってみました。

Guiceの特徴

Guiceの特徴としては、メタデータの設定にXMLファイルを使用しないところです。なので、DI対象となるBeanの設定はXMLで行いません。代わりにアノテーションなどで設定を行います。あくまでXMLではなく、Javaコードで設定を行うわけです。

使用例1

Guiceの使用方法は以下のようになります。


まず、DI対象となるクラスを作成します。

public class Service {
    public void go() {
        System.out.println("done!");
    }
}


次に上記クラスを利用するクラスです。(DIコンテナ使用)

public class Client {
    public static void main(String[] args) {
        // DIコンテナを取得
        Injector injector = Guice.createInjector();

        // DIコンテナから欲しいインスタンスを取得
        Service service = injector.getInstance(Service.class);

        // インスタンスからメソッド実行
        service.go();
    }
}

上記のようにInjector.getInstanceメソッドの引数に欲しいインスタンスの型を渡すことで、その型のインスタンスを取得することができます。おそらくこれが最も基本的な使用パターンでしょう。

使用例2

GuiceDIコンテナ)から取得したインスタンスは、Guiceによって管理されます。Guice管理下にあるインスタンスが別のインスタンスを参照している場合、この別のインスタンスGuiceの管理対象となるので、DIすることができます。例を見てみましょう。

1つ目のクラス(2つ目のクラスを使用するように変更)

public class Service {

    // DI対象であることを定義
    @Inject
    private Service2 service2;

    public void go() {
        service2.go();
    }
}


2つ目のクラス(新規作成)

public class Service2 {
    public void go() {
        System.out.println("done!");
    }
}


クライアントクラス
DIでServiceのインスタンスを取得します。

public class Client {
    public static void main(String[] args) {
        // DIコンテナを取得
        Injector injector = Guice.createInjector();

        // DIコンテナから欲しいインスタンスを取得
        Service service = injector.getInstance(Service.class);

        // インスタンスからメソッド実行
        service.go();

    }
}

ServiceはService2をnewせずに使用しています。このため、本来ならばService2使用時にNullPointerExceptionが発生します。
しかし、ServiceはGuiceの管理下におかれているので、Service2もGuice管理下となり、必要なとき(Service2のメソッド実行時)にGuiceによってインスタンスが生成されます。このため、NullPointerExceptionは発生しません。

使用例3

これまでの例は、通常のクラスを使用したものでした。しかしDIでは通常、インタフェースを駆使してオブジェクトが疎結合になるようにします。ここではこの例について見ていきます。

まずインタフェースです。

public interface Service {
    public void go();
}


ここで試しに、上記のインタフェースクラスを指定してDIしてみましょう。

public class Client {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector();
        Service service = injector.getInstance(Service.class);
        service.go();
    }
}

上記のコードは正常に動作せず、例外が発生します。

Exception in thread "main" com.google.inject.ConfigurationException: Missing binding to quice.sample.service.Service.
	at com.google.inject.InjectorImpl.getProvider(InjectorImpl.java:696)
	at com.google.inject.InjectorImpl.getProvider(InjectorImpl.java:689)
	at com.google.inject.InjectorImpl.getInstance(InjectorImpl.java:728)
	at quice.sample.main.Client.main(Client.java:15)

理由は単純で、インタフェース型を指定したときにどの実装クラスをインスタンス化すれば良いのか、Guiceはわからないためです。このため、インタフェース型を指定したときに、実際にどの実装クラスをバインドすればよいのかをGuiceに教えてあげる必要があります。


インタフェース実装クラス
インタフェースを指定したときに実際にバインドされる実装クラスを作成します。

public class ServiceImpl implements Service {

    public void go() {
        System.out.println("done!");
    }
}


バインドの定義は、Guiceの「Module」インタフェース実装クラスを作成して行います。

public class MyModule implements Module {
    public void configure(Binder binder) {
        binder.bind(Service.class).to(ServiceImpl.class);
    }
}

Moduleインタフェースはconfigureメソッドを定義しているので、これを実装します。
上記は、Service(インタフェース)を指定すると、ServiceImpl(実装クラス)にバインドされることを定義しています。


クライアントクラス
DIコンテナを取得する際、作成したModule実装クラスを引数に与えて、Guiceにバインドの設定を教えてあげます。

public class Client {
    public static void main(String[] args) {

        // Module実装クラスをcreateInjectorメソッドの引数に渡す
        // これにより、DIコンテナにバインドの設定が適用される
        MyModule module = new MyModule();
        Injector injector = Guice.createInjector(module);
        Service service = injector.getInstance(Service.class);
        
        // 取得したインスタンスのクラスを表示してみる
        System.out.println(service.getClass());
        service.go();
    }
}

で、実行結果です。

class quice.sample.service.ServiceImpl
done!

ServiceImplクラスのインスタンスが取得され、正常にコードが実行されたことが分かりますね。
ちなみに、Module実装クラスで以下のコードを記述して実行すると例外が発生します。

public class MyModule implements Module {
    public void configure(Binder binder) {
        binder.bind(Service.class).to(ServiceImpl.class);
        binder.bind(Service.class).to(ServiceImpl2.class);
    }
}

※ServiceImpl2はServiceImplと同様、Serviceインタフェースの実装クラスだとします。
実行結果は、

Exception in thread "main" com.google.inject.CreationException: Guice configuration errors:

1) Error at guice.sample.module.MyModule.configure(MyModule.java:14):
 A binding to quice.sample.service.Service was already configured at guice.sample.module.MyModule.configure(MyModule.java:13).

1 error[s]
	at com.google.inject.BinderImpl.createInjector(BinderImpl.java:277)
	at com.google.inject.Guice.createInjector(Guice.java:79)
	at com.google.inject.Guice.createInjector(Guice.java:53)
	at com.google.inject.Guice.createInjector(Guice.java:43)
	at quice.sample.main.Client.main(Client.java:17)

バインドする実装クラスはすでに設定されてますよ、ということのようです。
ここまで紹介してきた使用方法では、一つのインタフェースに対して、複数の実装クラスがあった場合に対処できません。
でも、当然のごとく、対処する方法がGuiceには用意されています。これについてはまた後日。