現在のプロジェクトで、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を呼んでみると、たしかにアクセス毎に別アドレスが表示されました。)