カスタムスコープ
ユーザが自身のカスタムスコープは通常推奨されない。 多くのアプリケーションではビルトインスコープで十分である。 もしウェブアプリケーションを記述しているのであれば、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(); } }