guiceの基本的なインジェクション

はじめに


目次 | 次へ

前提

こんな感じのクラスを作成するとします。

サービスのインターフェース Service
サービスの実装クラス ServiceImpl
サービスをインジェクションされるクラスのインターフェース Client
サービスをインジェクションされるクラスの実装 ClientImpl
サービスをインジェクションされたClientを利用するクラス Test

サービスとクライアントのコード

こんな感じです。

  • Service
public interface Service {
    public void execute();
}
  • ServiceImpl
public class ServiceImpl implements Service {
    public void execute() {
        System.out.println("Service is executed.");
    }
}
  • Client
public interface Client {
    public void executeService();
}
  • ClientImpl
public class ClientImpl implements Client {
    private Service service_;
    
    public void setService(Service service) {
        service_ = service;
    }
    
    public void executeService() {
        System.out.println("Client is executed.");
        service_.execute();
    }
}

Springの場合

guiceでDIする前に、比較として、デファクトなSpringでDIしてみたいと思います。

まず、Springの場合は、依存性を解決するためには以下のようなBean定義ファイルを作成する必要があります。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       (略)
       >
    <bean id="service" class="org.chiba.impl.ServiceImpl" />
    
    <bean id="client" class="org.chiba.impl.ClientImpl">
        <property name="service" ref="service" />
    </bean>
</beans>

上記のファイルは、とりあえずクラスパス直下に「applicationContext.xml」というファイル名で保存します。上記のBean定義ファイルを使って、Clientを通してServiceを実行するためのTestクラスのコードは以下のようになります。

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Client client = (Client)context.getBean("client");
        client.executeService();
    }
}

実行結果は以下のようになります。

Client is executed.
Service is executed.

TestはClientImplに直接依存していませんし、ClientImplはServiceImplに直接依存していませんが、それぞれClientImplとServiceImplのメソッドが実行されていることが分かると思います。

Guiceの場合

上記のことをGuiceで行う場合にはまず、DI対象のsetterに「@Inject」アノテーションを付加する必要があります。

public class ClientImpl implements Client {
    private Service service_;
    
    @Inject                                                                ←←←←← ココ
    public void setService(Service service) {
        service_ = service;
    }
    
    public void executeService() {
        System.out.println("Client is executed.");
        service_.execute();
    }
}

次に、Bean定義ファイルの代わりに、「モジュール」と呼ばれるクラスを「AbstractModule」を継承して作成する必要があります。

public class TestModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Service.class).to(ServiceImpl.class).in(Scopes.SINGLETON);
        bind(Client.class).to(Client.class).in(Scopes.SINGLETON);
    }
}

これらを利用するTestクラスのコードは以下のようになります。

public class Test {
    public static void main(String[] args) {
        Module module = new TestModule();
        Injector injector = Guice.createInjector(module);
        Client client = injector.getInstance(Client.class);
        client.executeService();
    }
}

実行結果は以下のようになります。

Client is executed.
Service is executed.

当然ですけど、Springの場合と同じですね。

比較

DIの設定をXMLで書くかコードで書くかは好みの分かれるところ*1ではあると思いますが、

  • Spring
    • Client client = (Client)context.getBean("client");
  • Guice
    • Client client = injector.getInstance(Client.class);

キャストや文字列リテラルを用いなくてもよい(IDEの補完が効く)のはGuice側のメリットだと思います。一方、Springの場合はClientやServiceはSpring自身にも依存していないのに対して、Guiceの場合は

    @Inject
    public void setService(Service service) {
        service_ = service;
    }

ここでClientがGuice自身に依存してしまっているのが残念というかデメリットと言えばデメリットだと思います。

*1: あまりXMLが好きだと言う人はいないかもしれませんが。