Play1のguice-moduleの使い方

| Comments

現在のプロジェクトで、Play1.2系でGuiceModule(1.2)を使っているのですが、なんかバグったので改めて学び直し。

ドキュメントがなんか不親切だったので色々調べましたが、GuiceModule使えねぇっす。。。

まずは基本から。

playには、手っ取り早くGuiceを使えるGuiceModuleというのがあります。

初期設定として、まずはconf/dependencies.ymlに

1
2
3
4
5
# Application dependencies

require:
- play
- play -> guice 1.2

そして、conf/application.confにも、

1
module.guice=${play.path}/modules/guice-1.2

を追加。

そして、

1
2
$ play dependencies
$ play eclipsify

これでEclipseからGuiceのライブラリが使えます。

細かいことは省きますが、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package di;

import com.google.inject.AbstractModule;

import controllers.ItemDao;
import controllers.ItemDaoImple;
import controllers.ItemService;
import controllers.ItemServiceImpl;

public class Di extends AbstractModule{
	
	@Override
	public void configure() {
		bind(ItemDao.class).to(ItemDaoImple.class);
		bind(ItemService.class).to(ItemServiceImpl.class);
	    }
}

これをどこかに置いておくと、アプリ起動時にconfigure()が走ってDIしてくれます。

Injectする先では、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package controllers;

import javax.inject.Inject;

import play.mvc.Controller;

public class Application extends Controller {
	
	@Inject
	private static ItemService itemService;

    public static void index() {
		renderText(itemService.hoge());
    }

}

というように、@Injectすれば起動時にDIされます。

※import javax.inject.Injectです。ここだけなぜかGuiceではないので注意

ControllerでないクラスへのInjectは、クラスの頭に@InjectSupportをつければ動きます。

さて、ここで問題が。。。

どうやらこのDI、対象のフィールドがstaticで定義されていないとnullになってしまうのです。 簡単に言うと、起動時に一度だけDIされます。インスタンスが一度しか生成されないという意味です。

何が問題かというと、インスタンスをマルチスレッドで呼ばれた場合にデータが不正になるバグを含んでしまいます。 基本的にはマルチスレッド間でのデータの共有は鬼門なので(synclonizedを適切にできればいいが、大抵はミスるor速度劣化する)、インスタンスは別々にするべきです。

しかしGuiceModuleではそれができませんでした。

(さらに、ちゃんと検証してませんが、なんかGuiceModuleがGuice2.0と3.0両方をパスに加えてる気がする。。。)

Guiceライブラリのみで対応。

仕方ないので、Guiceライブラリ単独でなんとかしてみましょう。(Guiceライブラリは、現在3.0が出ているのでそれで。)

  • 先ほどのGuiceModuleを全て消します。
  • 改めて、Guice3.0を入れます。

Diクラスは先ほどと同じでいいのですが、違うのは注入先の@Injectで使っていたjavax.inject.Injectがないことです。

どうするかというと、Injectorというのを呼び出してあげます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package controllers;

import play.mvc.Controller;

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

import di.Di;

public class Application extends Controller {
	
	private static Injector injector = Guice.createInjector(new Di());

    public static void index() {
    	ItemService itemService = injector.getInstance(ItemService.class);
		renderText(itemService.hoge());
    }
}

Controller以外はこんなカンジ。 ここで注入されたインスタンスはフィールド変数にfinalで持っておくのが無難でしょう。(一度のアクセスで複数回呼ばれるかもしれないため)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package services;

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

import di.Di;

public class ItemServiceImpl implements ItemService{

	private static Injector injector = Guice.createInjector(new Di());

	private final ItemDao itemDao;

	public ItemServiceImpl(){
		this.itemDao = injector.getInstance(ItemDao.class);
	}

	@Override
	public String hoge() {
		return itemDao.findByHoge();
	}
}

これで、GuiceModuleと同じように動きます。

最後に、毎回別のインスタンスが作られるように、Diの設定をやり直します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package di;

import com.google.inject.AbstractModule;
import com.google.inject.Scopes;

import controllers.ItemDao;
import controllers.ItemDaoImple;
import controllers.ItemService;
import controllers.ItemServiceImpl;

public class Di extends AbstractModule{
	
	@Override
	public void configure() {
		bind(ItemDao.class).to(ItemDaoImple.class).in(Scopes.NO_SCOPE);
		bind(ItemService.class).to(ItemServiceImpl.class).in(Scopes.NO_SCOPE);
	    }
}

Scopes.NO_SCOPEというのが加わりました。 何をしてるかというと、スコープを持たない=>毎回新しく作りなおす、ということを定義しています。

これでシングルトンでなく、きちんとインスタンスが分かれた設計になりました。

(試しにItemDao.toStringを呼んでみると、たしかにアクセス毎に別アドレスが表示されました。)

Comments