= カスタムスコープ =
ユーザが自身のカスタムスコープは通常推奨されない。
多くのアプリケーションではビルトインスコープで十分である。
もしウェブアプリケーションを記述しているのであれば、ServletModuleがシンプルでよくテストされたHTTPリクエスト及びHTTPセッション向けのスコープ実装を提供してくれる。
カスタムスコープの作成は複数のステップからなる。
1. スコーピングアノテーションを定義する
1. スコープインターフェースを実装する
1. スコープアノテーションを実装にアタッチする
1. スコープ入り口出口をトリガリングする
== スコーピングアノテーションを定義する ==
スコーピングアノテーションで目的のスコープを識別する。
これを使って、Guiceの生成するタイプ、@Providesメソッド、そしてbindステートメントのin()句をアノテートする。
以下のコードをコピーして変更し、スコーピングアノテーションを定義すること。
{{{
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
@Target({ TYPE, METHOD }) @Retention(RUNTIME) @ScopeAnnotation
public @interface BatchScoped {}
}}}
Tip: 目的のスコープがリクエストやセッションを表すならば(SOAPリクエストのような), Guiceのサーブレット拡張に存在するRequestScopedやSessionScopedを考慮すべきである。
そうしないと、誤ったアノテーションをimportしてしまう可能性がある。
そのような問題をデバッグするのはフラストレーションになるだろう。
== スコープの実装 ==
The scope interface ensures there's at most one type instance for each scope instance.
SimpleScopeがスレッド毎実装として適当なスタートだろう。
このクラスをコピーして、要望に応じて変更すること。
{{{
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.Maps;
import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Scope;
import java.util.Map;
/**
* 一つのコードブロックの実行をスコープする
* このスコープをtry/finallyブロックに適用すること:
{@code
*
* scope.enter();
* try {
* // 明示的に「種」オブジェクトを撒く
* scope.seed(Key.get(SomeObject.class), someObject);
* // create and access scoped objects
* } finally {
* scope.exit();
* }
* }
*
* The scope can be initialized with one or more seed values by calling
* seed(key, value)
before the injector will be called upon to
* provide for this key. A typical use is for a servlet filter to enter/exit the
* scope, representing a Request Scope, and seed HttpServletRequest and
* HttpServletResponse. For each key inserted with seed(), it's good practice
* (since you have to provide some binding anyhow) to include a
* corresponding binding that will throw an exception if Guice is asked to
* provide for that key if it was not yet seeded: {@code
*
* bind(key)
* .toProvider(SimpleScope.seededKeyProvider())
* .in(ScopeAnnotation.class);
* }
*
* @author Jesse Wilson
* @author Fedor Karpelevitch
*/
public class SimpleScope implements Scope {
private static final Provider SEEDED_KEY_PROVIDER =
new Provider() {
public Object get() {
throw new IllegalStateException("If you got here then it means that" +
" your code asked for scoped object which should have been" +
" explicitly seeded in this scope by calling" +
" SimpleScope.seed(), but was not.");
}
};
private final ThreadLocal, Object>> values
= new ThreadLocal, Object>>();
public void enter() {
checkState(values.get() == null, "A scoping block is already in progress");
values.set(Maps., Object>newHashMap());
}
public void exit() {
checkState(values.get() != null, "No scoping block in progress");
values.remove();
}
public void seed(Key key, T value) {
Map, Object> scopedObjects = getScopedObjectMap(key);
checkState(!scopedObjects.containsKey(key), "A value for the key %s was " +
"already seeded in this scope. Old value: %s New value: %s", key,
scopedObjects.get(key), value);
scopedObjects.put(key, value);
}
public void seed(Class clazz, T value) {
seed(Key.get(clazz), value);
}
public Provider scope(final Key key, final Provider unscoped) {
return new Provider() {
public T get() {
Map, Object> scopedObjects = getScopedObjectMap(key);
@SuppressWarnings("unchecked")
T current = (T) scopedObjects.get(key);
if (current == null && !scopedObjects.containsKey(key)) {
current = unscoped.get();
scopedObjects.put(key, current);
}
return current;
}
};
}
private Map, Object> getScopedObjectMap(Key key) {
Map, Object> scopedObjects = values.get();
if (scopedObjects == null) {
throw new OutOfScopeException("Cannot access " + key
+ " outside of a scoping block");
}
return scopedObjects;
}
/**
* Returns a provider that always throws exception complaining that the object
* in question must be seeded before it can be injected.
*
* @return typed provider
*/
@SuppressWarnings({"unchecked"})
public static Provider seededKeyProvider() {
return (Provider) SEEDED_KEY_PROVIDER;
}
}
}}}
== アノテーションを実装にバインドする ==
スコーピングアノテーションは関係するスコープ実装にアタッチされなければならない。
ちょうどバインディングのように、これをモジュールのconfigure()メソッドの中で行うことができる。
通常は、スコープそのものもバインドすることができるので、インターセプタやフィルタもそれを使うことができる。
{{{
public class BatchScopeModule {
public void configure() {
SimpleScope batchScope = new SimpleScope();
// Guiceにスコープのことを伝える。
bindScope(BatchScoped.class, batchScope);
// スコープを注入可能にする
bind(SimpleScope.class)
.annotatedWith(Names.named("batchScope"))
.toInstance(batchScope);
}
}
}}}
== スコープをトリガリングする ==
SimpleScopeは手作業で入りと出を制御する必要がある。
通常これは、フィルタやインターセプタと言った低レベルなインフラストラクチャコードの中で行われる。
finallyでexit()を呼び出すことを忘れないこと。さもないと、例外発生時にまでスコープは開いたままになってしまう。
{{{
@Inject @Named("batchScope") SimpleScope scope;
/**
* Runs {@code runnable} in batch scope.
*/
public void scopeRunnable(Runnable runnable) {
scope.enter();
try {
// explicitly seed some seed objects...
scope.seed(Key.get(SomeObject.class), someObject);
// create and access scoped objects
runnable.run();
} finally {
scope.exit();
}
}
}}}