Android+Guice その3

※2012・10 こちらのページにまとめるようにしてみました。

今回はScope編。といってもSingletonにするってだけですが。

前回getInstance()でインスタンスを取得しましたけど、

Client client1 = injector.getInstance(Client.class);
Client client2 = injector.getInstance(Client.class);

を実行したら結果はどうなるでしょう?
答えはclient1とclient2は それぞれ別instanceが作成されて戻ります。

Guiceでは特に指定しない限りはこれがデフォルトの動作でgetInstance()される度にインスタンスを作ってくれます。これはClientに注入されたServiceImplについても同じです。

インスタンスを全体で一つに制限してSingletonにしたい場合いくつかの方法があります。

一つ目はSingletonにしたいConcrete Classに対して@Singletonアノテーションを追加します。

import com.google.inject.Singleton;

@Singleton
public class ServiceImpl implements Service {
    @Override
    public String getResponse(String message) {
        return "ハイサイ!" + message;
    }
}

これでSingletonになります。
ただ、この方法は別の実装を作ったときに@Singletonを忘れてしまって、実装を切り替えた時に挙動が異なってしまう危険がある様に思います。
@Singletonアノテーションは ソースファイル上で「これはSingletonだよ!」という実装者へのマーク程度と考えて(それでも書いておいた方が良いです)、Moduleのconfigure()内でin(Singleton.class)を併用するのが良いのかなと個人的には思ってます。

bind(ServiceImpl.class).in(Singleton.class);
bind(Service.class).to(ServiceImpl.class);

ちなみに今回の場合は以下のように一行で書けますが複数Interfaceを実装している場合に注意が必要です。

bind(Service.class).to(ServiceImpl.class).in(Singleton.class);

以下の場合はService.classが要求された時と、Another.classが要求された時に それぞれでInstanceが作成され、2つのInstanceが作成されることになります。

bind(Service.class).to(ServiceImpl.class).in(Singleton.class);
bind(Another.class).to(ServiceImpl.class).in(Singleton.class);

前回のソースを変更してSingletonで作られている事を確認しましょう。
まずはSingletonである事の宣言

import com.google.inject.Singleton;

@Singleton
public class ServiceImpl implements Service {
    @Override
      public String getResponse(String message) {
          return "ハイサイ!" + message;
      }
}

続いてClientのServiceにアクセスできるように作られたオブジェクトの数を数えるのとServiceを比較できるようにgetterを追加。

import com.google.inject.Inject;

public class Client {
    @Inject
    private Service service;
    private static int objCount = 0;
    private final int number;

    public Client() {
        objCount++;
        number = objCount;
        System.out.println("Create instance " + number);
    }

    public void execute() {
        System.out.println(service.getResponse("Guice" + number));
    }

    public Service getService() {
        return service;
    }

    public int getNumber() {
        return number;
    }
}

最後にMainを書き換えてSingletonかどうかを確認します。

import com.google.inject.Guice;
import com.google.inject.Injector;

public class Main {

    public static void main(String[] args) {
        // Injectorを作成する。この時にBindの設定を書いたModuleのInstanceを渡す。
        Injector injector = Guice.createInjector(new OkinawaModule());

        System.out.println("Injector is created.");

        Client client1 = injector.getInstance(Client.class);
        Client client2 = injector.getInstance(Client.class);

        // 実行する。
        client1.execute();
        client2.execute();

        // service の instanceはどうなった?
        if (client1.getService() == client2.getService()) {
            System.out.println("Service is singleton.");
        } else {
            System.out.println("Service is NOT singleton.");
        }
    }
}

Service.javaとOkinawaModule.javaは昨日のと同じです。

さて実行してみましょう。

>javac -cp di.jar;. *.java
>java -cp di.jar;. Main
Injector is created.
Create instance 1
Create instance 2
ハイサイ!Guice1
ハイサイ!Guice2
Service is singleton.

この通りServiceImplはインスタンスが一つしか作成されずSingletonになっています。

ServiceImpl.javaの@Singletonをはずして、OkinawaModule.javaを以下の様に書き換えても同じ様な結果が得られます。

import com.google.inject.AbstractModule;
import com.google.inject.Singleton;

public class OkinawaModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(ServiceImpl.class).in(Singleton.class);
        bind(Service.class).to(ServiceImpl.class);
    }
}

次回はSingletonの初期化タイミングについて

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.