-
Notifications
You must be signed in to change notification settings - Fork 1.7k
CustomScopes
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:
- Define a scoping annotation
- Implementing the
Scope
interface - Attaching the scope annotation to the implementation
- Triggering scope entry and exit
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 a
SCOPE_NOT_FOUND
error.
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;
/**
* Scopes a single execution of a block of code. Apply this scope with a
* try/finally block: <pre><code>
*
* scope.enter();
* try {
* // explicitly seed some seed objects...
* scope.seed(Key.get(SomeObject.class), someObject);
* // create and access scoped objects
* } finally {
* scope.exit();
* }
* </code></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(), you must include a
* corresponding binding:
* <pre><code>
* bind(key)
* .toProvider(SimpleScope.<KeyClass>seededKeyProvider())
* .in(ScopeAnnotation.class);
* </code></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();
// don't remember proxies; these exist only to serve circular dependencies
if (Scopes.isCircularProxy(current)) {
return current;
}
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;
}
}
You must attach your scoping annotation to the corresponding scope
implementation. Just like bindings, you can configure this in your module's
configure()
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.
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 SimpleScope
enabled:
@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();
}
}
Be sure to call exit()
in a finally
clause, otherwise the scope will be left
open when an exception is thrown.
-
User's Guide
-
Integration
-
Extensions
-
Internals
-
Releases
-
Community