その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

Guice で独自の scope 切って inject したいって時

これを見ると良い.要は CDI のようなものをやるという話である.
CustomScopes · google/guice Wiki · GitHub


ここに書かれているように,

1. 任意の Scope を表す annotation を作る
2. Scope を実装する
3. Scope の実装と annotation を紐付ける (これは Module 内で行なう)
4. Scope#enter()Scope#exit() で Scope のライフサイクルを管理する

という流れに従う.


ここで少し難しく感じるのは2の「Scope を実装する」だと思う.
Wiki に書いてあるように基本的に Wiki に書いてある実装をコピペして使えという感じなのだが,まあ中身は読めばそこまで難しいことはしていない.
enter() する時に Scope が保持する objectを管理する Map を作成し,exit() する時にその Map を破棄するという感じ.あとは seed() で scope が持つ Map に直で object を登録したり,scope() で scope が保持している object Map から対応する object を取ってくるという処理を書いたら良い.
基本的に自分で Scope の実装を書く時はデフォルトの実装をそのまま使い,どこかで独自の処理をしたくなったらその部分を書き換えるという方法で良いと思う.元は thread safe な実装になっているし「大抵は」満足できる.


んでもって,あとは4のライフサイクル管理が若干面倒,一方で直感的ではあるかなあとは思う.
しかし,Module 内で injectee が bind されている時の挙動が個人的にわかりにくかったので書いておく.

例えば Module が以下のようになっていて,

final SimpleScope batchScope = new SimpleScope();
bindScope(BatchScoped.class, batchScope);
bind(SimpleScope.class)
    .annotatedWith(Names.named("batchScope"))
    .toInstance(batchScope);

bind(SomeObject.class)
    .toProvider(SomeObjectProvider.class)
    .in(BatchScoped.class);

Scope を管理する部分が以下のようになっている時,

final SimpleScope scope = injector.getInstance(Key.get(SimpleScope.class, Names.named("batchScope")));
try {
    scope.enter();

    injector.injectMembers(instance);

    // do something
} finally {
    scope.exit();
}

scope 内で injectMembers() しないと SomeObject は inject されない.
scope 内で injectMembers() された時は Module で指定されたように,SomeObject のライフサイクルは scope 内に固定される.ここは直感的.
なお Wiki の注意書きにも書かれているが,finally でちゃんと Scope#exit() しないと scope が閉じられず,ずっと生きながらえてしまうという問題があるので注意が必要.try-catch-resources が使えれば良いような気はしているが……


と,ここまで散々書いたのだけれど,Wiki の冒頭にあるように Custom Scope は自前で積極的に書くべきものではない.
基本的に,Singleton ServletModule が提供する RequestScopedSessionScoped で事足りるはずで,それで足りないケースというのはあまり存在しない.足りないケースの時だけ書くべきであるという事を留意されたし (今回はやんごとなき事情で自前で書きました).