Author: limpbizkit
Date: Thu Jan 1 16:43:02 2009
New Revision: 778
Added:
wiki/CustomScopes.wiki
Log:
Created wiki page through web user interface.
Added: wiki/CustomScopes.wiki
==============================================================================
--- (empty file)
+++ wiki/CustomScopes.wiki Thu Jan 1 16:43:02 2009
@@ -0,0 +1,179 @@
+=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:
+ # Define a scoping annotation
+ # Implementing the `Scope` interface
+ # Attaching the scope annotation to the implementation
+ # 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. Otherwise, you may import the
wrong annotation by mistake. That problem can be quite frustrating to debug.
+
+==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;
+
+/**
+ * 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();
+ * }
+ * }</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;
+ }
+}
+}}}
+
+==Binding the annotation to the implementation==
+You must attach your scoping annotation to the corresponding scope
implementation. Just like bindings, you can configure this in your module's
`configure()` method. Usually you'll also bind the scope itself, so
interceptors or filters can use it.
+{{{
+public class BatchScopeModule {
+ public void configure() {
+ SimpleScope batchScope = new SimpleScope();
+
+ // tell Guice about the scope
+ bindScope(BatchScoped.class, batchScope);
+
+ // make our scope instance injectable
+ bind(SimpleScope.class)
+ .annotatedWith(Names.named("batchScope")
+ .toInstance(batchScope);
+ }
+}
+}}}
+
+==Triggering the Scope==
+`SimpleScope` requires that you manually enter and exit the scope. Usually
this lives in some low-level infrastructure code, like a filter or
interceptor. Be sure to call `exit()` in a `finally` clause, otherwise the
scope will be left open when an exception is thrown.
+{{{
+ @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();
+ }
+ }
+}}}
\ No newline at end of file
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"google-guice-dev" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/google-guice-dev?hl=en
-~----------~----~----~----~------~----~------~--~---