カスタムスコープ
ユーザが自身のカスタムスコープは通常推奨されない。 多くのアプリケーションではビルトインスコープで十分である。 もしウェブアプリケーションを記述しているのであれば、ServletModuleがシンプルでよくテストされたHTTPリクエスト及びHTTPセッション向けのスコープ実装を提供してくれる。
カスタムスコープの作成は複数のステップからなる。
- スコーピングアノテーションを定義する
- スコープインターフェースを実装する
- スコープアノテーションを実装にアタッチする
- スコープ入り口出口をトリガリングする
スコーピングアノテーションを定義する
スコーピングアノテーションで目的のスコープを識別する。 これを使って、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ブロックに適用すること: <pre> {@code
*
* scope.enter();
* try {
* // 明示的に「種」オブジェクトを撒く
* scope.seed(Key.get(SomeObject.class), someObject);
* // create and access scoped objects
* } finally {
* scope.exit();
* }
* }</pre>
*
* The scope can be initialized with one or more seed values by calling
* <code>seed(key, value)</code> 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 <i>some</i> 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: <pre> {@code
*
* bind(key)
* .toProvider(SimpleScope.<KeyClass>seededKeyProvider())
* .in(ScopeAnnotation.class);
* }</pre>
*
* @author Jesse Wilson
* @author Fedor Karpelevitch
*/
public class SimpleScope implements Scope {
private static final Provider<Object> SEEDED_KEY_PROVIDER =
new Provider<Object>() {
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<Map<Key<?>, Object>> values
= new ThreadLocal<Map<Key<?>, Object>>();
public void enter() {
checkState(values.get() == null, "A scoping block is already in progress");
values.set(Maps.<Key<?>, Object>newHashMap());
}
public void exit() {
checkState(values.get() != null, "No scoping block in progress");
values.remove();
}
public <T> void seed(Key<T> key, T value) {
Map<Key<?>, 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 <T> void seed(Class<T> clazz, T value) {
seed(Key.get(clazz), value);
}
public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
return new Provider<T>() {
public T get() {
Map<Key<?>, 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 <T> Map<Key<?>, Object> getScopedObjectMap(Key<T> key) {
Map<Key<?>, 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 <T> Provider<T> seededKeyProvider() {
return (Provider<T>) 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();
}
}