Upload page content

You can upload content for the page named below. If you change the page name, you can also upload content for another page. If the page name is empty, we derive the page name from the file name.

File to load page content from
Page name
Comment

Locked History Actions

guice/Manual/Extensions/CustomScopes

カスタムスコープ

ユーザが自身のカスタムスコープは通常推奨されない。 多くのアプリケーションではビルトインスコープで十分である。 もしウェブアプリケーションを記述しているのであれば、ServletModuleがシンプルでよくテストされたHTTPリクエスト及びHTTPセッション向けのスコープ実装を提供してくれる。

カスタムスコープの作成は複数のステップからなる。

  1. スコーピングアノテーションを定義する
  2. スコープインターフェースを実装する
  3. スコープアノテーションを実装にアタッチする
  4. スコープ入り口出口をトリガリングする

スコーピングアノテーションを定義する

スコーピングアノテーションで目的のスコープを識別する。 これを使って、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のサーブレット拡張に存在するRequestScopedSessionScopedを考慮すべきである。 そうしないと、誤ったアノテーションを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();
    }
  }