CustomScopes (original) (raw)

Custom Scopes

It is generally recommended that users do not write their own custom scopes — the built-in scopes should be sufficient for most applications. If you're writing a web application, the ServletModule provides simple, well tested scope implementations for HTTP requests and HTTP sessions.

Creating custom scopes is a multistep process:

  1. Define a scoping annotation
  2. Implementing the Scope interface
  3. Attaching the scope annotation to the implementation
  4. Triggering scope entry and exit

Defining a scoping annotation

The scoping annotation identifies your scope. You'll use it to annotate Guice-constructed types, @Provides methods, and in the in() clause of a bind statement. Copy-and-customize this code to define your scoping annotation:

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: If your scope represents a request or session (such as for SOAP requests), consider using the RequestScoped and SessionScoped annotations from Guice's servlet extension.

When using a custom scope, make sure you have imported the correct scope annotation. Otherwise you may get aSCOPE_NOT_FOUNDerror.

Implementing Scope

The scope interface ensures there's at most one type instance for each scope instance. SimpleScope is a decent starting point for a per-thread implementation. Copy this class into your project and tweak it to suit your needs.

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;

/**

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<Map<Key, Object>> values = new ThreadLocal, 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 void seed(Key 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 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<Key<?>, Object> scopedObjects = getScopedObjectMap(key);

    @SuppressWarnings("unchecked")
    T current = (T) scopedObjects.get(key);
    if (current == null && !scopedObjects.containsKey(key)) {
      current = unscoped.get();

      // don't remember proxies; these exist only to serve circular dependencies
      if (Scopes.isCircularProxy(current)) {
        return current;
      }

      scopedObjects.put(key, current);
    }
    return current;
  }
};

}

private Map<Key, 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; }

/**

Registering the Scope

You must attach your scoping annotation to the corresponding scope implementation. Just like bindings, you can configure this in your module'sconfigure() method.

public final class BatchScopeModule extends AbstractModule { private final SimpleScope batchScope = new SimpleScope();

@Override protected void configure() { // tell Guice about the scope bindScope(BatchScoped.class, batchScope); }

@Provides @Named("batchScope") SimpleScope provideBatchScope() { return batchScope; } }

public final class ApplicationModule extends AbstractModule { @Override protected void configure() { install(new BatchScopeModule()); }

// Foo is bound in BatchScoped @Provides @BatchScoped Foo provideFoo() { return FooFactory.createFoo(); } }

We also bind the scope implementation as @Named("batchScope") SimpleScope. This is necessary since we will need to use batchScope to trigger the scope in the next step.

Triggering the Scope

Custom Scope implementation requires that you manually enter and exit the scope. Usually this lives in some low-level infrastructure code, such as the entry point of a web server. For example, to run a piece of code with SimpleScopeenabled:

@Inject @Named("batchScope") SimpleScope scope;

/**

} finally {
  scope.exit();
}

}

Be sure to call exit() in a finally clause, otherwise the scope will be left open when an exception is thrown.