Scope

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

以下みたいなコードに対して Singletonを実現したい!って場合

Client client1 = injector.getInstance(Client.class);
// どこか別の場所
Client client2 = injector.getInstance(Client.class);

Guiceでは特に指定しない限りはこれがデフォルトの動作で getInstance()される度にインスタンスを作る。

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

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

import com.google.inject.Singleton;

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

簡単だけど、注意も必要。インターフェースの実装が複数ある場合、一部の実装だけアノテーションを忘れてしまう危険性がある。ので以下の inと併用すると良いかと思う。

// ServiceImplは Singletonと宣言
bind(ServiceImpl.class).in(Singleton.class);

// 注入をする
bind(Service.class).to(ServiceImpl.class);

以下のように一行でも書けるが この場合 「このbind関係だけSingleton」になるので注意

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

例えば ServiceImplが複数のインターフェースを実装している場合、 以下のようなコードでは ServiceImplは Singletonとはならず、2つインスタンスが出来る

bind(ServiceInterface.class).to(ServiceImpl.class).in(Singleton.class);
bind(AnotherInterface.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.");
        }
    }
}

実行結果

$ 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の初期化タイミングについては また今度